•
6 min read
How this site works

This site is small, but there’s a real pipeline behind it, and lately a lot of the pipeline runs itself. Here’s how the pieces fit together.

Astro

The site is built with Astro 5, starting from the Astro Nano theme and then bent to my taste. Everything is rendered to static HTML at build time. There’s no server, no database, and no JavaScript on a page unless a component asks for it.

The pieces I lean on most:

  • Content collections. Each post is a folder under src/content/blog/ with an index.mdx inside. The frontmatter (title, date, description, draft, tags) is validated against a schema at build time, so a typo in a date fails the build instead of quietly breaking the list.
  • MDX. Posts are Markdown, but they can import components. That’s how the interactive bits get into a post: the linear calendar, the theme toggle comparison, the pixel header demo. Each one is an .astro file sitting next to the post that uses it.
  • Tailwind, with class-based dark mode. A dark class on <html> flips the theme, and the toggle in the footer stores your choice.
  • Scoped scripts. An Astro component’s <script> runs once per page, and the site uses view transitions between pages, so every widget is written to find its own elements and re-attach after a navigation. It’s a small discipline that pays off constantly.

Building is one command. It type-checks the whole project first and then writes the site to a folder. ESLint runs on top of that and has to be clean.

GitHub

The source lives in a private GitHub repository. Every change, including every blog post, is a branch and a pull request into main. That sounds heavy for a personal site, but it gives me two things I want:

  1. A preview before anything is public. More on that below.
  2. A gate. A ruleset on main requires the Cloudflare Pages check to pass before a merge, and auto-merge is on. So a pull request that builds cleanly merges itself once it’s marked ready, and one that doesn’t build can’t merge at all.

There are no GitHub Actions. Everything that needs to run happens in the Cloudflare build.

Cloudflare

Cloudflare Pages is connected to the repository and builds every push. A push to main becomes paulpetty.com. A push to any other branch gets its own preview site at a URL based on the branch name.

Two details make the previews useful for drafting:

  • Drafts are visible on previews only. Cloudflare sets a CF_PAGES_BRANCH variable during the build. A few lines in the site check it: if the branch isn’t main, posts marked draft: true are included. On the production build they’re left out. So a draft has a real URL I can open on my phone, and nobody else will find it.
  • Headers for embedded Artifacts. Cloudflare Pages reads a _headers file from the build output. Any Artifact served from /artifacts/ gets a Content Security Policy that only lets it be framed by this site and only lets it load scripts from here and two named CDNs.

The preview builds also post a comment on the pull request with the links, which turns out to matter for the last piece.

Claude

Most of what has happened on this site in the last week was done by Claude working in Claude Code in the cloud, with me steering from my phone or a browser tab. The posts about the heatmap, the pixel header and the two-button theme toggle were all built that way, and the prompts are quoted in the posts.

The setup is mostly plain files in the repository:

  • CLAUDE.md describes the project: the commands, where things live, and what “write a blog post” means here. It’s the first thing Claude reads in a session.
  • A skill for blog posts. It’s a Markdown file with the whole loop written down: where posts go, how to embed an Artifact so it inherits the site’s CSS, how to check the build, open a draft pull request, find the Cloudflare preview link, and ask me before publishing.
  • A session hook that installs dependencies when a cloud session starts, so the site can be built and previewed right away.
  • An Artifact kit. A small component, a set of CSS variables and a stylesheet endpoint, so an Artifact made in a Claude chat can be dropped into a post and look like it belongs. Inline components use the site’s Tailwind classes directly; self-contained apps go in an iframe that pulls the theme from the parent page.

A typical exchange goes: I describe what I want, sometimes with a screenshot. Claude writes the post and the component, builds the site, runs it in a headless browser to check both themes and a phone-sized viewport, pushes a branch, opens a draft pull request, waits for Cloudflare, and sends me the preview link with a question: “Publish this?” If I want changes, I say so and get a new link. If I say yes, it flips the draft flag, marks the pull request ready, and the merge and deploy happen on their own. Nothing goes live without that yes.

The same loop handles site changes that aren’t posts. The footer, the header and the theme toggle all went through it. The only difference is the question at the end is “Merge this?” instead.

What I like about it

Each piece does one job. Astro turns files into a site. GitHub holds the history and the gate. Cloudflare builds and serves, and hands out a preview for every branch. Claude does the typing, the testing and the waiting around, and the bar for a change is the same as it would be if I made it myself: it builds, it lints, it looks right in both themes, and I looked at it before it shipped.