Notes on standing up this blog: why the posts are datafiles, what the index can and can't see, and how the whole thing gets served on a custom domain.
This is the first post, so it may as well be about the thing it's running on.
There's no posts table here. Each post is a datafile sitting under a shared slug prefix:
nicksblog/posts/2026-07-30-hello-world
nicksblog/posts/2026-08-14-something-elseThe date prefix isn't decoration — it's the sort key. Listing that prefix in descending slug order gives newest-first for free, with no date parsing and no index to maintain. It also means a post's URL is stable the moment it's created.
Each one is bound to a post schema, which does double duty: it validates the JSON on save, and it generates the form I actually write in. The schema is the CMS. Get the field order right and you get a decent editor; get it wrong and you get a decent editor for the wrong thing.
The listing operation returns metadata only — slug, display name, description, timestamps. No bodies.
That's a constraint I initially read as a limitation and now think is the best part of the design. An index page that can't see post bodies stays cheap at ten posts and at ten thousand; the cost of rendering the front page doesn't grow with the archive behind it. The tradeoff is that the title and excerpt have to live in the datafile's metadata as well as its content — a small, deliberate duplication that buys a listing which never gets slower.
The detail pages aren't one endpoint per post. It's a single route with a parameter in the slug, and a transform that turns the path segment into workflow input. Three URL shapes, three behaviours:
The 404s get negatively cached at the edge, which sounds like a footgun and isn't: publish the missing post and the negative cache lapses on its own.
None of the above is visible from the outside. What's visible is a hostname, pointed at this app so its root serves the index and everything under it resolves against the same prefix.
Which is roughly the whole idea. The interesting parts should be the boring parts.