Upload a resume and paste a job posting. Get a fit score, a matched/missing/extra skills breakdown visualized in 3D, and a Claude-tailored rewrite of the resume aimed at that specific job.
Portfolio piece #5 (of 5, final app) -- back on TypeScript/Next.js, closing out the portfolio on the stack used in 3 of the 5 apps.
- Next.js 16 (App Router) + TypeScript + Tailwind, deployed on Vercel
- Neon Postgres via Prisma 7 +
@prisma/adapter-pg--ResumeandJobPostingeach have manyAnalysisrows (one per pairing) unpdffor PDF resume text extraction (zero native deps, unlikepdf-parse/pdfjs-dist)- Claude Fable 5 (
lib/ai/analyze-fit.ts) does one grounded call per analysis: given the resume text and job posting text (both ground truth, never invented), it returns a fit score, matched/missing/resume-only skill lists, and a tailored resume rewrite -- as a single JSON object parsed with a defensive markdown-fence strip + try/catch, since every failure mode must surface a real error rather than fail silently - 3D skill-space view (
components/skill-space-scene.tsx): skills positioned bylib/skills/layout.tsaround three fixed cluster centers (matched/missing/resume-only) with a deterministic hash-based jitter per skill name (notMath.random(), so the layout is stable across re-renders rather than reshuffling every time), colored with a reserved status-style palette (green/red/gray) since cluster membership is state, not a category - GitHub OAuth via Auth.js v5
- GitHub Actions CI (lint, typecheck, unit tests, migrate against a real Postgres service, build, smoke test) gating merges to
main; Vercel's Git integration handles the actual deploy
Requires Node 24+.
npm install
cp .env.example .env.local # fill in as you go through the steps below- Create a project at neon.tech. Copy the pooled connection string into
DATABASE_URLand the unpooled one intoDIRECT_URL. npx prisma migrate deploy.
- GitHub → Settings → Developer settings → OAuth Apps → New OAuth App.
- Homepage URL:
http://localhost:3000. Authorization callback URL:http://localhost:3000/api/auth/callback/github(and your Vercel domain's equivalent for production -- GitHub OAuth Apps only support one callback URL at a time, so update this same field after deploying rather than creating a second app). - Fill in
AUTH_GITHUB_ID/AUTH_GITHUB_SECRET. GenerateAUTH_SECRETwithopenssl rand -base64 32.
Get a key from the Anthropic Console, set ANTHROPIC_API_KEY. Claude Fable 5 requires the org's data retention to be 30+ days -- a zero-data-retention org gets a 400 on every request.
Set NEXT_PUBLIC_POSTHOG_KEY / NEXT_PUBLIC_POSTHOG_HOST if you want analytics locally. Safe to leave blank -- calls no-op without a key.
npm run devnpm run test # unit tests -- lib/skills/layout.ts, pure fixtures, no DB
npm run typecheck
npm run lint
npm run buildlib/skills/layout.ts is unit-tested with no database or network dependency. Everything that touches Postgres or Claude is exercised by CI's smoke test and manual end-to-end testing (scripts/manual-verify.ts), not vitest -- files that import "server-only" throw under plain Node, so they can't be unit-tested directly.
Push to GitHub, import the repo on Vercel, and add every variable from .env.example. Vercel deploys automatically on every push to main (production) and every PR (preview); CI in .github/workflows/ci.yml gates merges to main via branch protection but does not itself deploy.