01 · The bug that started it
Content was waiting on a deployment.
The public site was static HTML. Publishing in the CMS started a frontend build, and a Worker marked the publication successful once the build finished. Then a social handle in the footer changed. The build passed and staging showed the new handle, but the publication record still said pending. Anything that read "the last successful publication" kept serving the old handle. A one-line content edit now depended on a build pipeline.
02 · Drafts, snapshots, one active version
Editors save as often as they like. Visitors only ever see one version.
- A draft is private. Saving it doesn't create a version and doesn't change the site.
- Publishing validates the draft, copies it into an immutable snapshot and switches the active version, all in one database transaction.
- The previous version stays in history, ready for review or rollback.
- Version A: live before you publish B, then history, ready for rollback.
- Version B: a draft nobody sees before you publish it, then live.
03 · Read at request time
The site reads the live snapshot on every request.
CMS-backed pages render on demand in Astro on Cloudflare Workers. An anonymous read function returns only the active snapshot, never a draft. There is no local fallback: if a publication is missing, the request fails loudly instead of quietly serving old fixture data. Fixtures are still there, but only as test data.
-- Illustrative, not the production function
begin;
insert into snapshots (unit, payload, draft_hash)
values ('site', build_payload('site'), :reviewed_draft_hash)
returning id into new_id;
update publications set active_snapshot_id = new_id where unit = 'site';
commit;04 · Review before it goes live
You publish exactly what you reviewed, or nothing.
The CMS flow is "Save draft → Review & publish", with a before/after view of the whole unit. The publish transaction locks the source tables in a fixed order. It then checks two things: that the draft still matches the one you reviewed, and that the live version is still the one you compared against. If the result of a publish is unknown, for example after a network drop, the CMS checks the live version's hash before it offers a retry.
05 · One trust boundary
The browser never holds a server credential.
Only a trusted Worker holds the database service role. The CMS talks to that Worker, and visitors read through the anonymous function. A leaked browser bundle exposes nothing that can write.
06 · When I'd still choose static
This site does the opposite.
My portfolio is a static export. I edit content locally and deploy. That works because there's one editor, changes are rare, and runtime cost is zero. With several editors who expect "publish" to mean "live", the rebuild isn't a feature. It's the bug.
