CV Builder: write your CV once, export it ten ways
An app I built to stop rewriting the same CV in Word. Type your career in once, switch between ten layouts, and send anyone a read-only link to it.
Every time I applied for something, I reformatted the same fourteen years into somebody else’s idea of a layout. Different fonts, different section order, the same facts. The third time I did it in Word I stopped and wrote a tool instead.
It’s a React app on Vite, Tailwind for styling, Firestore for storage, and it does one thing: keep the content and the presentation apart, so changing the template doesn’t mean retyping the career.
The content model
A CV is six sections — profile, employment, education, skills, awards, and one for the extra-curricular material Google asks for. Each is a list of entries, each entry has rich text in it. Not markdown, because nobody wants to learn markdown to bold a job title, and not a plain textarea, because a CV needs bold and bullets and nothing else.
TipTap turned out to be right for that. It’s a headless editor, so the toolbar is mine and the schema is restricted to exactly what a CV needs — bold, italic, underline, lists, alignment. There is no way to paste in a heading or a table, which is the point. Every constraint I removed from the editor is a way the template can break later.
Templates are pure presentation. Ten of them, plus an internal one, and switching between them re-renders the same document object. That is the whole reason the thing exists.
Sharing, and where the boundary sits
The interesting part is the share link. You press Share, and you get a URL like ?share=<id> that anybody can open, with no login, and it renders your CV read-only.
There’s a version of this that is done badly: keep one copy of the document, add an isPublic flag, and check the flag in your JavaScript. It works right up until someone opens the network tab.
private resumes are scoped to a user ID, while sharing copies a snapshot into a separate public path with its own rule
Two collections. The private one is scoped to your user ID and no rule can reach it from outside. The public one is genuinely world-readable, and I’m fine with that, because the only documents in it are ones somebody pressed a button to put there.
The nice second-order effect: editing your CV doesn’t change what’s already been shared. The snapshot is frozen until you publish again. If you sent a link to a recruiter on Monday and rewrote your profile on Tuesday, Monday’s link still shows Monday’s CV. That wasn’t the plan, it’s just what falls out of copying instead of flagging, and it’s the behaviour you want.
Printing is the hard part
Everyone underestimates this. A CV is a print document that happens to be viewed on screen, and browsers disagree about almost everything that matters: page breaks inside a block, orphaned headings, background colours that vanish, margins that differ by a few millimetres between Chrome and Safari.
There’s a strict mode that forces every section to stay whole and accepts more whitespace, and a compact mode that lets sections break across pages to fit two pages instead of three. Making that a user-facing toggle rather than trying to be clever automatically was the right call. The person exporting knows whether they’d rather have a tidy three pages or a dense two.
Where the model helps, and where it doesn’t
There’s a Gemini call in there — gemini-3.1-pro-preview, through Firebase AI Logic, same client-side pattern as this site’s assistant. It’s for rewriting a bullet you’re not happy with, not for writing the CV.
That distinction matters more than it sounds. A model that generates your employment history invents things. A model that takes “led the migration, went well” and offers three tighter phrasings is doing editing, and editing is a job it’s actually good at. The facts stay yours; only the sentence changes.
One thing I’d fix
The internal Google template is gated on a hard-coded email address compared client-side. That’s a UI convenience, not a control — anyone can read it in the bundle and flip it in the console. It doesn’t guard any data, and the rules above are what actually protect the documents, so the worst case is a stranger sees an empty template they have no content for. Still, it’s the kind of line that looks like security to a reader skimming the file, and I’d rather it were a Firestore-backed entitlement or nothing at all.
Live at cv-builder-25b27.firebaseapp.com.