Track every application, follow up on time, and see what's actually working. Free, forever.
π Live: jobapplytracker.com
The job-search loop is messy: spreadsheets that started clean and ended unreadable, follow-ups that slipped, no idea which channels were actually converting. JobTrack replaces the spreadsheet with a calm, opinionated tracker that knows about real-world job-hunt mechanics β kanban progress, follow-up reminders that actually email you, drag-paste a job URL to autofill the form, import from CSV, share a stripped-down stats profile, and a per-application activity log so you can see what moved when.
It's a full-stack TypeScript app built on Next.js 16 (App Router, RSC), Supabase (Postgres + Auth + Storage), Resend for outbound mail, and Vercel for hosting + cron.
List view β pipeline health strip, quick filter chips, sortable list.
Kanban β drag applications across stages, sticky horizontal scroll.
Detail β pipeline progress, real activity timeline, follow-up reminder card, details sidebar.
Analytics β funnel, source performance, conversational insights.
Dark-mode variants are bundled at
public/*_dark.pngand used automatically when the visitor's system or theme preference is dark.
- Paste a job URL β autofill. Server-side scraper (
/api/jobs/parse) tries schema.orgJobPostingJSON-LD first (covers Greenhouse, Lever, Workable, Ashby, SmartRecruiters), then LinkedIn-specific DOM hooks, then OpenGraph, then page title heuristics. SSRF-safe (blocks private hosts, 12s timeout, auth-gated). Pulls company, role, location, JD content, work type, source, and salary range when present. - Quick add bar. "Company β Role" inline form at the top of the list β 30-second new entry without leaving the page.
- CSV import. papaparse-based ingestion with header auto-detection (EN+TR synonym table), status/work-type/date aliasing (
phone screenβhr_interview,DD/MM/YYYYβ ISO), per-row validation with skip-and-continue on errors, override-able column mapping in the preview step. Sample CSV download included. - Resume attachments. Per-application PDF upload (β€2 MB) to a private Supabase Storage bucket with per-user RLS.
- 8-stage pipeline. Applied β Test Case β HR Interview β Technical Interview β Management Interview β Offer β Accepted / Rejected. Status changes log to the activity feed.
- Drag-and-drop kanban. Edge-scroll while dragging, always-visible scrollbar so the off-screen columns are obvious.
- List view. Pinned-first, recent-first sort, inline status pills, follow-up badges (Overdue Nd / Follow up today / Follow up in Nd).
- Pipeline health strip. Active count Β· This week Β· Response rate % Β· either Follow-ups due (red when > 0) or Best converting source.
- Quick filter chips. Active / This week / Stale / Follow-up due / All / Closed, each with a live count.
- Cmd+K command palette. Jump to any application by name, or to Applications / Analytics / Settings / New Application.
- Follow-up reminders. Pick a date (preset chips or calendar). When the date arrives the daily cron at
08:00 UTC(/api/cron/follow-ups) emails the user via Resend, grouped per user (one email per inbox per day, no spam). Idempotent viafollow_up_sent_atstamping. Snooze (+1d / +3d / +1w), Mark done, Reschedule, Clear β all inline in the detail sidebar. - Activity event log. Real
activity_eventstable; the timeline shows every status change, note, resume upload, follow-up set/done, pin/unpin, and creation event with payload-aware labels. - Quick notes. Add an inline note from the detail page without opening the full edit form.
- Funnel by stage, source performance bars, average-response-time, conversational insights (
"Your response rate is 100% β that's strong. Whatever you're doing, keep doing it."). - Per-user rendering β every figure is RLS-scoped.
- Public profile at
/u/[handle]. Opt-in, picks a unique handle (case-insensitive, 2β32 chars, reserved-name list). Shows applications count, response rate, offers, weekly streak, funnel %, top sources. Optional company highlights (off by default β good for stealth job hunts). Dynamic OG image at/u/[handle]/ogso shared links preview with the user's actual stats. Sitemap auto-discovers opted-in handles.
- Theme toggle (light / dark / system) β works for logged-out visitors on the landing page too.
- i18n β English + TΓΌrkΓ§e across landing, login, settings, status labels. Inline language switcher in the landing header for logged-out visitors.
- Custom sources & industries β bend the source/industry pickers to your search style.
- Hide rejected toggle to focus the active pipeline.
- Follow-up email opt-in toggle (synced to
user_settings.follow_up_emails, respected by the cron). - Public profile card with handle picker, live availability check, display name, show-companies toggle, copy link.
- Export to JSON β full dump of your applications + settings.
- Clear all data β wipes apps, activity, resumes, settings on the server. Sign-in preserved.
- Delete account β type-DELETE-to-confirm; cascades via FK deletes on
auth.users. Resume files removed from storage. Signs out + redirects home.
- Email + password, Google OAuth, GitHub OAuth, passwordless magic links β all via Supabase Auth.
- Password-reset flow, password strength meter on signup.
- Middleware-enforced route protection.
- PWA installable. Dynamic manifest + apple-touch-icon, SVG favicon multi-size ICO fallback.
- Privacy + Terms pages at
/privacyand/termswith sharedJtLegalShelland readable typography. - Sitemap + robots.txt auto-generated, public profiles included.
- Dynamic OG image at
/opengraph-imagefor the home page, separate per-handle generator for public profiles. - Toast feedback (sonner) on every mutation.
- Skeleton loaders for application list and detail.
- Mobile bottom nav, in-app new-app form sticky save bar on mobile, inline footer on desktop.
| Tool | Purpose |
|---|---|
| Next.js 16 | App Router, RSC, route handlers, dynamic OG via ImageResponse |
| React 19 | UI |
| TypeScript 5 | Strict types end-to-end |
| Tailwind CSS 4 | Utility styling |
| shadcn/ui | Accessible primitives (Dialog, Popover, Command, Dropdown, etc.) |
| Radix UI | Underlying headless primitives |
| Zustand | Client store with optimistic mutations |
| React Hook Form | Form state |
| date-fns | Date math + formatting |
| Lucide React | Icon set |
| Sonner | Toasts |
| cmdk | Command palette |
| next-intl | EN / TR localization |
| next-themes | Theme switching |
| Tool | Purpose |
|---|---|
| Supabase | Postgres + Auth + Storage + RLS |
| Resend | Outbound transactional email (follow-up reminders) |
| Vercel | Hosting + cron (vercel.json daily schedule) |
| cheerio | HTML parsing for the URL-paste autofill |
| papaparse | CSV parser for import flow |
| sharp | Build-time favicon ICO generation from the SVG brand mark |
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Browser (RSC + Client) β
β Landing β’ Login β’ /applications β’ /analytics β’ /u/[handle] β
βββββββββββββββ¬βββββββββββββββββββββββββββββββββββββββββββββββββ
β
ββββββββββββΌβββββββββββββββββββββββββββββββββββββββββββββββ
β β β
βΌ βΌ βΌ
ββββββββββ ββββββββββββββββββββββββ ββββββββββββββββββββββββββββ
β Server β β /api/jobs/parse β β /api/cron/follow-ups β
β Actionsβ β (cheerio scraper, β β (Bearer-auth, service β
β β β auth-gated) β β role, daily 08:00 UTC) β
ββββββββββ ββββββββββββββββββββββββ βββββββββββββββ¬βββββββββββββ
β β β
β β βΌ
β β βββββββββββββββββββ
β β β Resend β
β β β (verified domain)β
β β βββββββββββββββββββ
βΌ βΌ
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Supabase β
β Auth Β· Postgres (RLS) Β· Storage (resumes/) β
β Tables: applications Β· activity_events Β· user_settings β
β skill_suggestions β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
auth.users
β² β² β²
β β β ON DELETE CASCADE
β β β
β β ββββ user_settings
β β Β· hide_rejected, custom_sources, custom_industries
β β Β· follow_up_emails
β β Β· public_handle (unique), public_enabled,
β β public_show_companies, public_display_name
β β
β ββββ applications
β Β· status (enum), work_type (enum), is_pinned
β Β· follow_up_date, follow_up_sent_at,
β follow_up_completed_at
β Β· contacts (jsonb), skills (text[])
β β²
β β ON DELETE CASCADE
β β
β ββββ activity_events (append-only)
β Β· kind, payload (jsonb)
β
ββββ storage.objects in resumes/{user_id}/{app_id}/resume.pdf
RLS is enforced on every user-owned table β direct queries are scoped by auth.uid(). The cron worker uses the service-role key only inside the Bearer-gated route, never on the client.
- Node 18+
- A Supabase project (free tier is fine)
- A Resend account if you want follow-up emails to actually send locally
git clone https://github.com/berkinduz/job-apply-tracker.git
cd job-apply-tracker
npm install
cp .env.example .env.local # then fill in the values below# Supabase β both client and server
NEXT_PUBLIC_SUPABASE_URL=https://xxxx.supabase.co
NEXT_PUBLIC_SUPABASE_ANON_KEY=eyJ...
# Server-only (never expose to the browser)
SUPABASE_SERVICE_ROLE_KEY=eyJ...
# Public URL β used in OG, sitemap, email links
NEXT_PUBLIC_SITE_URL=http://localhost:3000
# Follow-up email cron
CRON_SECRET=long-random-string # openssl rand -hex 32
RESEND_API_KEY=re_xxx # optional locally
EMAIL_FROM=JobTrack <reminders@example.com> # optional; default is set in codeApply the SQL files in supabase-*.sql in order via the Supabase SQL editor, or rebuild from the migrations history in src/types/database.ts. Key tables: applications, user_settings, activity_events, skill_suggestions.
Don't forget to:
- Enable Row Level Security on every user table (each has its own policy).
- Create the private
resumesstorage bucket with per-folder RLS ((storage.foldername(name))[1] = auth.uid()::text). - Turn on "Leaked password protection" under Auth β Policies.
- Connect a custom SMTP (or use Supabase's default for testing β rate-limited to 3 mails/hour).
npm run dev
# Landing β http://localhost:3000
# App β http://localhost:3000/applicationscurl -i -H "Authorization: Bearer $CRON_SECRET" \
http://localhost:3000/api/cron/follow-upsWithout RESEND_API_KEY, the route returns { ok: true, sent: 0, errors: [...] } with a reason-skipped notice β useful while you wire Resend up.
src/
βββ app/
β βββ api/
β β βββ cron/follow-ups/route.ts # daily reminder worker
β β βββ jobs/parse/route.ts # URL paste autofill
β βββ applications/ # list, new, detail, edit (RSC)
β βββ analytics/ # analytics page
β βββ auth/ # OAuth callback
β βββ login/ # email/OAuth/magic link
β βββ onboarding/ # 3-step welcome flow
β βββ settings/ # account, theme, customization, danger zone
β β βββ danger-actions.ts # clearAllData + deleteAccount
β β βββ public-profile-actions.ts # handle pick + save profile
β βββ u/[handle]/ # public profile + dynamic OG
β βββ privacy/ terms/ # legal pages
β βββ icon.svg apple-icon.tsx # favicon assets
β βββ manifest.ts robots.ts sitemap.ts
β βββ opengraph-image.tsx # landing OG generator
βββ components/
β βββ jt/ # "Design v2" components β app shell,
β β βββ app-shell.tsx # header + nav + Cmd+K palette
β β βββ application-form.tsx # new/edit form with URL paste autofill
β β βββ application-detail.tsx # detail page
β β βββ application-detail-extras.tsx # pipeline, timeline, follow-up card
β β βββ applications-page.tsx # list + kanban + filters
β β βββ csv-import-dialog.tsx # CSV import wizard
β β βββ landing.tsx # marketing homepage
β β βββ login.tsx # auth screen
β β βββ onboarding.tsx # 3-step welcome
β β βββ public-profile.tsx # /u/[handle] renderer
β β βββ public-profile-card.tsx # settings card
β β βββ primitives.tsx # JtButton, JtPill, JtDot, status tokens
β β βββ settings.tsx # the settings page
β βββ applications/ # list/card/kanban (data-bound)
β βββ ui/ # shadcn primitives
βββ lib/
β βββ csv/import.ts # papaparse + auto-detect + alias map
β βββ email/send.ts # Resend wrapper + email shell
β βββ jobs/parse.ts # cheerio JD scraper
β βββ public-profile/stats.ts # funnel aggregation
β βββ supabase/ # browser, server, admin (service-role)
β βββ applications.ts # CRUD + bulk + follow-up
β βββ activity.ts # event log writer + reader
βββ messages/{en,tr}.json # i18n
βββ store/index.ts # Zustand store (applications + settings)
βββ i18n/request.ts # next-intl setup
βββ types/ # JobApplication, ActivityEvent, Database
The app deploys cleanly to Vercel β push to main, Vercel rebuilds, Cron picks up vercel.json automatically.
-
NEXT_PUBLIC_SUPABASE_URL,NEXT_PUBLIC_SUPABASE_ANON_KEYset in Vercel -
SUPABASE_SERVICE_ROLE_KEYset (marked Sensitive) -
NEXT_PUBLIC_SITE_URL=https://yourdomain.com -
CRON_SECRETset (marked Sensitive) β a freshopenssl rand -hex 32 -
RESEND_API_KEYset (marked Sensitive) and the sending domain verified (SPF + DKIM) -
EMAIL_FROM=JobTrack <reminders@yourdomain.com> - Storage bucket
resumesexists, private, with the per-user RLS policy -
auth.usersleaked-password protection enabled in Supabase dashboard - Vercel Cron lists
/api/cron/follow-upsat the daily schedule
The HANDOFF doc (HANDOFF.md) walks through the rest of the manual setup if you're forking this and going to production with it.
Built, not yet built:
- Email forwarding β
you@inbox.jobtrack.appβ forward a job listing email, auto-create application. Resend inbound webhook + HTML extractor. - Browser extension β "Track in JobTrack" button on LinkedIn / Indeed job pages. Manifest v3 + popup auth flow.
- AI features (gated behind a future paid tier) β JD summarizer, cover-letter draft from JD + resume, interview prep prompts.
- Notification preferences β per-kind opt-in (only today, only overdue, weekly digest).
- Bulk actions β multi-select on the list view for bulk status change / bulk archive.
MIT β see LICENSE.
Berkin Duz
- Website β berkinduz.com
- GitHub β @berkinduz
- β Buy me a coffee
If JobTrack helps you land your next role, a β on the repo is the nicest way to say thanks.