Building This Site with Astro, Tailwind, and Claude Code
- #astro
- #ai
This site is a static portfolio and blog built with Astro. This post covers how it was built, the tools involved, and what the development process looked like with Claude Code as a collaborator throughout.
The Stack
Astro — the framework. Astro is a content-focused static site generator that ships zero JavaScript by default. Pages are .astro files, blog posts are Markdown files in a content collection. The build output is plain HTML — fast to serve, easy to deploy.
Tailwind CSS v4 — styling. v4 drops the tailwind.config.js file in favour of CSS-native configuration via @theme blocks. No build plugin needed beyond @tailwindcss/vite. All design tokens (colours, fonts) live in global.css.
Pagefind — static search. Runs after the build, indexes the HTML output, and ships a small JS bundle for client-side search. No server required. The /search page loads the Pagefind UI and wires a custom input to it.
Vitest — unit tests. Covers utility functions: reading time calculation, tag deduplication. Fast, no configuration overhead since it shares the Vite pipeline already used by Astro.
Playwright — end-to-end tests. Tests run against the dev server on Desktop Chrome and Pixel 5 mobile. Covers page rendering, navigation, tag filtering, URL persistence, and the QR code back link.
oxlint — linter. Significantly faster than ESLint. Runs on staged files via lint-staged before every commit.
Prettier — formatter. With prettier-plugin-astro and prettier-plugin-tailwindcss for .astro file support and class sorting. Also runs on staged files pre-commit.
Husky + lint-staged — git hooks. Pre-commit runs Prettier and oxlint on staged files, then the full unit test suite.
Vercel — deployment. Connected to the GitHub repo. Every merge to main triggers a production deploy.
The Development Process
The entire site was built with Claude Code — Anthropic’s CLI for AI-assisted development. Not as a one-shot prompt, but as an ongoing back-and-forth: propose, review, adjust, repeat.
A few things that worked well in practice:
Small, focused files. Astro’s page-per-file model fits naturally. Each page is its own file, each utility is its own module. When a file has one job, the AI produces focused output. When a file has many jobs, the output gets muddier. Keeping files small isn’t just good structure — it’s also better input.
Extracting logic before testing. The reading time calculation and tag deduplication both started as inline expressions in .astro frontmatter. Before adding tests, they were extracted into src/utils/. Two small pure functions. The unit tests practically wrote themselves — and more importantly, it was easy to verify they were correct.
AI generates, I review. The test suite was generated by Claude Code and reviewed by me. The review process: read first (does this test what I think it tests?), run second (does it pass?), then think about what’s missing and ask for more. The loop is fast when the code is well-structured. When it isn’t, the tests are hard to verify — which is usually a signal that the code needs to be split, not the tests.
CI as a safety net, not a gate. The GitHub Actions workflow runs on every PR: format check, lint, unit tests, build, e2e tests. It’s not there to slow things down — it’s there to catch the things that slip through when you’re moving quickly.
Slash commands for repetitive git work. Claude Code supports custom slash commands — scripts that expand into full prompts. Two that became part of the daily workflow here: /commit-push-pr and /clean_gone — covered in more detail in a separate post.
What’s on the Site
- Blog posts (this one included)
- Tag filtering with URL persistence —
/?tag=astrogives you a shareable filtered view - Static full-text search via Pagefind
- An
/aboutpage with contact links and a QR code - RSS feed at
/rss.xml - A 404 page
Source
The source is on GitHub if you want to look around.