
Problem
Job hunting is chaotic. Most people track applications in a spreadsheet that goes stale fast — no idea which stage each application is at, nothing to prep with before an interview, and no signal when a follow-up window quietly closes.
JobTrackr set out to replace the spreadsheet with a single, focused tool that stays up to date because updating it is the path of least resistance.
Approach
Every application lives on a Kanban board — Saved → Applied → Interview → Offer → Rejected — so status is always a drag, not a data-entry chore. The part that usually kills these tools is friction at the top of the funnel: manually retyping company, role, salary, and tags for every posting. JobTrackr removes that by parsing the posting itself, either pasted as text or fetched from its URL, entirely client-side with zero AI API calls.


Architecture
- Parser (
lib/parser.ts): regex heuristics plus a curated, alias-aware skills dictionary extract company, role, salary, location, and tech tags from pasted text — instant, no network round-trip. - URL extraction (
lib/extract/): a server route fetches the posting and works down three confidence levels —JobPostingJSON-LD, then Open Graph metadata, then a scoped read of the posting's own container (never the whole page, so a "similar jobs" sidebar can't leak into the result). A field left ambiguous is left blank rather than guessed. - Data (Supabase / PostgreSQL): jobs, profiles, and learned tags, with Row-Level Security enforcing
auth.uid() = user_idon every table — not just at the API layer. - Auth (Supabase Auth): email + Google OAuth, session held in httpOnly/Secure cookies via
@supabase/ssr— no tokens in localStorage. - UI (Next.js 16 + React 19 + shadcn/ui): the board itself uses
@dnd-kit, with the dragged card lifted into an overlay layer so it tracks the cursor and isn't clipped crossing columns; a long-press starts the drag on touch so the board still scrolls normally.
Implementation

The URL path is the harder half. Fetching is capped by byte size and timeout, redirects are re-validated, and the target is rejected outright if it isn't https:, carries credentials in the URL, or resolves to a private/internal address — a server-side fetch is effectively a user-controlled outbound request, so it gets treated like one. Auth-walled boards (Indeed, MyFutureJobs) fall back to manual entry with the link preserved rather than failing silently.
Interview prep and stale-application reminders both lean on the extracted tags rather than another round of AI calls: lib/interview-questions.ts is a curated local bank matched to a job's tech tags plus universal behavioral questions, and lib/reminders.ts flags anything sitting in Applied for 7+ days with no client-side cron involved — it just checks on load.


Challenges
Client-side scraping heuristics break in specific, recurring ways — a sidebar's "similar jobs" salary bleeding into the real posting, a board with no JSON-LD at all, a title field that's actually the company name on some ATS. Each of the 190 tests across the parser and extraction pipeline pins one of these as a regression, so the test suite doubles as a running log of how job boards actually misbehave in the wild, rather than a spec written in advance.
Outcome
JobTrackr ships as a complete pipeline: paste-or-link extraction, a drag-and-drop board, weekly stat tiles, interview prep, and stale-application flags — deployed on Vercel, backed by Supabase, with self-service data export and account deletion for users who want out.
Lessons Learned
Removing AI calls from the extraction path wasn't a cost-cutting compromise — it made the tool faster and more predictable, and it forced the parsing logic to be legible enough to write regression tests against. The security work (URL guarding, RLS, no tokens in localStorage) mattered as much as the features: a job tracker holds someone's entire search history, salary expectations, and rejection reasons, so treating it casually wasn't an option.