A developer-focused CV builder. Write your CV like a README, export it as a PDF.
# Your Name
Software Engineer
you@example.com Β· github.com/you Β· City, Country
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
## Experience
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
ββ 2022βpresent
β Senior Engineer @ Acme Corp
β
β Led distributed caching project with Go and Redis.
β Owned backend infrastructure team.
β
ββ 2019β2022
Engineer @ Beta Ltd
Full-stack development with React and Node.js.
- Live preview β dark monospace aesthetic, styled like a GitHub README
- ASCII timelines β vertical tree (
ββ / ββ) or horizontal for experience & education - JSON render mode β render the whole CV or individual sections as a syntax-highlighted
.jsonfile - Per-section color overrides β collapsible Colors panel per section, each token resettable to global
- Semantic color tokens β heading, subtitle, period, role, company, category, project title β separate from base palette
- Style presets β GitHub Dark, GitHub Light, Dracula, Nord; fully customisable
- Photo β ASCII art β upload a JPG/PNG/WebP, get ASCII art in the header or a dedicated section
- Drag-and-drop section reordering
- Section types β experience, education, skills, projects, photo, personal, custom
- Projects section β parses
Stack: item1, item2into a structured array in JSON mode - EuroPass import β drop a EuroPass SkillsPassport XML or PDF; all sections, photo, and links extracted automatically
- Dynamic links β
meta.links[]replaces the old GitHub / LinkedIn / Website fields; unlimited labelled links in the header - Mobile-responsive β bottom tab navigation (Edit / Meta / Preview) on small screens
- Scaled preview β preview zooms to fit any screen width while preserving the exact PDF layout
- PDF export β prints with correct per-page margins, preserving background color
- No account, no backend β state persisted to
localStorage
| Package | Purpose |
|---|---|
| Next.js 16 | Framework + API routes |
| React 19 | UI |
| Zustand 5 | Persisted state |
| dnd-kit | Drag-and-drop section reordering |
| Sharp | Server-side image processing for ASCII art |
| Tailwind CSS v4 | Editor UI styling |
| @vercel/analytics | Usage analytics |
PDF export uses the browser's native print API (window.print()). No Puppeteer required.
Requirements: Node.js 18+, npm
git clone https://github.com/sudokku/cvgen.git
cd cvgen
npm install
npm run devOpen http://localhost:3000.
The style panel includes Monaspace Neon, Fira Code, JetBrains Mono, and Cascadia Code. These must be installed on your system to render.
# macOS (Homebrew)
brew install font-monaspace font-fira-code font-jetbrains-mono font-cascadia-codeThe app has three panels on desktop, and a tabbed layout on mobile.
| Area | Purpose |
|---|---|
| Left sidebar β Sections tab | Add, delete, reorder sections via drag-and-drop |
| Left sidebar β Meta tab | Name, title, email, phone, location, links, profile photo |
| Left sidebar β Style tab | Preset, font, font size, base colors, semantic colors, JSON colors, render mode |
| Left sidebar β Import tab | EuroPass XML or PDF import |
| Center β Editor | Edit the selected section's content, layout, render mode, and per-section color overrides |
| Right β Preview | Live CV preview, Export PDF button |
Set the section layout to vertical or horizontal in the Editor panel.
### Role @ Company | Period
Description of what you did.
Second line of description.
### Another Role @ Company | Period
More description here.
Each entry renders as an ASCII tree in the preview:
ββ 2022βpresent
β Senior Engineer @ Acme Corp
β
β Led distributed caching project.
β
ββ 2019β2022
Engineer @ Beta Ltd
Full-stack development.
Set layout to list to render as plain markdown instead.
Languages: TypeScript Β· Go Β· Python Β· Rust
Frameworks: Next.js Β· React Β· Gin Β· FastAPI
Infra: Docker Β· Kubernetes Β· AWS Β· Terraform
Category labels (Languages:, Frameworks:, etc.) are coloured with the category semantic token.
In JSON render mode, each category becomes a key with an array of values.
A personal section type renders key-value pairs (date of birth, nationality, gender, etc.).
It is automatically created when importing a EuroPass document that includes demographics.
Date of birth: 15 Jun 1990
Nationality: German
Gender: Male
### Project Name
Brief description of what you built. Stack: TypeScript, React, Node.js
In JSON render mode, Stack: is parsed out and rendered as a separate array:
{
"name": "Project Name",
"description": "Brief description of what you built.",
"stack": ["TypeScript", "React", "Node.js"]
}Plain content sections support **bold** and `code` inline formatting.
### headings, ## headings, and # headings are also rendered with appropriate weight.
Set document-wide in the Style tab:
| Mode | Behaviour |
|---|---|
md |
All sections rendered as markdown-style plain text |
json |
Entire CV rendered as a syntax-highlighted JSON document |
per-section |
Each section independently toggles between md and json in the Editor |
Drop a EuroPass SkillsPassport file onto the Import tab (desktop sidebar) or the Import sub-tab under Meta (mobile).
| Format | How it works |
|---|---|
.xml |
Parsed entirely in the browser β no server round-trip |
.pdf |
Sent to /api/import (Node.js route); embedded XML is extracted from the PDF binary and returned to the client, then parsed the same way as a plain XML upload |
What is imported:
| EuroPass field | Maps to |
|---|---|
| Name, headline, email, phone, address | meta fields |
| WebsiteList | meta.links[] β labelled links (GitHub, LinkedIn, etc. auto-detected) |
| Photo (base64) | meta.photoUrl, displayed in the CV header |
| ProfileSummary | custom section β "Summary" |
| Demographics (birthdate, nationality, gender) | personal section β "Personal Information" |
| WorkExperienceList | experience section, vertical timeline layout |
| EducationList | education section, vertical timeline layout |
| Languages + ComputerSkills | skills section |
| Communication / Organisational / JobRelated skills | custom section β "Soft Skills" |
| AchievementList | one section per achievement code; projects type if code is "Projects", otherwise custom |
Your current theme and color settings are preserved on import.
Imported HTML in description fields (lists, bold, italic) is converted to Markdown.
meta.links[] is an array of { label: string; url: string } objects. They appear in the CV header after the email address.
The Meta tab has a dynamic links editor: click + add to add a new entry, edit the label and URL inline, and click Γ to remove.
CVs created before this feature was added may have the old github, linkedin, and website fields. The Meta tab detects these and shows a one-click Migrate to links list button.
Four built-in presets (GitHub Dark, GitHub Light, Dracula, Nord). Selecting a preset sets all color tokens at once.
background, text, muted, accent / link, border, code bg
Applied to specific content elements in both md and json render modes:
| Token | Applied to |
|---|---|
heading |
## Section Title headings |
subtitle |
Section subtitle line |
period |
Timeline period tokens (e.g. 2022βpresent) |
role / degree |
Job titles, degree names |
company |
Company and institution names |
category |
Skills category labels |
project title |
Project names |
key, string, number, punctuation β only shown when render mode includes JSON.
Each section has a collapsible Colors panel in the Editor. Any of the semantic tokens plus text, muted, and accent can be overridden per section. Overridden labels appear in blue; click Γ to reset an individual token or reset all to global to clear all overrides.
- Upload a JPG/PNG/WebP in the Meta tab β appears top-right of the CV header
- Or add a Photo section for a full-width ASCII block
- Use the width/height sliders to control ASCII art dimensions
- The aspect ratio is preserved automatically
- Toggle between
asciiandimagerender mode per photo
Two export options are available from the preview panel:
Click Quick Print. The browser print dialog opens with:
@page { margin: 0 }β no browser-added headers or footersbox-decoration-break: cloneβ the CV's padding is repeated at every page break, giving consistent gutters on multipage CVs- Background color is preserved via
-webkit-print-color-adjust: exact
Click Export PDF. This sends the CV to the server, which generates the PDF server-side and embeds rich metadata:
- CV JSON is
POSTed to/api/pdf - The route caches the CV and launches a headless Puppeteer browser
- Puppeteer navigates to the internal
/printpage (not indexed by robots), which renders the full CV with JSON-LD and Open Graph metadata tags - Puppeteer captures an A4 PDF with background colors preserved
pdf-libpost-processes the PDF:- Sets standard PDF document info (title, author, subject, keywords)
- Injects an XMP metadata stream with Dublin Core + a custom
cvgen:namespace - Attaches
cv-metadata.jsonas an embedded file β a schema.org/Person JSON-LD document mapping experience βhasOccupation, education βalumniOf, skills βknowsAbout
- The enriched PDF is returned as a file download
The exported PDF is machine-readable: any tool that can extract PDF attachments or XMP will find structured CV data inside it.
Requirements: puppeteer must be installed (npm install puppeteer). If it is not available the route returns a 500 with a hint to fall back to Quick Print.
npm test
# or
npx vitest runTests use Vitest + jsdom. Config is in vitest.config.ts; test files live under src/__tests__/.
MIT