Build a site like this with AI, part 3 of 4
One file for the resume page and the PDF
A JSON resume renders the web page at build time, and Playwright prints the same data to a PDF during the deploy.
I wanted a resume page and a downloadable PDF that never disagree. The fix is to keep the resume in one file, web/src/data/resume.json, and generate both from it.
The data
The file is plain JSON: a summary, skill groups, each role with its bullets, education and certifications.
{
"name": "Richard Clegg",
"title": "Lead Data Engineer",
"experience": [
{
"role": "Senior Data Engineer",
"company": "UTurn Data Solutions",
"period": "Jan 2025 - Present",
"bullets": [
"Led the migration from Talend to Apache Airflow on Amazon MWAA, ..."
]
}
]
}
To build it, I gave Claude Code my old Word resumes and asked it to merge them into this shape. Then I asked it to check my certifications against my Credly profile, which showed which ones had lapsed so the site lists only active ones.
The page
Astro imports JSON directly, so the resume page (web/src/pages/resume.astro) is a template over the data. Simplified:
---
import resume from "@/data/resume.json"
---
{resume.experience.map((job) => (
<article>
<h3>{job.role}, {job.company}</h3>
<ul>{job.bullets.map((b) => <li>{b}</li>)}</ul>
</article>
))}
The PDF
scripts/build-pdf.mjs reads the same file, builds a plain print layout as an HTML string, and asks headless Chromium to print it:
const browser = await chromium.launch()
const page = await browser.newPage()
await page.setContent(html)
const pdf = await page.pdf({ format: "Letter" })
writeFileSync(join(root, "public/Richard-Clegg-Resume.pdf"), pdf)
The npm build script runs it before Astro, so the PDF lands in public/ and ships with the site:
"build": "node scripts/build-pdf.mjs && astro build"
In CI, the deploy’s build command installs Chromium first with npx playwright install --with-deps chromium. The PDF is in .gitignore, so the repo never holds a stale copy.
Updating it
I edit resume.json and push. The page and the PDF update together in about two minutes.