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

LumaCV: AI Resume Intelligence

project.lumacv.local
LumaCV: AI Resume Intelligence project screenshot
  1. 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.

  2. 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.

  3. 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."

Results & metrics

  • Reduced resume tailoring turnaround from 30 minutes to under 60 seconds
  • BYOK architecture across 4 AI providers plus 5 system fallbacks for 99.9% uptime
  • Sub-50ms deterministic ATS scoring algorithm eliminating AI hallucination risks
  • 52 pixel-perfect Typst templates generating sub-100KB vector PDFs

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

What I built

Tailor and score

Tailors bullets to a pasted JD, then scores the match with no AI call in under 50ms.

Fact validator

Fact-checks every tailored resume server-side; a bullet with an invented metric reverts.

Native Typst PDFs

The Typst CLI typesets a vector PDF in 20-45ms, from 52 ATS-safe templates.

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

    Why
    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

    Why
    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

    Why
    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

    Why
    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.

Diagram, 12 connections:
  1. User Resume / JD → Client-side PDF/DOCX extraction
  2. Client-side PDF/DOCX extraction → LLM Parser
  3. LLM Parser → JD Analyzer
  4. JD Analyzer → Tailoring Engine
  5. Tailoring Engine → Fact Validator
  6. Fact Validator → Zustand State
  7. Zustand State → Deterministic ATS Scorer
  8. Zustand State → Typst Compiler
  9. Typst Compiler → Vector PDF
  10. Supabase Auth + Postgres ↔ Tailoring Engine
  11. Zustand State ↔ User Resume / JD
  12. Upstash Rate Limiter → Tailoring Engine
Groups: Input: User Resume / JD; Client: Client-side PDF/DOCX extraction; AI: LLM Parser, JD Analyzer, Tailoring Engine, Fact Validator; Infra: Supabase Auth + Postgres, Zustand State, Upstash Rate Limiter; Output: Deterministic ATS Scorer, Typst Compiler, Vector PDF.
  • Next.js 14 App Router + Zustand

    Resume Builder Client

    How

    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

    How

    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

    How

    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 edit 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.

Select a step for its detail

Incidents

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

  • P1

    Export success reported on a failed compile

    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

  • P1

    IDOR on resume and application writes

    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

  • P2

    Vercel build silently missing the Typst binary in production

    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