Building ResuPals - A Privacy-First Resume Builder
How I built a fully client-side resume and cover letter builder with friendly mascot characters, focusing on privacy, ATS compliance, and user experience.
When I decided to build a resume builder, the market was already crowded. Dozens of tools promise to help you craft the perfect resume, but they all want something in return: your email, your data, your payment details, often before you've even seen the product.
I wanted to build something different.
The privacy problem
Most resume builders operate on a simple model: you create an account, store your resume on their servers, and they have access to some of your most personal professional information. Your work history, skills, salary expectations, and career aspirations, all sitting in someone else's database.
Resume data is worth stealing, since it's raw material for identity theft and social engineering. It's also worth selling, packaged up and passed along to recruiters, advertisers, or worse. And it doesn't come back out easily, because your resume lives in their format on their platform.
I asked: what if we didn't store any of that?
Client-side everything
ResuPals runs entirely in your browser. When you type your work experience, it goes into localStorage, a small database that lives on your device, not mine. When you export to DOCX or PDF, the document generation happens in your browser using jsPDF and docx.js.
// All data stays local
const saveResume = (data: ResumeData) => {
localStorage.setItem('resupals-resume', JSON.stringify(data));
};
// Document generation happens in-browser
const exportDocx = async (resume: ResumeData) => {
const doc = new Document({
sections: [{
children: buildDocxContent(resume)
}]
});
const blob = await Packer.toBlob(doc);
saveAs(blob, 'resume.docx');
};
There's no account creation and no authentication flow. Nothing gets processed server-side. You open the app and start building, and your data never touches my servers because there aren't any servers handling your data.
Making ATS happy
A beautiful resume that can't be parsed by Applicant Tracking Systems is useless for most job applications, so ATS compliance became a core constraint.
ATS systems hate a predictable set of things:
- Tables and multi-column layouts
- Text boxes and graphics
- Headers and footers containing contact info
- Creative fonts and unusual formatting
- Images embedded in the document
What they want is boring:
- Simple, linear document flow
- Standard section headers (Education, Experience, Skills)
- Common fonts (Arial, Calibri, Times New Roman)
- Consistent date formats
- Clean plain text extraction
Every template in ResuPals follows those rules, and Polly has an ATS checker that analyzes your resume and flags potential parsing issues before you submit it.
Adding personality with mascots
Resume builders are stressful to use. You're often building one because you need a job, which is already an anxious situation. I wanted to soften that experience.
Enter the mascots. Resu, a cheerful document with a pencil, guides you through building the resume. Cova is a friendly envelope with a heart seal, and handles cover letters. Polly, a sparkly star with a magic wand, does polish and review.
Each has mood states that respond to what you're doing:
type MascotMood = 'happy' | 'focused' | 'encouraging' | 'excited' | 'thinking';
// Mood changes based on context
const getMood = (section: string, progress: number): MascotMood => {
if (progress > 0.8) return 'excited';
if (section === 'work-experience') return 'focused';
if (progress < 0.2) return 'encouraging';
return 'happy';
};
It's a small touch. The app stops feeling like a form you're filling out and starts feeling like company.
The technical stack
Next.js 16 with the App Router runs the shell as server components and the interactive builder as client components, which keeps the landing page mostly server-rendered and quick to load.
TipTap handles rich text, with a schema that outputs clean, ATS-friendly content. The floating toolbar appears on selection for quick formatting and stays out of the interface the rest of the time.
dnd-kit does drag and drop that feels native, so work experiences, education entries, and skills all reorder by dragging. Tailwind CSS 4 handles styling with the new CSS-first configuration, and the dark theme with coral accents matches the friendly-but-professional tone.
Lessons learned
localStorage has limits. The 5MB ceiling is plenty for resume data, but I had to think about cleanup and data validation, since corrupted localStorage can brick the app if you're not careful.
DOCX generation is complex. The docx.js library is powerful but verbose, and building templates requires understanding Word's XML structure, which has... quirks.
Privacy turned out to be a competitive advantage. Users notice when you don't ask for their email, and several early users specifically mentioned choosing ResuPals because they could use it without creating an account.
Mascots, on the other hand, need restraint. It's tempting to make them do more: animate constantly, speak in speech bubbles, react to everything. Less is more. They should sit alongside the work, not compete with it.
What's next
I'm exploring WebLLM for AI-powered writing suggestions that run locally. The model runs in the browser like everything else, which keeps the privacy-first principle intact while adding intelligent assistance: resume feedback without your data ever leaving your device.
The cover letter builder (Cova's domain) needs more guided templates. Cover letters are harder than resumes because they require narrative, and narrative is harder to structure.
And mobile support, because sometimes you need to update your resume from your phone.
Try it
ResuPals is live at resupals.com. No account needed. Your data stays yours. The repository is private, so the write-ups here are the closest thing to a tour of how client-side document generation works in it.