HomeJournalThis post

Build a resumable Server-Sent Events client

A production SSE client needs monotonic event IDs, replay retention, duplicate-safe reducers, heartbeats, visible recovery, and snapshot fallback.

JP
JP Casabianca
Designer/Engineer · Bogotá

Server-Sent Events look simple because the browser reconnects for you.

That convenience is only the beginning. A useful live surface still needs stable event identity, replay retention, duplicate tolerance, connection status, authentication behavior, heartbeat strategy, proxy configuration, and a way to recover when the requested history no longer exists.

The WHATWG HTML standard defines EventSource, text/event-stream, reconnection, event IDs, and the Last-Event-ID request header. We will build the product contract around those primitives.

The result is a client that can lose its connection, resume from the last applied event, reject duplicates, and replace its state from a snapshot when replay is no longer safe.

Live should mean recoverable, not merely moving.

01 · ConnectOpen a bounded stream

Send identity and cursor through supported channels, expose connecting state, and start a heartbeat clock.

02 · ApplyAdvance monotonically

Validate schema, reject duplicate or older IDs, update product state, and persist the last committed cursor.

03 · RecoverReplay or replace

Reconnect with Last-Event-ID; replay retained events or fetch a snapshot when the cursor is outside history.

Figure 1: A resumable stream separates transport, replay, and product state.

Define the live-state promise

Start by naming which product state should become current, how quickly, and what temporary staleness the user can tolerate.

I would pressure-test that decision with four questions:

  • Which state is streamed?
  • How stale may it become?
  • Does order matter?
  • What can the user still do while disconnected?

The failure mode here is starting with EventSource code before defining product consequence. In live product surfaces where one-way server updates must survive proxy buffering, tab suspension, network changes, duplicate delivery, reconnects, deployment restarts, stale cursors, authentication expiry, and user-visible gaps, that can hide the exact boundary a reviewer or teammate needs to understand. My working artifact would be a one-page live-state contract. I want it close enough to the implementation that it can change the work, not created afterward to decorate the story.

The result I would look for is a transport design matched to the actual experience. That is a narrower claim than saying the whole system improved, but it is also one I can verify and defend.

In practice, I would put a one-page live-state contract beside the question “Which state is streamed?” before the first implementation review. The next pass would use “How stale may it become?” to test the boundary, then “Does order matter?” to expose the state most likely to be missed. I would keep “What can the user still do while disconnected?” for the release check because it asks whether the decision still holds outside the ideal path. The work is ready to move when the artifact can explain the choice and the observed result supports a transport design matched to the actual experience.

Create a typed event envelope

Every event should carry a stable ID, type, schema version, occurred time, and payload shape that can evolve deliberately.

The practical review starts here:

  • Is the ID monotonic?
  • Which clock is authoritative?
  • How is schema versioned?
  • Can the reducer reject unknown types?

Those questions keep sending arbitrary JSON fragments with no compatibility rule from becoming the default. I would capture the decision in a versioned event-envelope schema, then use it while the work is still cheap to change. For reliable browser event streaming, the artifact should make ownership, constraint, and next action visible without requiring a private explanation.

Success would look like events that can be validated and replayed. If I cannot point to that evidence, I have a direction, not a finished decision.

The implementation move is to make a versioned event-envelope schema part of the working surface. I would use it to answer “Is the ID monotonic?” while scope is still flexible, and “Which clock is authoritative?” before code or content becomes expensive to unwind. During QA, “How is schema versioned?” and “Can the reducer reject unknown types?” become concrete checks rather than discussion prompts. That sequence turns reliable browser event streaming into something the team can operate and gives me a specific outcome to report: events that can be validated and replayed.

  1. HealthyUpdates are current

    The stream is open, heartbeat is recent, and the applied cursor is within the server freshness window.

  2. RecoveringContent may be stale

    Keep usable state, show a quiet freshness cue, back off reconnects, and avoid duplicate user actions.

  3. ResyncedSnapshot restored

    Replace derived state atomically, announce recovery when consequential, and resume from the new cursor.

