This site is the first project I'm writing up publicly — partly because it's a good example of agentic coding in practice, and partly because building it surfaced enough interesting problems to be worth documenting.
The goal
I wanted a personal site that could serve two purposes: a short bio landing page and a full CV, hosted on GitHub Pages with a custom domain. Simple requirements, but I also wanted the implementation to be a genuine demonstration of how I work — using Claude Code throughout, treating it as a collaborative tool rather than a code generator.
Stack choices
The core stack is three tools: Jinja2 for HTML templating, Tailwind CSS v3 for styles, and WeasyPrint for generating the PDF résumé from the CV page.
Jinja2 was an easy call — it's Python, straightforward, and gives you a clean separation between layout and content without the overhead of a full static site generator. Tailwind v3 (not v4) because v4 requires Node 18+ features that create friction in CI. WeasyPrint for PDF because it renders directly from HTML+CSS, so the CV page and the résumé PDF stay in sync automatically.
The design aesthetic I described to Claude as "Editorial Scientific" — DM Serif Display for the name, DM Sans for body copy, JetBrains Mono for metadata. Warm off-white background, navy headings, teal for links and timeline accents.
The build pipeline
The pipeline runs in GitHub Actions on every push to main that touches source files:
- Compile Tailwind CSS →
dist/styles.css - Render Jinja templates → HTML pages (
scripts/build_site.py) - Generate the PDF résumé via WeasyPrint (
scripts/generate_pdf.py) - Commit and push the built artifacts back to the repo
The key design decision here: source files live in templates/ and src/,
and the generated HTML, CSS, and PDF are committed by the Actions workflow. You never edit
index.html or cv/index.html directly — you edit the templates and let CI
rebuild everything.
One gotcha: dist/styles.css is gitignored locally but needs to be committed for GitHub Pages
to serve it. The solution is git add -f dist/styles.css in the Actions step — the -f
flag bypasses the .gitignore rule. Not elegant, but it works.
PDF layout: why display: table
The CV page uses a two-column layout. Getting that to render correctly in WeasyPrint took a few tries.
CSS Grid didn't work — WeasyPrint's Grid support is partial. Floats were unreliable with the column heights.
The solution was the old-school display: table / display: table-cell approach,
which WeasyPrint handles cleanly. It's not how you'd write CSS in 2025 for the web, but for a PDF renderer
it's the right tool.
Where Claude Code fit in
I used Claude Code throughout — not just to generate boilerplate, but as an active collaborator on design decisions, debugging, and architecture. A few specific examples:
- Iterative design: I described the aesthetic I wanted in plain language and Claude produced the Tailwind component classes. I gave feedback ("the nav doesn't look great", "remove the AI-purple", "make it more editorial"), and we iterated until it matched what I had in mind.
- Debugging WeasyPrint: When the two-column layout broke in the PDF, Claude worked through the CSS Grid → floats → display:table progression with me, explaining why each approach failed in WeasyPrint's rendering model.
- Git recovery: OneDrive sync corrupted a git object mid-session. Claude walked through diagnosing the corrupt blob, identified an orphan tree object, and guided a targeted recovery — soft reset, re-stage, re-commit, manual object deletion — without needing to re-clone.
-
CI setup: The Actions workflow needed
permissions: contents: writeand a specific push URL format usingGITHUB_TOKEN. Claude caught the permission issue and fixed the push step.
Things I'd do differently
The main thing I'd reconsider is having the Actions workflow commit built artifacts back to the repo. It works, but it means every push generates a follow-up "build" commit that clutters the history. A cleaner approach would be to deploy from an artifact rather than committing built files — but that requires a different GitHub Pages setup, and for a personal site the current approach is fine.
I'd also set up the blog structure earlier. Adding individual post pages required updating the Jinja
builder, adding new CSS classes, and creating a templates/blog/ subdirectory — none of it
is hard, but it's easier to think through the routing upfront than to retrofit it.
What's next
The Projects page is a placeholder. I want to write up a few specific pieces of work there — the QC utilities, the agentic coding rollout at GAR. The blog will grow with more posts on prompt patterns, AI-assisted development in regulated environments, and tooling.
The site itself is open source at github.com/dchelimo/afya-bio if you want to see how it's wired up.