Generate a print-optimized, ATS-friendly single-page resume PDF from portfolio content. Use when user wants to create or regenerate their resume. (project)
Trigger phrases: "resume", "generate resume", "PDF", "export resume", "make resume"
| File | Purpose |
|---|---|
src/pages/ResumePage.tsx |
React component for print-optimized layout |
src/pages/ResumePage.css |
Print/screen styles with theme isolation |
scripts/generate-resume.ts |
Puppeteer PDF generation script |
content/profile.yaml |
Name, email, location, tagline, stats |
content/experience/index.yaml |
Jobs with highlights |
content/skills/index.yaml |
Categorized skills |
public/resume.pdf |
Default output file |
Critical: Include ALL jobs from content/experience/index.yaml. Missing experience (especially major companies) damages credibility more than a slightly longer resume.
MAX_JOBS to match total jobs in experience fileHighlights should follow Action ā Outcome format with quantified results:
ā "Managed product development"
ā
"Shipped Advanced API from 0ā1: 17 methods serving 1M+ daily requests"
One-page resumes get 2x more callbacks. Fit content by:
const RESUME_CONFIG = {
SUMMARY_SKILL_CATEGORIES: 2, // Categories in impact summary
SKILLS_PER_CATEGORY: 3, // Skills per category in summary
SUMMARY_COMPANIES: 4, // Companies to list in summary
MAX_JOBS: 6, // Include ALL jobs - adjust to match your experience
MAX_HIGHLIGHTS_PER_JOB: 3, // Bullets per job (action ā outcome format)
};
Adjust MAX_JOBS to match the total number of jobs in your experience file.
Skills are displayed in a 2-column grid with category names in bold:
Category A: skill1, skill2, skill3... | Category B: skill1, skill2...
Category C: skill1, skill2, skill3... | Category D: skill1, skill2...
Categories are pulled from content/skills/index.yaml. Common category structures:
For Engineers:
For Product Managers:
For Designers:
Cause: ThemeProvider applies dark background (#08080a) to body/#root, which bleeds into the PDF outside the .resume-page container.
Fix: CSS must override parent elements in @media print:
@media print {
html,
body,
#root {
background: white !important;
min-height: auto !important;
padding: 0 !important;
margin: 0 !important;
}
}
Fix: Reduce MAX_HIGHLIGHTS_PER_JOB in ResumePage.tsx. Do NOT reduce MAX_JOBS - missing experience is worse than a slightly longer resume.
Cause: MAX_JOBS was set too low, cutting off important companies.
Fix: Always set MAX_JOBS to the total number of jobs in content/experience/index.yaml. Count your jobs and update the config.
Fix: Ensure .resume-role-highlight has no border-bottom or background-color.
Fix: Reset all children with:
.resume-page * {
background: transparent;
color: inherit;
}
Fix: The script waits for document.fonts.ready + 500ms buffer. If fonts still don't load, increase POST_FONT_BUFFER_MS in generate-resume.ts.
npm run devcontent/profile.yaml (name, email, location, tagline, stats)content/experience/index.yaml (jobs with highlights)content/skills/index.yaml (categorized skills)npm run validatenpm run generate:resume
public/resume.pdfnpm run generate:resume -- --name "Jane Smith"
# Output: public/jane-smith.pdf
npm run generate:resume -- --url http://localhost:3000
# Uses different dev server port
| Issue | Solution |
|---|---|
| "net::ERR_CONNECTION_REFUSED" | Start dev server: npm run dev |
| PDF is blank | Check browser console at /resume for React errors |
| Wrong content displayed | Verify YAML files, run npm run validate |
| Fonts look wrong | Increase POST_FONT_BUFFER_MS in script |
| Two pages instead of one | Reduce MAX_HIGHLIGHTS_PER_JOB, NOT MAX_JOBS |
| Dark background in PDF | Verify print CSS overrides html/body/#root |
| Jobs missing | Increase MAX_JOBS to match total in experience file |
| Skills look cramped | Reduce number of skills per category or categories |
Update hero CTA in content/profile.yaml to link to the resume:
hero:
cta:
secondary:
label: "Download Resume"
href: "/resume.pdf"
const PAGE_DIMENSIONS = {
LETTER: {
WIDTH_PX: 816, // 8.5" at 96dpi
HEIGHT_PX: 1056, // 11" at 96dpi
},
};
const TIMING = {
POST_FONT_BUFFER_MS: 500, // Wait after fonts.ready
PAGE_LOAD_TIMEOUT_MS: 30000, // Navigation timeout
};
await page.pdf({
path: output,
format: 'letter',
printBackground: true,
margin: {
top: '0.4in',
right: '0.5in',
bottom: '0.4in',
left: '0.5in',
},
preferCSSPageSize: true,
});
The resume pulls data from these content files:
| Data | Source | Field |
|---|---|---|
| Name | profile.yaml |
name |
profile.yaml |
email |
|
| Location | profile.yaml |
location |
| Tagline | profile.yaml |
about.tagline |
| Stats | profile.yaml |
about.stats[] |
| Jobs | experience/index.yaml |
jobs[] |
| Skills | skills/index.yaml |
categories[].skills[] |
npm run validate)