Figure 2: Connection state should be visible without becoming noisy.

Emit valid event streams

The server must set the correct content type, flush complete event records, avoid transformations, and keep intermediaries from buffering the experience into batches.

Before implementation, I would answer:

  • Is text/event-stream set?
  • Do records end with a blank line?
  • Which proxy buffers?
  • How is compression handled?

The artifact is a server and proxy header fixture. Its job is to expose the tradeoff early enough that design, engineering, support, or product can disagree with something concrete. The common trap is testing only against a direct local server; it moves uncertainty downstream and makes the final interface carry a problem the system never resolved.

For me, the useful receipt is incremental delivery through the deployed network path. That connects a resumable EventSource loop with monotonic event IDs, replay boundaries, explicit connection state, and a snapshot fallback to an observable result instead of a process claim.

I would test this with one typical case and one boundary case. The typical case should make “Is text/event-stream set?” easy to answer. The boundary should force a decision about “Do records end with a blank line?” and “Which proxy buffers?.” I would record both in a server and proxy header fixture, including the part that stayed unresolved after the first pass. The final check, “How is compression handled?,” is where the artifact earns its place: it either supports incremental delivery through the deployed network path, or it shows exactly why another iteration is needed.

Persist the committed cursor

The cursor should advance only after the event has been validated and applied, so reconnect never skips work the UI failed to commit.

I would use these prompts during the working review:

  • When is an event committed?
  • Where is the cursor stored?
  • Can two tabs compete?
  • What happens after refresh?

If the team slips into saving the received ID before state application succeeds, the product can still look complete while its operating rule stays ambiguous. I would make a cursor commit rule beside the reducer the shared reference and keep it small enough to update as evidence changes.

The standard is resume from the last durable client state. That tells me whether the decision helped the product, not merely whether the document was completed.

The working sequence is small: draft a cursor commit rule beside the reducer, review it against “When is an event committed?,” implement the narrowest useful path, and then return with evidence for “Where is the cursor stored?.” I would use “Can two tabs compete?” to inspect product consequence and “What happens after refresh?” to decide whether the result is stable enough to ship. This keeps saving the received ID before state application succeeds visible as a known risk and makes resume from the last durable client state the release receipt rather than a hopeful conclusion.

SignalDecisionWorking note
DuplicateIgnore safelyAn event ID at or below the committed cursor must not repeat notifications, counters, or side effects.
GapStop and inspectA noncontiguous required sequence should trigger replay or snapshot rather than pretending the missing change is harmless.
ExpiredReplace stateWhen retention no longer contains the cursor, fetch a versioned snapshot and resume after its high-water mark.
Figure 3: Event identity controls the failure behavior.

Make reducers idempotent

Reconnection and intermediary behavior can repeat events, so applying the same event twice must not duplicate visible or external effects.

I would pressure-test that decision with four questions:

  • Can counters double?
  • Can a toast repeat?
  • Is entity revision checked?
  • Which side effect belongs elsewhere?

The failure mode here is assuming the transport provides exactly-once delivery. In live product surfaces where one-way server updates must survive proxy buffering, tab suspension, network changes, duplicate delivery, reconnects, deployment restarts, stale cursors, authentication expiry, and user-visible gaps, that can hide the exact boundary a reviewer or teammate needs to understand. My working artifact would be an idempotent reducer test table. I want it close enough to the implementation that it can change the work, not created afterward to decorate the story.

The result I would look for is duplicate-safe product state. That is a narrower claim than saying the whole system improved, but it is also one I can verify and defend.

In practice, I would put an idempotent reducer test table beside the question “Can counters double?” before the first implementation review. The next pass would use “Can a toast repeat?” to test the boundary, then “Is entity revision checked?” to expose the state most likely to be missed. I would keep “Which side effect belongs elsewhere?” for the release check because it asks whether the decision still holds outside the ideal path. The work is ready to move when the artifact can explain the choice and the observed result supports duplicate-safe product state.

