2026.10.15 · 4 min · javascript · browsers · backend
SSE vs WebSockets: you probably need the one-way pipe.
If only the server talks (notifications, live scores, AI tokens), Server-Sent Events give you a live stream over one ordinary HTTP response, with reconnection built into the browser. WebSockets are for when the client talks back a lot too. The format, the reconnect, and the limits people get wrong. As of 2026.10.
"Real-time" makes people reach for WebSockets by default. That buys a separate protocol, extra work on the server and the load balancer, and reconnect logic you write yourself. Most real-time features only push one way: a notification, a score, the next token of an AI reply. For those, Server-Sent Events do the job over plain HTTP, and the browser handles reconnecting for you.
Watch the 60-second version on Instagram.
Two pipes
An SSE stream is a normal HTTP response with Content-Type: text/event-stream, always UTF-8, that simply stays open. The server keeps writing to it; the client keeps reading.
A WebSocket starts with an HTTP handshake (Sec-WebSocket-Key / Sec-WebSocket-Accept) and then stops speaking HTTP. From there it's its own two-way session.
Real-time is about latency, not direction. If the client only listens, it doesn't need a new protocol.
The whole format
The body is lines of text: data:, event:, id: and retry:, with a blank line between messages. A line starting with : is a comment, which servers often send as a keep-alive.
// browser
const es = new EventSource("/scores");
es.onmessage = (e) => render(e.data);HTTP/1.1 200 OK
Content-Type: text/event-stream
id: 42
data: {"home":2,"away":1}
es.close() stops it. That's the client.
Reconnecting is built in
When the connection drops, EventSource reconnects on its own. retry: sets how long it waits, in milliseconds. On the new request the browser sends a Last-Event-ID header holding the last id: it received, so the server can resume from the next event instead of starting over. (MDN doesn't spell out Last-Event-ID; the WHATWG spec does.)
There's one exception: auto-reconnect doesn't cover HTTP errors. A non-200 status or the wrong content type fails the connection for good, so a 401 or a 500 ends it. A 204 is how the server tells the client to stop.
SSE or WebSocket
| SSE | WebSocket | |
|---|---|---|
| Direction | server → client | both ways |
| Protocol | ordinary HTTP response | HTTP handshake, then its own |
| Reconnect | built in, resumes with Last-Event-ID | you write it |
| Data | text (UTF-8) | text or binary |
| Good for | notifications, live scores, AI tokens | chat, multiplayer |
The Claude API, for one, streams its responses as SSE. EventSource works in every modern browser, about 97% of global usage.
What people get wrong
- "Real-time means WebSockets." Only when the client talks back a lot. A one-way push needs nothing beyond HTTP.
- "SSE is limited to 6 connections." Only on HTTP/1.1, where you get at most 6 per browser and domain, shared across tabs (marked "Won't fix" in Chrome and Firefox). On HTTP/2 the stream limit is negotiated, with a default of 100.
- "I'll send the token in a header."
EventSourcetakes only a URL and{ withCredentials }: no method, no custom headers, always GET. Use cookies withwithCredentials, orfetch()and readresponse.bodyas a stream. Don't put tokens in the URL; they end up in logs. - "AI chat UIs use
EventSource." Usually not. They POST the prompt withfetchand read the SSE-formatted reply as a stream, becauseEventSourceis GET-only. - "It just works behind any proxy." Proxies and compression can buffer the stream. Flush each event, and send
:comments as keep-alives.
Two smaller ones: SSE is text only, so binary data needs a WebSocket or an encoding. And the WebSocket API has no backpressure, and an open socket can keep a page out of the bfcache.
On a client project, I'd start every server-push feature on SSE and move to WebSockets only once the client genuinely has to talk back at a high rate.
The short version
- SSE is one HTTP response with
Content-Type: text/event-streamthat stays open. new EventSource(url)reconnects by itself and resumes withLast-Event-ID; HTTP errors end it for good.EventSourceis GET-only with no custom headers; use cookies orfetchstreaming for auth.- The 6-connection limit is HTTP/1.1 only; HTTP/2 defaults to 100 streams.
- Reach for WebSockets when the client talks back a lot too: chat, multiplayer.
Sources: MDN, Using server-sent events · WHATWG HTML, Server-sent events · MDN, EventSource() constructor · MDN, WebSockets API · Claude docs, Streaming messages · caniuse, Server-sent events