Skip to main content
All projects
Automation & AI
/projects/lumacv

LumaCV: AI Resume Intelligence

  • 30 min -> <60 sec tailoring
  • BYOK across 4 providers + 5 system fallbacks
  • Deterministic ATS scoring (no AI call, <50ms)
  • 52 Typst templates, 5 export formats
project.lumacv.local
LumaCV: AI Resume Intelligence project screenshot

Problem: Resume tailoring is a manual, error-prone process that often fails ATS screening due to poor keyword alignment, inconsistent formatting, and AI rewrites that quietly invent facts.

Solution: Built a free, open-source resume-building platform that extracts resumes client-side, tailors bullets against a target JD across a multi-provider BYOK AI layer, fact-checks every AI edit against the source before it reaches the client, scores the result with a deterministic (non-AI) ATS algorithm, and typesets it natively with Typst.

Impact: Cut resume tailoring from ~30 minutes to under 60 seconds, with a server-side anti-hallucination validator as the product's core trust guarantee and vector PDFs across 52 ATS-safe templates.

Overview

LumaCV is a free, open-source resume-building SaaS (Next.js 14 App Router, TypeScript, Supabase, Typst). A user pastes or uploads an existing resume, text is extracted entirely client-side (the file never reaches the server), an LLM structures it into typed resume data, the user optionally tailors bullets against a pasted job description, a server-side fact validator strips or reverts anything the AI invented, a deterministic algorithm scores the result against the JD, and the Typst typesetting engine compiles it to a vector PDF. The product's stated design center: "the AI can rephrase, it cannot invent."

Before / after

Resume Tailoring

Before: Manual rewriting + keyword guessing

After: AI-assisted JD alignment with a fact-checked audit trail

Trust

Before: Black-box AI rewrites risk inventing metrics or titles

After: Server-side validator reverts any bullet with an unverifiable metric

Formatting

Before: Inconsistent Word/Docs formatting

After: Deterministic, native Typst-generated vector PDFs

Time to Output

Before: 20-30 minutes

After: <60 seconds

Stack

Next.js 14 App Router with a server/client component split for per-page SEO metadata
Multi-provider LLM orchestration (Gemini, Groq, Mistral, OpenRouter, GitHub Models system fallback; Gemini/OpenAI/Anthropic/Groq BYOK)
Supabase Auth, Postgres + RLS, no ORM (direct PostgREST)
Zustand for persistent client state
Upstash Redis rate limiting, bypassed for BYOK callers
Native Typst CLI compilation via child_process, bundled as a platform binary

Decisions

Key trade-offs and design calls that shaped the final delivery.

Native Typst over LaTeX or HTML-to-PDF

Context: HTML-to-PDF risks rasterized text and floating divs that ATS parsers misread; LaTeX is precise but heavyweight and slow to compile

Decision: Compile via the native Typst CLI (not WASM)—pure vector glyphs, guaranteed top-down reading order, 20-45ms typesetting, at the cost of bundling a platform-specific binary and Node-only (non-Edge) compilation

BYOK plus system fallback, not a single provider

Context: A single AI provider outage blocks every user, and free users need a usable default without configuring anything

Decision: 4 BYOK providers (Gemini, OpenAI, Anthropic, Groq) for users who bring their own key and bypass the shared rate limit, backed by 5 system fallback providers for everyone else

Deterministic ATS scoring, not another AI call

Context: An LLM-scored ATS match is unverifiable and non-reproducible, undermining the product's explainability claim

Decision: Edge-rendered, sub-50ms keyword-match formula with dynamic weight renormalization across only the JD categories actually present, so an empty 'preferred skills' list can't cap a well-matched resume at 80%

Server-side fact validation as a non-negotiable invariant

Context: LLMs can invent metrics, titles, or employers that a candidate never had, and prompt-level instructions alone aren't a reliable guardrail

Decision: Every tailored resume is fact-checked server-side before reaching the client; any bullet containing a metric not traceable to the source resume reverts to the original text entirely, not just the invented number

Architecture

The primary system boundaries, runtime pieces, and how the project was structured in production.

Next.js 14 App Router + Zustand

Resume Builder Client

Multi-step wizard (Upload/Paste + JD → Edit Details → AI Tailoring → Preview + Score) with persistent client state and zero-upload client-side PDF/DOCX text extraction.

lib/llm-client.ts + lib/fact-validator.ts

AI Orchestration + Fact Validation

BYOK across 4 providers plus 5 system fallback providers for resilience; every tailored response is run through a server-side validator that locks employer names, dates, and credentials, and reverts any bullet containing a fabricated metric before it reaches the client.

Native Typst CLI

PDF Compilation

child_process.spawn against a bundled Typst binary—typesetting itself completes in 20-45ms, producing 40-120KB vector PDFs with clean ATS-parseable text, versus rasterized HTML-to-PDF output.

Mermaid source. Paste into mermaid.live to visualize the diagram.

flowchart LR
  subgraph Input
    U[User Resume / JD]
  end
  subgraph Client
    X[Client-side PDF/DOCX extraction]
  end
  subgraph AI
    P[LLM Parser]
    A[JD Analyzer]
    T[Tailoring Engine]
    F[Fact Validator]
  end
  subgraph Infra
    S[(Supabase Auth + Postgres)]
    Z[Zustand State]
    R[Upstash Rate Limiter]
  end
  subgraph Output
    SC[Deterministic ATS Scorer]
    L[Typst Compiler]
    PDF[Vector PDF]
  end
  U --> X --> P --> A --> T
  T --> F --> Z
  Z --> SC
  Z --> L --> PDF
  S <--> T
  Z <--> U
  R --> T

Pipeline

How changes moved from development through validation and deployment.

1

Build

Vercel

Auto-deploy from GitHub; postinstall re-downloads/caches the native Typst binary each build

2

Rate Limiting

Upstash Redis

Sliding-window rate limiting on AI endpoints, skipped entirely for BYOK callers

3

Deploy

Vercel (Node + Edge)

maxDuration: 60 for AI/tailor routes on Node serverless; Edge runtime for the deterministic ATS score and stats endpoints

Incidents

Operational failures, rehearsals, or recovery moments that changed how the system was run.

Export success reported on a failed compile

P1

Resolution: Traced through 4 consecutive point releases (2.15.1→2.16.3): removed a fake-PDF fallback string, rebuilt Word export as real OOXML, swept ~60 unguarded .trim() crashes with a safeTrim() helper, and fixed a Zod validation error being handed to the toast layer as a raw object

Lesson: A failed export must fail loudly—never synthesize a fallback file to paper over a real error, and re-examine a 'fixed' bug rather than assuming the first patch found the root cause

IDOR on resume and application writes

P1

Resolution: Added explicit user_id ownership checks before upsert on any client-supplied resource id

Lesson: Never trust a client-supplied id for ownership—verify server-side on every write, even for authenticated users

Vercel build silently missing the Typst binary in production

P2

Resolution: Bundled bin/ and typst/ into every API route via outputFileTracingIncludes, since local dev reads the filesystem directly and gives zero signal that production bundling is broken

Lesson: A dev-only code path that happens to work by accident hides real production bundling regressions—verify against the actual deploy artifact, not just local behavior