Design replay retention

The server needs a bounded history keyed by event ID and a clear response when the requested cursor is older than retained events.

The practical review starts here:

  • How long is history kept?
  • Can events be partitioned by user?
  • What signals an expired cursor?
  • What is the snapshot boundary?

Those questions keep holding an unbounded in-memory event array from becoming the default. I would capture the decision in a replay retention and expiry policy, then use it while the work is still cheap to change. For reliable browser event streaming, the artifact should make ownership, constraint, and next action visible without requiring a private explanation.

Success would look like predictable recovery across restarts and long absences. If I cannot point to that evidence, I have a direction, not a finished decision.

The implementation move is to make a replay retention and expiry policy part of the working surface. I would use it to answer “How long is history kept?” while scope is still flexible, and “Can events be partitioned by user?” before code or content becomes expensive to unwind. During QA, “What signals an expired cursor?” and “What is the snapshot boundary?” become concrete checks rather than discussion prompts. That sequence turns reliable browser event streaming into something the team can operate and gives me a specific outcome to report: predictable recovery across restarts and long absences.

Add heartbeats and backoff

Heartbeats expose half-open connections, while capped exponential backoff with jitter prevents a recovering service from receiving synchronized reconnect storms.

Before implementation, I would answer:

  • What heartbeat interval fits?
  • When is a connection stale?
  • How large can backoff grow?
  • When should it reset?

The artifact is a heartbeat and reconnect state machine. Its job is to expose the tradeoff early enough that design, engineering, support, or product can disagree with something concrete. The common trap is reconnecting every client immediately on every error; it moves uncertainty downstream and makes the final interface carry a problem the system never resolved.

For me, the useful receipt is faster recovery without a thundering herd. That connects a resumable EventSource loop with monotonic event IDs, replay boundaries, explicit connection state, and a snapshot fallback to an observable result instead of a process claim.

I would test this with one typical case and one boundary case. The typical case should make “What heartbeat interval fits?” easy to answer. The boundary should force a decision about “When is a connection stale?” and “How large can backoff grow?.” I would record both in a heartbeat and reconnect state machine, including the part that stayed unresolved after the first pass. The final check, “When should it reset?,” is where the artifact earns its place: it either supports faster recovery without a thundering herd, or it shows exactly why another iteration is needed.

Handle authentication deliberately

Native EventSource limits custom headers, so cookie, signed URL, token refresh, and stream restart choices must be explicit and safe.

I would use these prompts during the working review:

  • How is the stream authorized?
  • Can credentials leak in URLs?
  • When does auth expire?
  • How does renewal preserve the cursor?

If the team slips into placing a long-lived bearer token in a query string, the product can still look complete while its operating rule stays ambiguous. I would make an SSE authentication decision record the shared reference and keep it small enough to update as evidence changes.

The standard is recoverable authorization without credential sprawl. That tells me whether the decision helped the product, not merely whether the document was completed.

The working sequence is small: draft an SSE authentication decision record, review it against “How is the stream authorized?,” implement the narrowest useful path, and then return with evidence for “Can credentials leak in URLs?.” I would use “When does auth expire?” to inspect product consequence and “How does renewal preserve the cursor?” to decide whether the result is stable enough to ship. This keeps placing a long-lived bearer token in a query string visible as a known risk and makes recoverable authorization without credential sprawl the release receipt rather than a hopeful conclusion.

Build connection-state UI

The interface should keep last-known state usable, disclose meaningful staleness, and reserve interruption for moments where user decisions would be unsafe.

I would pressure-test that decision with four questions:

  • Which status is visible?
  • Is stale content still actionable?
  • When is an announcement needed?
  • What manual recovery exists?

