A rebuild of an earlier Next.js live blog, reworked to put the reader first. Each story is a single Durable Object addressed by name, so every author and reader worldwide lands on the same instance — which turns total ordering into an autoincrementing integer, fan-out into a loop over local sockets, and read-after-write into a non-problem. Posts live in the object's embedded SQLite database with two separate sequences: one fixes chronological order so a correction to an old post does not resurface it, and one advances on every mutation so a reconnecting reader replays creations, edits, and deletions in a single query. Astro reads the object over RPC during server rendering, placing the whole feed in the first HTML byte, and ships one plain-TypeScript island; Preact loads only on the author console, which readers never request. Authoring is gated by a capability token carried in the URL fragment, so it never reaches server logs, and only its SHA-256 digest is persisted. Abuse guardrails are split deliberately: Rate Limiting bindings reject junk at the edge keyed on the token digest rather than an IP, while authoritative totals are counted in SQL inside the object. A 180-line markdown renderer that escapes input before emitting any tags replaces DOMPurify, keeping jsdom out of the Durable Object entirely.
Reader (19.7KB island) Desk (Preact console)
│ GET /blog/:id │ writes + X-Edit-Token
▼ ▼
Cloudflare Worker (src/worker.ts)
├── /api/blogs/* answered before Astro
│ └── 101 upgrade must pass through untouched
├── Rate Limiting bindings ── keyed on token digest
└── Astro SSR ── RPC getSnapshot(), no HTTP hop
▼
LiveBlog Durable Object (one per story)
├── capability check ──── SHA-256 digest, constant time
├── created_seq ───────── stable feed order
├── seq ───────────────── resume cursor for ?since=N
├── SQLite ────────────── posts + meta, authoritative counts
├── hibernatable sockets presence + fan-out
└── alarm ─────────────── 30-day idle cleanup
▼
Feed in the first HTML byte, then deltas over WSS