Server-Sent Events Patterns

Server-Sent Events (SSE) is a one-way streaming protocol over HTTP. The server pushes events to the client; the client reads them as they arrive. Compared to WebSockets, SSE is simpler and works through HTTP infrastructure — proxies, load balancers, CDNs — without special configuration.

For server-to-client streaming use cases, SSE is often the better choice than WebSockets. This page covers when and why.

The basics

SSE uses a long-lived HTTP response. The server writes events as they happen; the client (browser-native EventSource) parses them.

Server side:

GET /events
Accept: text/event-stream

Response (streaming, never closes):

data: {"order":"abc","status":"shipped"}

data: {"order":"def","status":"delivered"}

Client side:

const events = new EventSource('/events');
events.onmessage = (e) => {
    const data = JSON.parse(e.data);
    handle(data);
};

The connection stays open; the server writes new events; the client receives them in order.

Format

SSE format is line-oriented:

event: order_update
data: {"id":"123","status":"shipped"}
id: 42

event: order_update
data: {"id":"124","status":"pending"}
id: 43

Fields:

Empty line separates events.

Reconnection and resumption

EventSource automatically reconnects on disconnect. The client sends the last received event ID:

GET /events
Accept: text/event-stream
Last-Event-ID: 42

Server resumes from event 43. The reconnection logic is built-in to the browser EventSource — no client code needed.

The server must support Last-Event-ID for resumption to work; otherwise reconnection misses events.

When SSE wins over WebSockets

ConcernSSEWebSocket
DirectionServer → client onlyBidirectional
ProtocolHTTPCustom upgrade from HTTP
Auto-reconnectBuilt-inManual
Last-event resumptionBuilt-inCustom
Browser supportEventSource nativeWebSocket native
Proxy/CDN compatibilityExcellent (just HTTP)Variable
ComplexityLowHigher

For one-way streaming (notifications, live updates, AI streaming responses), SSE is simpler. WebSockets pay off when the client also needs to send data through the same channel.

Common use cases

Notifications

Push notifications from server to client. New messages, status changes, alerts.

Live updates

Stock tickers, sports scores, dashboards. Periodic updates pushed as they happen.

AI streaming

Modern LLM APIs (OpenAI, Anthropic) use SSE for streaming responses. The server emits tokens as the model generates them.

Progress updates

Long-running operations (uploads, batch processing, builds). Server emits progress events; client renders.

Specific patterns

Heartbeats

Long idle connections may be closed by proxies. Send a periodic comment line:

:keep-alive

The empty colon-line is a comment; clients ignore it. Keeps the connection alive without affecting the data stream.

Authentication

EventSource doesn't support custom headers. Two approaches:

For APIs requiring header-based auth, custom EventSource implementations or fetch+ReadableStream alternatives exist.

Backpressure

SSE doesn't have native backpressure. If the client can't keep up, events queue or are dropped depending on infrastructure.

For streams where every event must be delivered, consider:

Filtering

For multi-topic streams, the server filters per-client:

GET /events?topics=orders,shipments

Or use named events that clients can ignore:

events.addEventListener('orders', handleOrder);
events.addEventListener('shipments', handleShipment);

Limitations

Common failure patterns

Further Reading