The failure mode here is showing a permanent red offline banner for every brief reconnect. In live product surfaces where one-way server updates must survive proxy buffering, tab suspension, network changes, duplicate delivery, reconnects, deployment restarts, stale cursors, authentication expiry, and user-visible gaps, that can hide the exact boundary a reviewer or teammate needs to understand. My working artifact would be a connecting/recovering/stale/resynced UI matrix. I want it close enough to the implementation that it can change the work, not created afterward to decorate the story.

The result I would look for is calm and truthful live-state feedback. That is a narrower claim than saying the whole system improved, but it is also one I can verify and defend.

In practice, I would put a connecting/recovering/stale/resynced UI matrix beside the question “Which status is visible?” before the first implementation review. The next pass would use “Is stale content still actionable?” to test the boundary, then “When is an announcement needed?” to expose the state most likely to be missed. I would keep “What manual recovery exists?” for the release check because it asks whether the decision still holds outside the ideal path. The work is ready to move when the artifact can explain the choice and the observed result supports calm and truthful live-state feedback.

Test interruption as the happy path

Verification should cut networks, duplicate IDs, create gaps, expire replay, restart servers, suspend tabs, rotate auth, and observe recovery receipts.

The practical review starts here:

  • Can failures be scripted?
  • Does the cursor advance correctly?
  • Are duplicates invisible?
  • Does snapshot recovery preserve truth?

Those questions keep calling one open event a successful live feature from becoming the default. I would capture the decision in an SSE interruption test harness, then use it while the work is still cheap to change. For reliable browser event streaming, the artifact should make ownership, constraint, and next action visible without requiring a private explanation.

Success would look like evidence that the stream survives production-shaped failure. If I cannot point to that evidence, I have a direction, not a finished decision.

The implementation move is to make an SSE interruption test harness part of the working surface. I would use it to answer “Can failures be scripted?” while scope is still flexible, and “Does the cursor advance correctly?” before code or content becomes expensive to unwind. During QA, “Are duplicates invisible?” and “Does snapshot recovery preserve truth?” become concrete checks rather than discussion prompts. That sequence turns reliable browser event streaming into something the team can operate and gives me a specific outcome to report: evidence that the stream survives production-shaped failure.

What I would show in the work

The public version needs evidence from the work itself. For this topic, the first five artifacts I would reach for are:

  • a one-page live-state contract
  • a versioned event-envelope schema
  • a server and proxy header fixture
  • a cursor commit rule beside the reducer
  • an idempotent reducer test table

I would not publish all five at equal weight. One should orient the reader, one should reveal the hardest tradeoff, and one should prove the result. The others can live in a downloadable note or appear as supporting frames. That edit matters because a resumable EventSource loop with monotonic event IDs, replay boundaries, explicit connection state, and a snapshot fallback becomes harder to understand when every process detail is treated as equally important.

I would also show one rejected direction. The useful version is specific: which option looked attractive, which constraint made it wrong, and what evidence supported the narrower choice. That gives an engineering manager something real to question and keeps the case study from reading like the final answer was obvious from the beginning.

resumable-sse-client.ts
# event
id: 18442; type: order.updated
JSON payload carries schema version and entity revision; UI reducer is idempotent by event ID.

# retry 1s to 30s with jitter Reset after stable open; pause when offline; renew auth on 401-compatible control path; one stream per tab policy.

# fallback GET snapshot?after=18442 Server returns replay, or snapshot plus cursor 18510; client commits replacement before reopening stream.

Figure 4: The client contract keeps reconnection deterministic.

Resource path

The practical follow-up I would build is an SSE starter with event schema, retry policy, last-event cursor, replay store, heartbeat, auth renewal, connection-state UI, duplicate handling, observability, load test, and fallback snapshot. I am treating that as a resource backlog item, not pretending the adjacent downloads below are the same artifact. The related cards cover useful pieces of the workflow today; this specific file should only be published when its examples, fields, and instructions are complete.

The first version should stay concise: context, constraint, decision, evidence, owner, and follow-up. Its value would come from helping someone repeat this exact review, not from adding another generic PDF to the site.

Review checklist

