DEV Community

Haru Jeong
Haru Jeong

Posted on

I Built a Free CV Builder with Zero Backend — Here's How

Every "free" CV builder I tried had a catch: a paywall, a watermark, or forced account creation. So I built CVFREE — a completely free, open-source CV builder that runs entirely in your browser.

No signup. No backend. No tracking. No watermark.

CVFREE

What it does

  • 10 professional templates — clean, ATS-friendly designs
  • 5 languages — English, Korean, Japanese, Chinese (Simplified), Spanish
  • Real-time preview — WYSIWYG editing
  • PDF export — zero branding, clean output
  • Privacy-first — all data stays in localStorage/IndexedDB

The zero-backend architecture

The entire app is a static site deployed on Cloudflare Pages. Here's how I handled the common "you need a backend for that" problems:

Persistence → localStorage + IndexedDB

No user accounts means no server-side storage. I use localStorage for the active document metadata and IndexedDB for storing multiple CV drafts. This gives users the "save and come back" experience without ever touching a server.

// Simplified: save current document to IndexedDB
async function saveDocument(doc: CVDocument): Promise<void> {
  const db = await openDB('cvfree', 1, {
    upgrade(db) {
      db.createObjectStore('documents', { keyPath: 'id' });
    },
  });
  await db.put('documents', doc);
}
Enter fullscreen mode Exit fullscreen mode

PDF Export → Client-side rendering

Instead of using a server-side headless browser for PDF generation, I render templates as React components and use the browser's native print API (window.print()) with carefully crafted @media print CSS. This produces pixel-perfect PDFs with zero server cost.

Template System → Component-driven

Each template is a React component implementing a shared interface:

interface TemplateProps {
  data: CVData;
  theme: ThemeConfig;
}

interface TemplateMeta {
  id: string;
  name: string;
  component: React.FC<TemplateProps>;
  thumbnail: string;
}
Enter fullscreen mode Exit fullscreen mode

Adding a new template is literally one file: create the component, register it in the template registry, done.

Internationalization → next-intl

Full i18n with locale-prefixed routing (/en, /ko, /ja, etc.). The CV content itself is also localized — section headers, date formats, and suggested text adapt to the selected language.

Tech Stack

Layer Choice Why
Framework Next.js 14 (App Router) File-based routing, RSC
Styling Tailwind CSS Utility-first, fast iteration
State Zustand Lightweight, no boilerplate
i18n next-intl Clean API, SSR-compatible
Hosting Cloudflare Pages Free tier, global CDN
Analytics None Privacy-first by design

What I learned

  1. Print CSS is powerful — You can achieve professional PDF output with just @media print, page-break-* properties, and precise mm units. No headless browser needed.

  2. IndexedDB > localStorage for documents — localStorage has a 5MB limit and synchronous API. IndexedDB handles large documents asynchronously and supports much larger storage.

  3. Zero backend = zero operating cost — The entire project runs on Cloudflare's free tier. No database, no API server, no maintenance overhead.

  4. Templates as pure components — Separating data schema from presentation makes adding templates trivial. The hardest part is designing good-looking templates, not wiring them up.

Try it

Visit cvfree.pages.dev — no account needed. Just start typing.

I'd love feedback on the template designs, PDF quality, or any languages you'd want added!

Top comments (0)