HM

How this site is built

This site is a project too. Here is how it works, what it protects, what it stores and what it costs, read from the code as it is today.

It is live on Cloudflare's free plan. What this page says comes from the code and its tests, not from traffic numbers.

Architecture

One Worker, one deploy. Astro renders every page to static HTML at build time, and Cloudflare's asset layer serves the pages, fonts and images. Those requests never run any code, and Cloudflare serves static assets free and without limit, so the pages stay up even if the Worker's daily quota runs out.

Code runs only where it has to:

POST /api/contact
A contact form endpoint: it stores a message, then emails me. The form itself is off the page for now, so nothing calls it.
POST /api/hit
The page-view counter.
GET /api/github
My pull requests, from a snapshot that a cron trigger refreshes every six hours, so no page view waits on GitHub.
GET /api/admin/stats and /api/admin/messages
My private view of the counts and messages. Switched off until Cloudflare Access is in front of it.
GET /resume
The resume PDF, counted as a download.

Any other path under /api/ gets a JSON 404. Everything else is a static file or the 404 page.

A browser's requests reach Cloudflare's network. Pages, fonts and images come from the asset layer and never run code. Only /api/* and /resume reach the Worker, and the admin routes stay switched off until Cloudflare Access is in front of them. Only the Worker uses what sits behind it: D1 for contact messages and daily view counts, KV for the GitHub snapshot, an email binding that can send to one verified address, and the rate limits and Turnstile check on the contact endpoint. A cron trigger refreshes the GitHub snapshot every six hours.

One Worker. Static pages never touch it.

Drawn from wrangler.jsonc and worker/index.ts in this site's repository.

Security

Privacy

No cookies, and no analytics service. The page-view counter keeps one number per day for each combination of page, referring site, country and network (an internet provider or a university, say), and nothing else: no IP address, no visitor ID, and no full referrer, only its host name. A network name can be a company's, so a row can show that someone at that company opened a page. It never shows who.

A browser's user agent is checked only to leave bots out of the count, and an IP address only to rate limit the counter. Neither is stored.

The contact form is off the page for now. If it comes back, it stores what you send, your name, email, company and message, plus your country and the time it arrived, so I can reply, and emails me a copy. Your IP address is used for the rate limit and the Turnstile check, and is never saved with your message.

Testing

The Worker's tests run inside workerd, the real Workers runtime, against a real local D1 database and KV store. Email, rate limits, Turnstile and GitHub are faked, so nothing talks to the network.

They test the abuse cases as well as the happy path: a missing origin, oversized and malformed bodies, a flood of messages, a Turnstile outage, forged admin tokens from the none algorithm to HS256 to an expired token, and a GitHub outage that must leave the last good snapshot in place.

The content is typed too. A project fact with no source fails the build, and on the case studies any sentence with a number in it needs a source. One command, npm run gate, runs the type checks, the tests and the build.

A second suite drives real browsers through every built page, served by the real Worker: console errors, the security policy, layout from phone to desktop, keyboard order, accessibility, and the rule that nothing is coloured until you hover. Both suites run in CI on every push to main.

Cost

Hosting costs $0 on Cloudflare's free plan, so the domain name is the only bill. Static pages are free and unlimited, and only the routes above ever run code.

Credits

The code lives in a private repository. I'm happy to walk through it.