A multi-user job application tracker built as a single full-stack Next.js app — track applications, follow-ups, resumes, and useful links in one place.
Next.js 16 (App Router, Turbopack) · TypeScript (strict) · Tailwind v4 · Clerk auth ·
Prisma 7 (@prisma/adapter-pg) · PostgreSQL (Neon) · React Hook Form + Zod · Recharts.
- Kanban and list views, per-application detail page with tabs (Overview, Timeline, Resume used).
- Quick Add with a collapsible "Add more details (optional)" section (priority, location, work mode, deadline, salary, notes) — collapsed by default so the fast path stays fast.
- Archive / unarchive from the same row menu; dedicated Archived list at
/applications/archived. - "Saved for later" jobs kept separate from active applications.
- Bucketed by due date, plus an "All" tab showing every pending follow-up in one flat list.
- Resume Library: upload, edit, and delete resume versions (already fully wired — pencil/trash icons on each card).
- Resume ↔ job linking: a
resumeVersionIdrelation onJobApplication→ResumeVersion(old free-text field kept as a fallback). The Application tab has a "Resume used" dropdown pulling from your library, with an "Open" link to the file. Resume Library cards show every application that used that resume, clickable through to the job.- Requires a migration:
npx prisma migrate devafter pulling this.
- Requires a migration:
- A personal, foldered link library at
/vault— save any URL (e.g. a job posting) into a folder tree. - Schema:
VaultFolder(self-referencing,onDelete: CascadeonparentId),VaultLink, andVaultLinkFolderas a many-to-many join (denormalizeduserIdso batched loads never need an extra join). - Quick-add-with-path (e.g.
linkedin/java/threading) resolves/creates the whole folder chain and attaches the link in oneprisma.$transaction— atomic, no half-created paths on failure. - Cycle-safe folder moves: checked client-side against the cached tree for instant feedback, and re-checked server-side against freshly-fetched rows inside the same transaction as the move.
- Move is implemented as a "Move to..." picker dialog, not literal drag-and-drop, by deliberate choice — the transactional/cycle-check correctness was prioritized over pixel-level DnD. Real HTML5 drag-and-drop can be layered on top later if wanted.
- Job rows have a "Save URL to Vault" action in their menu (when a posting URL exists).
- Requires a migration for the new tables.
- A
/captureroute (outside the main app's sidebar layout) intended for a browser bookmarklet: opens a popup with the current page's URL/title pre-filled, lets you file it straight into either an application (viaquickAddJob) or the Vault (viaquickAddVaultLink) without duplicating existing actions. - Auth-gated via Clerk, preserving the capture URL through the sign-in redirect.
- Best-effort server-side Open Graph tag fetch (strict
AbortControllertimeout, ~3s) to suggest a better title when the page didn't supply one — never blocks or throws. - Status: functional pieces built incrementally; verify the full capture → save flow end-to-end before relying on it.
- Removed a
QueryClientProvider(@tanstack/react-query) that wrapped the entire app in the root layout despite zero components usinguseQuery/useMutation— was dead client JS on every page load. - Pruned three unused dependencies:
react-query,framer-motion,cmdk. - Added
optimizePackageImportsforrecharts,lucide-react, anddate-fns. - Renamed
middleware.ts→proxy.ts(Next.js 16's current convention;clerkMiddleware()code itself is unchanged, per Clerk's docs). - Analytics charts are dynamically imported (
next/dynamic,ssr: false) to keep them out of the initial bundle. - Partial Prerendering (PPR) deliberately not adopted — every page here is ~100% per-user data, so there's no static shell worth prerendering.
- Neon's free-tier compute auto-suspends when idle; the first query after that has to wait for
it to wake up. Fixed in
src/lib/prisma.ts/src/lib/auth.ts:- Raised the connection timeout to 15s.
- Added a retry (up to 3 attempts, backing off) specifically for Prisma's
P1001("can't reach database server") error, wrapped aroundgetCurrentUser— the first DB touch on nearly every page/action.
- The
pgSSL deprecation warning ('prefer'/'require'/'verify-ca'are aliases for'verify-full') comes from the connection string itself. Fix: appendsslmode=verify-fullexplicitly instead of Neon's defaultsslmode=require— documented in.env.example.
npm install
cp .env.example .env.local # fill in real values, see below
npx prisma generate
npx prisma migrate dev --name init
npm run dev| Variable | Where to get it |
|---|---|
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY, CLERK_SECRET_KEY |
Clerk dashboard → API Keys |
CLERK_WEBHOOK_SECRET |
Clerk dashboard → Webhooks → add endpoint https://<your-domain>/api/webhooks/clerk subscribed to user.created, user.updated, user.deleted |
DATABASE_URL, DIRECT_URL |
Neon dashboard (pooled + direct connection strings). Append sslmode=verify-full explicitly (see note above). For local dev without Neon, run docker compose up -d and use postgresql://jobtracker:jobtracker@localhost:5432/jobtracker for both. |
Schema lives in prisma/schema.prisma. After changing it, or after pulling the Resume-linking
or Vault features for the first time: npx prisma migrate dev.
Prisma 7 note: connection URLs no longer live in schema.prisma — the CLI reads them from
prisma.config.ts (uses DIRECT_URL, a non-pooled connection, for migrations), while the app's
runtime client (src/lib/prisma.ts) connects via the @prisma/adapter-pg driver adapter using
DATABASE_URL (Neon's pooled connection string). Make sure both are set in .env.local.
npm run testCurrently covers the pure business-rule helpers in src/lib/stale.ts (auto-follow-up suggestion,
staleness detection). Add integration tests around Server Actions with a test database as the
app grows.
- Push this repo to GitHub.
- Import it in Vercel.
- Add the environment variables above in Project Settings → Environment Variables.
- Set the Clerk webhook endpoint to your production URL once deployed.
- Vercel runs
prisma generateautomatically via thepostinstallscript — confirm it's present inpackage.json:"postinstall": "prisma generate"
- Deploy. Run
npx prisma migrate deployagainst the productionDATABASE_URL(e.g. via a one-off Vercel CLI command or a CI step) before first use — this includes the Vault and Resume-linking tables if you haven't migrated them yet.
- Business rules (auto follow-up suggestions, staleness, duplicate detection) live in
src/lib/andsrc/actions/, kept framework-agnostic so they're easy to test. - Every Prisma query is scoped to the Clerk-authenticated user (
src/lib/auth.ts) — no user ID is ever trusted from the client. - Kanban view updates status without drag-and-drop (a select per card); the Vault's folder
"move" is likewise a picker dialog rather than pixel-level drag-and-drop, both by deliberate
choice (see above). Real drag-and-drop can be added later with
@dnd-kitif desired. - Known sandbox limitation during development: builds were verified with
next build's TypeScript/lint checks, but Prisma Client generation (and therefore a live-DB check) requires network access to Prisma's engine binaries that isn't available in the dev sandbox — always re-verify against your real Neon instance after pulling a change.