← Learnings

The database is a folder

This site's database is a folder of HTML files in a git repository. That sentence gets raised eyebrows in 2026, so this article is the full argument: what a request actually costs here, what I get for free, what I knowingly gave up, and where the escape hatch is.

A request costs almost nothing, by construction

When you opened this page, no server of mine ran. There is no server of mine. The URL maps to a file; Cloudflare's edge cache handed you pre-computed bytes from a machine near you. Time-to-first-byte is a cache hit — tens of milliseconds — versus the 200–500ms a typical origin-plus-database render pays before the first byte moves. The site cannot go down on my account, cannot be slow under load, and a traffic spike costs me exactly nothing.

The rule that makes this possible: content here changes only when I publish, so everything is computed when I publish. Reading time, the RSS feed, the sitemap, the card grid, even the little plotter line above this article — all frozen into files by the publish engine. The browser draws; it doesn't derive.

What a commit buys that a database sells

  1. Transactions — a post, its card, the feed, and the sitemap change in one atomic commit. The site is never half-updated.
  2. Point-in-time recovery — the history is git history; rollback is a revert.
  3. An audit log — every publish is signed, dated, and diffable.
  4. Replication and backup — GitHub holds the data, Cloudflare serves copies of it from hundreds of locations.
  5. A schema — one denormalized record per post in a manifest, from which every listing surface is regenerated. No foreign keys, but also no drift.

Those are the features people install a database to get. A repository ships them as a side effect.

Moral

Request-time computation is the only thing that earns a server. Everything else is publish-time work wearing a costume.

What I gave up, on purpose

No request-time computation means no per-reader anything: no personalization, no view counters, no server-side search, and — the one that matters — no comments. Publishing takes about a minute of commit-and-deploy, so nothing goes live in seconds. Concurrent authors would collide in git; there's one of me, so that's a constraint I don't pay for. And listing pages grow linearly with posts — the static remedy is generated pagination, which the manifest makes a non-event.

Search sounds like it belongs on that list, but doesn't: an index built at publish time and queried in the browser holds comfortably into the thousands of posts. Static search is a solved problem; comments are not.

Comments will be a sidecar, not a migration

When comments come, the posts stay files. The plan is a deliberately boring standalone service — one worker and one small database that do comments and nothing else, keyed by the post's slug, rendered in a dialog on the page. The blast radius of that decision is one feature: if the comment service dies, the site doesn't notice. The content never migrates anywhere.

That's the whole architecture: files for everything that changes at publish time, a scoped sidecar for the first thing that truly changes at request time, and a hard rule for telling the two apart. Boring on purpose — and this page loaded before you finished reading the tab title.