Skip to content

For a while, my sites were just an old repo with an export of my previous blog, plus an idea: split my "code" self from my "music" self. I finally did it, with an AI agent as my pair programmer, and the whole setup costs me a single bill a year.

What I pay for (and what I don't) ​

WhatWhereCost
Domain registrationA domain registrarAround $10 a year for a .com
DNS and HTTPS certificatesCloudflareFree
HostingCloudflare Workers, static assetsFree
hey@ email addressCloudflare Email RoutingFree
Contact formsCloudflare Turnstile and a small WorkerFree

No hosting plan, no server to patch, no monthly invoice. Cloudflare's own docs say requests to static assets are free and unlimited, with no extra cost for storing them.

How it's set up ​

  • Three static sites, built with VitePress: I write Markdown and it builds plain HTML, CSS and JavaScript. Any static site generator works the same way: Astro, Hugo, Eleventy, Jekyll...
  • One Cloudflare Worker per site, made of static assets that Cloudflare serves from its network, close to whoever is visiting. The only code that runs is a small contact-form handler, and only when someone sends a message.
  • Cloudflare DNS for the domain. Each Worker claims its own hostname (dinartux.com, code.dinartux.com and music.dinartux.com), and Cloudflare creates the DNS record and the HTTPS certificate for it.
  • Email Routing forwards mail sent to my domain to my personal inbox, and lets the contact forms email me without an address on the page.

Cloudflare Pages used to be the go-to for this, and it still works, but Cloudflare now recommends Workers with static assets for new projects, so that's what I went with.

Do it yourself ​

You'll need a domain, a free Cloudflare account and Node.js.

  1. Move your domain's DNS to Cloudflare. Add the domain on the Free plan, then switch the nameservers at your registrar to the two Cloudflare gives you.

  2. Build your site into a folder of static files, for example with npm run build into dist/.

  3. Add a wrangler.jsonc next to it. This is almost exactly the one behind this blog:

    jsonc
    {
      "name": "my-site",
      "compatibility_date": "2025-07-02",
      "assets": {
        "directory": "./dist",
        "not_found_handling": "404-page"
      },
      "routes": [{ "pattern": "example.com", "custom_domain": true }]
    }
  4. Deploy. Log in once, then deploy:

    sh
    npx wrangler login
    npx wrangler deploy

    Wrangler uploads only the files that changed and attaches your domain, and Cloudflare sets up the DNS record and the certificate.

  5. Redirects (optional). A _redirects file in your build output handles them. Mine keeps the links from my old blog alive:

    txt
    /be-understood-not-clever  https://code.dinartux.com/blog/be-understood-not-clever  301
  6. Email (optional). In Email Routing, create an address like hey@example.com and point it at your inbox. Cloudflare adds the MX records for you.

  7. A contact form (optional). Rather than publishing your address, add a form with Cloudflare Turnstile (a free CAPTCHA alternative) and a small Worker that emails you through Email Routing. Sending to your own verified address is free.

  8. More sites? One Worker per subdomain, each with its own routes entry. That's how I run three.

Things I ran into ​

  • An old Pages project was holding my domain. wrangler deploy couldn't take dinartux.com over ("externally managed DNS records", error 100117). Removing the domain and its DNS record from the Pages project fixed it.
  • Static files are free; code is what's metered. Requests that run Worker code count against the Free plan's 100,000 a day. My contact form's Worker only runs for /api/* (run_worker_first in wrangler.jsonc), so it only counts when someone sends a message.
  • Free-plan limits: 20,000 files per deployment and 25 MiB per file. Plenty for a personal site.
  • Clean URLs: with VitePress's cleanUrls, Workers serves /about from about.html and redirects /about.html to /about.
  • Self-host your fonts. Not a cost thing, but bundling them with the site means no third-party requests: faster, and more private for your visitors.

How Claude Code helped ​

I built all of this with Claude Code, Anthropic's AI coding agent, working right in my repo. I'm an AI enthusiast, and this was a good test of using AI responsibly: it did the heavy lifting, and I kept the decisions.

What it did:

  • Read the old repo and my blog export, and proposed the plan: three sites on one domain, each an assets-only Worker.
  • Migrated my posts and pages, checking them character by character against the export, and set up 301 redirects from the old URLs. It kept the old feed address too, so subscribers didn't notice a thing.
  • Designed the look with me, from the beard logo to an accent color per site, and wrote the Latin American Spanish version of my music site.
  • Deployed with Wrangler, found the DNS conflict with the old Pages project, and told me exactly what to remove in the dashboard.
  • Checked every deploy against the live sites before calling it done.

What I did: decide, review, and approve every deploy, deletion and commit. Claude Code is a paid tool, so it's not part of the "free" here, but it saved me a lot of time. Fittingly, it helped me draft this post too, and I reviewed it before it went out.

When this isn't the right fit ​

This works because my sites are static. If you need a database, server-side code, a CMS where several people edit content, or real mailboxes (Email Routing only forwards), you'll want proper hosting. For a personal site, a portfolio or a blog, though, the domain might be the only bill you need.