izzy.build costs $0 a month to run. Here's how it's built, and why it isn't a CMS.
Two briefs, one day of design in Claude Design, one day of build with Claude Code. Static pages on Cloudflare, git as the CMS, and a small API for views, reactions and moderated comments.
“A personal site shouldn't need a database, a plugin updater, and a monthly bill.”
Most personal websites carry a database, a plugin updater, a hosting plan, and a login page someone in another country is trying to guess. For a site whose job is to publish writing and collect a few comments, that’s a lot of moving parts to own.
izzy.build has none of them. It runs for $0 a month, serves pages as fast as a phone can draw them, and still has view counts, reactions, moderated comments, a newsletter signup and a contact form. It took two briefs, one day of design in Claude Design, and one working session of build with Claude Code. This post is the whole build, the decisions behind it, and the parts that broke.
What the site had to do
The job is narrow. When someone hears my name, the site should prove in 30 seconds that I know the gap between connecting an MCP and getting real work out of it, then send them to the right place: the next post, the newsletter, or PromptMetrics.
That turned into a short list of requirements:
- Writing first. Posts are the product. Each one follows the same four-part shape: the symptom, why the stock tool can’t do it, the fix, what it saved.
- Engagement. View counts, three reactions, and comments. Comments are moderated: nothing publishes until I approve it.
- Germany. The site runs from Berlin and collects names and emails, so GDPR applies from day one. No cookies, no stored IP addresses, no data sent anywhere it doesn’t need to go.
- Fast on a phone. Targets from the dev brief: Lighthouse 95+ on mobile, largest contentful paint under 1.5 seconds, layout shift under 0.05, under 30 KB of JavaScript on a post.
- No maintenance. Nothing to patch, nothing to restart, nothing that dies the first month I’m busy.
Design first, in Claude Design
I didn’t start with code. I wrote a design brief and gave it to Claude Design: the audience, the brand direction, every color as a named token in light and dark, the typography candidates, the page list, the post template, every engagement component, and real sample copy. No lorem ipsum anywhere.
The part that mattered most was the build order. The brief told Claude Design to work in six stages and stop after each one until I approved it: the style sheet first, then the post page on mobile before desktop, then home, articles, the builds, about and contact pages, and finally the social image template. It also set rules I’d give any designer. Use the tokens exactly, and if you need a new color, propose it and wait. Show light and dark side by side on every screen. Keep the accent blue to links and one primary button. Draw diagrams as simple line drawings in ink and slate. No stock photos, no robots, no glowing brains. Design the empty, loading, pending and error states, not just the happy path.
Stopping after each stage is what kept it honest. A style sheet I didn’t like would have leaked into every screen after it. Catching it at stage one cost minutes. I rejected two of three hero options and one of two type pairings on the way, and every rejection happened before anything downstream depended on it.
What came out was a handoff Claude Code could build from without guessing: a token file, a component stylesheet built only from those tokens, six pages at both 375 and 1280 pixels, four boards of loading, empty, error and success states, and five social image templates. The site’s stylesheet still uses those two files verbatim. When I want to change a color, I change it in one place.
Why not a CMS
The obvious options all fail the brief in a different way.
WordPress gives you an admin panel, and with it a database to secure, a PHP runtime, plugins that each need updating, and a login page that attracts automated attacks from day one. That’s a part-time job for a site that publishes every two weeks.
Hosted builders like Webflow or Squarespace remove the maintenance and replace it with rent, and the rent isn’t the real cost. The exit is. I wrote about that in I stopped renting my website: templates, content and analytics fused to the platform, and a rebuild when you leave.
A headless CMS fixes the portability problem and adds another vendor, another account, and another API between my writing and my site.
So izzy.build has no CMS. Git is the CMS. Every post is a markdown file in the repository, with a short block of settings at the top: title, date, rung, the symptom quote. A schema checks those settings at build time and refuses to publish a post with a missing or misspelled field. Version history comes free. So does backup.
Two ways to write feed the same files. On the laptop, I write or Claude Code writes, and a push publishes. On the phone, Pages CMS gives me a form-based editor at izzy.build/admin that commits to the same repository. No database behind either one.
The other reason is agents. A coding agent can read, write and check a markdown file in a repository perfectly well. It can’t reliably click through a WordPress admin panel, and it shouldn’t have to. Content in git is content an agent can maintain.
The architecture
The key split: almost everything is a static file, and only a small API runs code.
Astro builds every page at deploy time into plain HTML. Cloudflare serves those files from its edge, which means the reader’s phone gets a finished page from a data center near them, with nothing to compute. The styles are inlined into each page, the fonts are self-hosted, and a post ships about 5 KB of compressed JavaScript.
The only code that runs on demand is /api/*: record a view, return the counts, toggle a reaction, save a comment, add a newsletter signup, send a contact message. It runs as a Cloudflare Worker next to a D1 database, a small SQLite database created in Cloudflare’s EU region so the data stays in Europe. The Worker calls out to three services only when it has to: Loops for the newsletter, Resend for contact emails, Slack to tell me a comment is waiting.
Two choices I’d defend. First, Cloudflare over Vercel: Vercel’s free Hobby plan is for non-commercial use, and this site routes readers to a paid service. Second, D1 over Supabase: one vendor instead of two, and the database lives on the same platform as the code that reads it.
Reading a post
The rule for every post page: the article never waits for the API.
The page arrives complete. The reader starts reading. Only then does a small script make one call for the view count, reactions and the first comments, and it drops them into space the layout already reserved, so nothing on the page jumps. If the API is slow or down, the article is already on screen: the view count quietly disappears and the reactions show a retry.
A view counts after 10 seconds on the page or half a scroll, whichever comes first, and known bots don’t count at all. That’s closer to “someone read this” than “someone’s browser loaded this.”
Engagement without cookies
Counting views and reactions normally means a cookie, and a cookie in Germany means a consent banner. izzy.build has neither.
Instead, the Worker combines the visitor’s IP address and browser details with a secret that changes every day, and stores only the resulting fingerprint. The raw IP address is never stored. Because the secret changes daily, fingerprints from different days can’t be linked. That’s enough to count one view per person per day and let someone undo their own reaction, and not enough to follow anyone around.
Comments get more protection, because they’re the one place strangers can put text on my site.
A Turnstile check blocks bots without a puzzle for real people. The Worker allows three comments per visitor per hour, strips anything that looks like HTML, and saves the comment as pending. The commenter sees their own pending comment, stored only in their browser, and nobody else sees it. Slack sends me a link to the moderation page, which sits behind Cloudflare Access: a one-time code to my email, and nobody else gets past the login screen. The page also checks the login itself, so a mistake in the Access settings can’t expose the queue. From the same page, one form deletes everything stored under a commenter’s email, which is how I answer a GDPR deletion request.
Publishing
Publishing is a push to GitHub. Everything after that is automatic.
The check at the start of each build is the part that lets me hand writing to an agent or edit from a phone without worrying. A missing field, a rung that doesn’t exist, a diagram file that isn’t there, or raw HTML pasted into a post each stops the build with an error that names the file. The live site keeps running its last good version. A broken post never reaches readers.
Each deploy also regenerates the RSS feed, the sitemap, and a social preview image for every post, drawn from the same template Claude Design produced.
What it costs, and how it scales
The running cost at my traffic is $0 a month. The only bill is the domain. Here’s what each piece allows for free, from the vendors’ pricing pages today:
| Piece | Free allowance | What happens beyond it |
|---|---|---|
| Static pages on Cloudflare | Unlimited requests, not counted against anything | Nothing. A viral post costs the same as a quiet one |
Worker (/api/*) | 100,000 requests a day | Workers Paid, $5 a month for 10 million requests |
| D1 database | 5 million rows read and 100,000 written a day, 5 GB | Paid usage pricing |
| Workers Builds | 3,000 build minutes a month | A deploy takes about a minute |
| Loops (newsletter) | 1,000 subscribers, 4,000 emails a month | Paid plan |
| Resend (contact form) | 3,000 emails a month, 100 a day | Paid plan |
| Turnstile, Web Analytics, Access | Free (Access up to 50 users) | I need one user |
The scaling story is in the first row. Reading is the expensive part of any popular site, and here reading costs nothing and has no cap. What scales with traffic is the engagement API, and only because each post page makes one or two small calls. If a post went properly viral, the risk isn’t a bill. It’s that view counts would stop counting for the rest of the day while every article kept loading, and $5 a month removes that ceiling.
The numbers
I ran Lighthouse against the live site today, on the heaviest page: the WHOOP post, with three diagrams.
| Performance | Accessibility | Best practices | SEO | |
|---|---|---|---|---|
| Mobile | 99 | 100 | 100 | 100 |
| Desktop | 100 | 100 | 100 | 100 |
Layout shift is 0 and blocking time is 0 ms on both. The whole page weighs 163 KB. Largest contentful paint is 0.5 seconds on desktop and 1.8 seconds on a simulated mid-range phone.
That last number misses my own target. The brief said under 1.5 seconds on mobile, and the site lands at 1.8 on the live network. Two changes got it this far: inlining the styles into each page, which removed a separate request the browser had to wait for, and self-hosting the fonts, which also keeps visitors’ IP addresses away from Google. The remaining gap is the fonts loading on a slow connection, and the fix would show a fallback font for a moment on first visit. I’d rather keep the look. The site is also too new for Chrome’s real-user data, so these are lab numbers and nobody should read rankings into them.
What broke
It wasn’t smooth, and the breaks are more useful than the wins.
The framework changed under me. Astro had just shipped version 7 with a new default markdown engine, and the plugins that turn a post into the numbered-section layout didn’t run on it. The fix was to switch back to the older engine explicitly. Reading the current docs instead of trusting memory caught it in minutes.
A safety check that only warned. The rule that rejects raw HTML in a post logged an error, and the build carried on and published the broken page anyway. I only found it because I tested the failure on purpose. Now a separate check runs before every build and stops it.
Three minutes offline. While connecting the izzy.build domain, the temporary address went down. The deploy tool reads a copy of the configuration made at build time, not the file I’d just edited, so my fixes didn’t apply until I rebuilt. The lesson is now written into the repository’s notes for the next agent that works on it.
Two quiet URL bugs. Canonical links pointed search engines at .html addresses instead of clean ones, and the RSS feed added trailing slashes the pages don’t use. Neither shows up in a browser. Both would have split search signals across two addresses for the same page. A scan of the built output caught them before search engines did.
Every one of these crossed layers: framework, build tooling, DNS, SEO. That used to mean four specialists and a ticket queue. Here, one session held the whole picture, and my job stayed the same as always: define what good means, review what comes back, and refuse to ship what I can’t defend.
Steal this
If you’re rebuilding a personal or small-company site this year, this is the pattern:
- Design before code, in gated stages. Write the brief, name every token, and make the design tool stop for approval after each stage.
- Git is the CMS. Markdown files, a schema that refuses bad posts, and a phone editor if you need one.
- Static by default. Prebuild every page and serve it from the edge for free. Only the interactive parts run code.
- The article never waits. Load engagement after the content, and reserve the space so nothing moves.
- Privacy by construction. No cookies, no stored IPs, moderation behind a real login, deletion in one step.
If you’ve built yours differently, or you tried this and hit a wall I didn’t, tell me. That’s the more useful story.
Get the next MCP-gap post in your inbox. Every two weeks, no fluff.
Almost done. Check your inbox.
Click the link in the email to confirm. No confirmation, no emails.
Comments
Loading comments…No comments yet. The form is right below.
Your connection or our server hiccuped. Your draft below is safe.
Your comment will appear after review.