Most resume builders make you sign up before you've even decided if the tool is any good. I wanted the opposite: open a page, start typing, and watch a CV take shape in real time — no account, no paywall.
That became CV Builder — and it's gone through two clear phases.
Phase 1: A dumb-simple live preview
The whole app is built around one function. Every keystroke — name, summary, a bullet point — fires the same render call, which rebuilds the preview from scratch based on whatever's in the form:
function renderCV() {
const name = document.getElementById('f-name').value.trim() || 'Your Name';
// ...read every field...
document.getElementById('cv-page').innerHTML = `
<div class="cv-header">
<div class="cv-name">${esc(name)}</div>
...
</div>
`;
}
No diffing, no state library — just "read the form, redraw the page." It sounds too simple, but that's exactly what makes it feel instant.
Two other decisions mattered:
- Repeatable sections — Experience and Education are dynamically generated blocks you can add/remove, each wired to the same render call.
- One clean template — no tables, no icons, no columns. Just a layout an ATS parser and a human recruiter can both read.
PDF export skips a PDF library entirely — the "Download" button just calls window.print(), with print-only CSS that hides the form and leaves only the CV page. Zero dependencies, and the output always matches the screen exactly.
Phase 2: The obvious problem
Refresh the page, and everything's gone. For a tool you might spend twenty minutes tuning, that's a real cost — and it meant you could only ever work on one CV, in one sitting.
Adding persistence without standing up a backend
Instead of spinning up a server + database + auth, I used a built-in per-user key-value storage layer available directly in the browser context this app runs in. From the app's perspective, it behaves like a small backend: data survives a refresh, survives closing the tab, scoped privately per user.
async function performSave() {
const data = collectFormData();
await window.storage.set('cv-builder:' + currentId, JSON.stringify(data), false);
// update an index of {id, name, updatedAt} so saved CVs are listable
}
What this unlocked:
- Auto-save — once a CV's been saved once, edits save in the background on a debounce, with a status dot + timestamp confirming it.
- Multiple saved CVs — a dropdown lists everything you've saved, most recent first, so you can keep a different version tailored per role.
- Clean delete — removes both the stored data and its index entry, with a confirm step.
The honest limitations
- It's personal storage, not multi-tenant — no login system separating one person's data from another's.
- It's tied to the environment it runs in — porting this to a fully independent, self-hosted site would mean swapping this for a real database.
- No version history — saving overwrites the previous draft.
None of these are hard walls, just the next milestones — real auth + Postgres + a small REST API is the natural v3 if this ever needs to serve more than one person's desk.
The takeaway
Solve the problem in front of you with the smallest tool that does it honestly. A live-preview editor didn't need a framework. A save feature didn't need a server yet. Build the version that's true to what you actually need right now.
I f interested you can have a look and give a feedback on what's need to added git@github.com:emilioochieng/CV_BUILDER.git
Top comments (0)