match
00open source · personal project

Job hunting,automated.

Match scores open roles against your profile, rewrites your resume for each one, researches the company, preps you for the interview and tracks every application, all in one place.

Read the source
job matchillustrative

Senior Frontend Engineer

Northwind Labs

Remote$140,000–$170,000

Five years of React and TypeScript line up with the core stack. Design-system work matches the role's main project. No GraphQL listed.

  • React
  • TypeScript
  • Next.js
  • Design systems
  • GraphQL
A made-up role, laid out the way a real match arrives: the score, the model's reasoning, and which skills you have or lack.
01why this exists

Match does the partsa computer should do,and shows you the rest.

Job hunting is a loop of repetitive work: scrape the boards, judge fit, rewrite the resume for each posting, research the company, prep, chase replies. Match automates the loop and stops wherever a human decision is needed.

02how it works

Five steps,start to offer.

Anything slow runs as an n8n workflow in the background. The app polls Postgres for the result, so a refresh never loses your place.

  1. Sign in

    Email or a social account. No setup wizard, no onboarding call.

  2. Fill in your profile

    Skills, job titles, salary range and work type, plus a base resume as PDF, DOC or DOCX.

  3. Find matches

    Match scores the latest scraped roles against your profile with an LLM and keeps the ones worth your time, each with its reasoning and skill gaps.

  4. Apply with a tailored resume

    One click rewrites your resume for that posting while keeping your layout, then tracks the application from Applied to Offer.

  5. Walk in prepared

    A company research brief and an interview prep guide: role analysis, likely questions, STAR scaffolds, questions to ask.

03what it does

Five tools,one pipeline.

Open a row for the detail.

04engineering

Built for theslow parts.

Each note links to the file it describes.

  1. 01

    Pre-flight diagnostics, not stuck spinners

    POST /api/match/jobs runs an eligibility query before it triggers n8n. When the daily scrape found nothing new, you see “you’re caught up” instead of a two-minute spinner. The button tells four failure modes apart.

    app/api/match/jobs/route.ts
  2. 02

    Polling that tracks the right signal

    The matching poller used to watch only the count of pending matches. If the model rejected every candidate, the count never moved and the UI looked stuck. It now also watches the total and the last match time.

    components/find-matches-button.tsx
  3. 03

    A signed server-to-server callback

    n8n needs your original resume to keep its formatting while tailoring. Resume bytes live in Postgres, not a public bucket, and n8n reads them with a shared x-webhook-secret header, the same secret that signs outbound calls.

    lib/n8n-client.ts
  4. 04

    Third-party HTML stays sandboxed

    Prep guides and research come from n8n and an LLM, and can carry user-influenced text. They render inside an iframe with sandbox="" and a fresh document, so nothing injected can reach the page around it.

    components/prep-html-viewer.tsx
05architecture

One round trip,no websockets.

  1. 01

    Browser

    Next.js 16 · React 19 · Clerk

    You trigger an action: tailor, research, prep.

  2. 02

    API routes

    Next.js · Zod

    Validate, sign the payload, POST it to n8n.

  3. 03

    n8n

    Groq · scrapers · PDF

    The slow work runs off the request cycle.

  4. 04

    Postgres

    Neon · Prisma 7

    n8n writes the result. The source of truth.

  5. 05

    Browser

    polling

    The poller sees the row land and renders it.

frontend
Next.js 16 · React 19 · TypeScript · Tailwind
auth
Clerk
data
PostgreSQL on Neon · Prisma 7
workflows
n8n · Groq
hosting
Vercel
06security

Your resume.Your session.Your data.

  • Identity comes from the session

    lib/auth.ts

    Every API route reads the user ID from the Clerk session on the server. The client never sends it, and routes that touch a record check you own it first.

  • Validated request bodies

    lib/validation.ts

    Every POST body goes through a Zod schema before it reaches Prisma, and string fields are HTML-escaped.

  • Headers on every response

    next.config.mjs

    nosniff, DENY framing, a strict referrer policy, a locked-down permissions policy and a content security policy, set globally.

  • Uploads are checked, not trusted

    app/api/resume/upload/route.ts

    PDF, DOC or DOCX only, 5 MB max, verified by magic bytes rather than the declared type. The filename is sanitised and stored as a column, never used as a path.

Sign in.Drop in a resume.

Scoring a batch of roles takes 30 to 90 seconds.