Make live state reliable
Repair with snapshots
Treat the event stream as a sequence of changes and the snapshot as the source for rebuilding current state.
An event log cannot grow forever. At some point you drop old entries, restart the process, or deploy new code. Clients need a snapshot that says “start here” with a known position.
The stream carries deltas. The snapshot route carries truth.
Snapshot carries continuation
{
"incidents": [
{ "id": 41, "status": "monitoring" },
{ "id": 42, "status": "resolved" }
],
"latestEventId": "evt_90",
"generatedAt": "2026-09-08T15:00:00.000Z"
}
After a server restart, a stale client loads this response, repaints the board, and opens SSE with Last-Event-ID: evt_90. Events after evt_90 apply on top. Events before it are irrelevant.
When to discard local state
Document triggers that wipe the projection:
resync.requiredon the stream- snapshot
latestEventIdbehind the client’s last applied id - schema version bump the client no longer understands
async function resync() {
const snapshot = await fetch('/api/incidents').then(r => r.json())
replaceBoard(snapshot.incidents)
lastEventId = snapshot.latestEventId
reconnectStream(lastEventId)
}
Bounded replay plus snapshot
We keep five hundred events in memory for the tutorial. Production might keep fewer. When replay fails, fall back once, not in a loop.
Restart the server, expire the replay window, and recover a client that slept through the outage. The final board must match a fresh GET /api/incidents call.
Consistency note
The snapshot must reflect the same rules as streamed events. If the stream hides resolved incidents after seven days, the snapshot should too. Otherwise resync paints a different board than live updates.
Include generatedAt in the snapshot JSON so the UI can show freshness even before the live channel reconnects.
Treat snapshots as cheap. Regenerate them on a schedule if replay retention is small. Clients prefer one fast GET over waiting for a long replay chain.
Try this on your own project: delete your in-memory replay buffer, hit the snapshot route, and prove a client catches up using snapshot plus later events only.
Lesson completed