The article-specific review questions are:

  • Which state is streamed?
  • Is the ID monotonic?
  • Is text/event-stream set?
  • When is an event committed?
  • Can counters double?
  • How long is history kept?
  • What heartbeat interval fits?
  • How is the stream authorized?
  • Which status is visible?
  • Can failures be scripted?

I would add two editorial checks before publishing: can a recruiter find the point in the first minute, and can an engineer trace at least one claim to an implementation or production receipt? If either answer is no, the article needs another edit.

Implementation notes

For reliable browser event streaming, I would write the implementation note before polish. It would name the changed surface, source of truth, owner, failure boundary, and verification path. Those details prevent the principle from floating above the actual code or operational workflow.

The proof signals I care about are specific to this article:

  • predictable recovery across restarts and long absences
  • faster recovery without a thundering herd
  • recoverable authorization without credential sprawl
  • calm and truthful live-state feedback
  • evidence that the stream survives production-shaped failure

I would choose two or three of those signals for the first release rather than instrumenting everything. The strongest pair usually combines one direct behavior check with one operating check: a route and a data query, a keyboard path and a support state, a handler replay and a reconciliation result, or a migration count and a rendered screen.

The follow-up belongs in the note before shipping. It should say what remains temporary, what evidence would trigger another pass, and who owns that decision. That is how the first version stays intentionally narrow without making the boundary invisible.

Case-study packaging

I would structure the case-study version around the four visual lessons already established:

  • A resumable stream separates transport, replay, and product state.
  • Connection state should be visible without becoming noisy.
  • Event identity controls the failure behavior.
  • The client contract keeps reconnection deterministic.

The opening frame explains the product pressure. The middle two show the decision moving through the system. The last frame is the receipt: what was checked, what held, and what remained unresolved. That order lets the reader move from product judgment into implementation detail without reconstructing the whole project first.

I would include one caveat tied to live product surfaces where one-way server updates must survive proxy buffering, tab suspension, network changes, duplicate delivery, reconnects, deployment restarts, stale cursors, authentication expiry, and user-visible gaps: a data limit, rollout boundary, unsupported state, external dependency, or result that is still directional. A precise caveat makes the evidence easier to trust because it shows where the claim stops.

The final test is whether the page creates a better conversation. If the artifact helps someone ask a sharper question about product judgment, implementation detail, or release proof in a live interview, it belongs in the story.

Interview angle

In an interview, I would explain this through a resumable EventSource loop with monotonic event IDs, replay boundaries, explicit connection state, and a snapshot fallback. The story should start with the product pressure, then move into the system constraint, the artifact, and the proof. That order keeps the answer grounded. It also gives the interviewer several places to go deeper: data, frontend architecture, design systems, support, migration, accessibility, or release process.

The strongest version of the answer includes a tradeoff. I want to be able to say what I chose, what I left alone, and how I knew the work helped. That is more credible than presenting every project as a clean win.

The hiring signal

A resumable SSE client is a hiring signal because it shows I can turn a deceptively small browser API into a production protocol with recovery, product states, and observable guarantees.

That is the level I want this site to communicate. The work should show taste, but it should also show operating judgment. It should make me look like someone who can enter a real product system, understand the messy middle, ship the useful version, and leave enough proof for the next person to trust it.

Companion artifacts

Use this after reading.

Practical downloads and templates that turn the article into something you can bring into a product review, implementation pass, or agent workflow.

DownloadJun 2026

Front-End State Recipes

Reusable recipes for optimistic actions, loading, empty, error, data-transition, and disabled-control states.

FrontendStatesUX
View details
RepoJun 2026

Agent-Ready API Spec Template

An OpenAPI and Postman starter template for APIs that AI agents can discover, call, and recover from safely.

OpenAPIPostmanAI agents
View details
DownloadJun 2026

UI PR Risk Review Checklist

A merge-readiness checklist for product intent, states, accessibility, visual durability, and UI implementation risk.

UI reviewQAFrontend
View details