14 June 2026

Rebuilding devenney.io with Claude and Cloudflare Pages

← back to writing

Every few years, I rebuild this site.

This one happened over a weekend, between World Cup matches.

The pattern is consistent: new technology arrives, I wait until the guilt about the old stack becomes unbearable, and I redo the whole thing. The previous site was webpack and Handlebars, which tells you roughly how long it had been sitting there. I have been busy. I also jumped straight past whatever the intermediate era is supposed to be (pick your flavour of React framework) and landed directly in the Claude and Astro 6 era. I did manage to keep Tailwind, as I love that framework.

The honest context: most of this rewrite was done by Claude Code. The workflow was: write a spec, hand it to Claude, review the output, iterate. I contributed the judgment; Claude contributed the typing. That is a workflow you can fit between matches.

The reason I want to write about this is not the AI angle. It is the stack. Each technology I picked had the same criterion: does it remove a category of problem from my personal time, or add one? Every step closer to that answer being “remove” is a step closer to being able to focus on the things that are actually worth my time.

Astro

Astro is a static site framework with a simple idea: build once, serve flat files, no runtime. For a personal site it is the right default. Content collections give you typed and validated blog posts without a CMS. The island architecture means interactive pieces do not pull in JavaScript you do not need.

The previous stack was webpack and Handlebars. Getting a blog post onto the page required a build pipeline, a template, and enough manual wiring that I avoided touching it. The content schema for this blog is about as simple as it gets:

const blog = defineCollection({
  loader: glob({ pattern: '**/[^_]*.md', base: './src/content/blog' }),
  schema: z.object({
    title: z.string(),
    description: z.string().optional(),
    date: z.string(),
  }),
});

Write a markdown file, push, done. No templates, no build configuration to maintain.

One category of problem removed: I no longer have to think about the build system.

Cloudflare Pages

This is the part worth writing about.

Here is what deploying used to look like:

Build → S3 sync → CloudFront invalidate → check DNS → hope

Three AWS services, IAM policies that needed to be exactly right, and a mental overhead that meant I avoided shipping unless I had a good reason.

Cloudflare Pages is configured with two fields:

Build command:  npm run build
Output dir:     dist

That is the entire configuration. Every pull request gets a preview deployment automatically, so before merging anything I can click a URL and check it looks right. When I merge, CI has already confirmed there are no broken links and accessibility has not regressed. Cloudflare deploys the result. GitHub Actions tags the release. I do nothing else.

I have been running things on AWS for years and I know how to navigate it. But there is a real difference between infrastructure you can use and infrastructure that stays out of your way. One category of problem removed: I no longer have to think about deployment.

It is also free.

Lighthouse CI

Lighthouse is Google’s tool for auditing accessibility, SEO, and performance. Running it in CI means any regression fails the build before it ships.

The configuration lives in .lighthouserc.json:

{
  "ci": {
    "collect": { "staticDistDir": "./dist" },
    "assert": {
      "assertions": {
        "categories:accessibility": ["error", { "minScore": 0.9 }],
        "categories:seo":           ["error", { "minScore": 0.9 }]
      }
    },
    "upload": { "target": "temporary-public-storage" }
  }
}

The key detail for a statically built site: use staticDistDir rather than pointing Lighthouse at a live URL. Astro builds flat HTML files you can audit locally without a server. The upload block is optional but gives you a shareable report URL for every CI run at no cost.

It caught several accessibility issues I would not have noticed manually. One category of problem removed: quality regressions that used to slip through now do not.

Lychee

Lychee checks every link in the built site. It is one step in the CI workflow:

- name: Serve dist for link check
  run: npx --yes serve dist -l 4321 &

- name: Wait for server
  run: npx --yes wait-on http://localhost:4321 --timeout 15000

- name: Check links
  uses: lycheeverse/lychee-action@v2
  with:
    args: --verbose --no-progress --exclude 'linkedin\.com' http://localhost:4321/
    fail: true

The non-obvious thing: Lychee cannot resolve root-relative links (like /blog/ or /rss.xml) from static HTML files directly. The fix is to serve dist/ on a local port first and point Lychee at the running server. Two extra steps, but it means broken links fail the build.

One category of problem removed: I no longer ship broken links without noticing.


The full flow, from writing to live: open a PR, CI runs lint and type check and Lighthouse and Lychee, Cloudflare Pages builds a preview, I check it looks right, I merge. Cloudflare deploys. GitHub Actions tags the release. That is it.

Every step in this stack was chosen because it removed something from my mental overhead. Personal time is finite. I would rather spend mine watching Scotland win the world cup.

The source is on GitHub.

My dearest Erin,

Tonight will mark the end of a life long dream of mine. The completion of a labour I began as a child, even before I knew the words for it. Before I knew the ways to accomplish such a task. Even now my heart is heavy with dread; I fear I've built a great and powerful evil. Or rather, together, Albert and I have built it. I know that I am to blame. I allowed Albert to persuade me to change the designs. I allowed this perversion of my course.

This new machine that we've built is unlike any I ever imagined. It looks... like a man. A lifeless steel man. Albert convinced me that even with the new Geological Unmanned Terraforming System we designed, the task of extracting the ore is still too dangerous for a human. He plans to completely replace human workers in the mining sector. The idea of displacing so many men... the annihilation of countless jobs in the name of safety seems an unbalanced trade off. What is worse, the latest prototypes have been revised to carry small firearms.

Erin, I fear that I have put you and this entire city in danger. I will speak to him tonight on the matter. Perhaps I can persuade him to scrap the whole project. In the meantime, please be careful. This world is getting darker all the time.

I weep at the thought of something terrible happening to you. I could not bear it.

All my love,
Brendan