Blog

Building This Site with Claude Code

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:

  1. Compile Tailwind CSS → dist/styles.css
  2. Render Jinja templates → HTML pages (scripts/build_site.py)
  3. Generate the PDF résumé via WeasyPrint (scripts/generate_pdf.py)
  4. 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:

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.