Skip to main content
Server-Sent Events (SSE)lesson 3 of 3 · 2 min read

SSE vs WebSocket

One question decides it

Reduce this decision to a single question. Does your client need to send messages over the same channel, often?

If it does not, server-sent events wins on nearly every operational axis, and the set of genuinely two-way features is smaller than product specs make it sound.

Tally what you get for free. It is plain HTTP, so it travels through balancers and proxies with no protocol-specific configuration, and any platform that can stream a response can serve it. Reconnecting with resume is built into the browser. Your server code is a loop writing to a response.

Compare what WebSocket asks for. Upgrade support at every hop, heartbeats you write yourself, reconnect and resume you write yourself, and its own security review, because it steps outside normal HTTP rules.

Resist overweighting client-to-server traffic, because that is where people get this wrong. A chat user sends a message every few dozen seconds, and a plain POST alongside the stream handles that perfectly well, riding the same underlying connection on HTTP/2 anyway.

When WebSocket earns it

Reserve WebSocket for genuinely high-frequency upstream traffic. Cursor positions in a shared editor at 20 a second, game inputs, audio. There the overhead of a request per message becomes real and two-way frames earn their complexity. Binary payloads and sub-100 millisecond round trips point the same way.

Recognise the tell that a team chose wrong, because it almost always points one direction. A WebSocket deployment where the client sends nothing but heartbeats is a server-sent events workload paying WebSocket's operational bill.

Look at how token streaming settled this in public. The large language model products stream their completions over server-sent events. That is pure server-to-client push at high frequency, and none of them found a reason to upgrade it.

the shape of it
ServerOne long responseserver talksTwo-way socketclient talks tooClient1. writes events2. browser resumes3. frames4. frames back
step 1 of 4
If the client only listens, the simpler channel already handles reconnection.

Worked example

A fintech team specs real-time notifications and defaults to WebSocket because the ticket said real-time. Rohan, reviewing the design, asks what the client ever sends over the channel: the answer is mark-as-read, which happens maybe five times a day per user and already exists as a REST endpoint. He reworks the design as one SSE endpoint fed by their existing Kafka topic, roughly 200 lines of server code, shipped in three days against the two sprints estimated for the WebSocket version with its sticky sessions and custom reconnect protocol. Six months in, the stream serves 60,000 concurrent connections from four pods, notifications land in under 400 ms end to end, and the mark-as-read POST works exactly as it always did.

Server-Sent Events (SSE): wrapping up

In the real world

  • 01OpenAI's API streams completions as SSE when stream: true is set, sending data: chunks and a final data: [DONE], and the ChatGPT web app renders tokens from the same kind of stream.
  • 02Anthropic's Claude API streams responses over SSE with named event types like message_start and content_block_delta, so clients switch on the event: field of each frame.
  • 03LinkedIn's instant messaging delivers over Server-Sent Events from Play/Akka servers, an architecture their engineering blog describes holding hundreds of thousands of concurrent open connections.
  • 04The Mercure protocol, standard in the Symfony ecosystem, builds an entire pub/sub hub on SSE so PHP apps can push updates without running a WebSocket stack.
  • 05Twitter's original streaming API delivered the firehose over long-lived chunked HTTP responses, the same never-ending-response idea SSE later standardized for browsers.

Questions people ask

Can the client send data back over an SSE connection?

No, the stream is strictly server to client. Clients send data with ordinary separate requests, usually POSTs, which is fine for occasional actions like sending a chat message or acknowledging a notification. If the client needs to send many messages per second, that is the signal to consider WebSocket instead.

Does SSE work through load balancers and proxies?

Yes, better than WebSocket, because it is a normal HTTP response. The two things that break it are response buffering, fixed with proxy_buffering off or an X-Accel-Buffering: no header, and idle timeouts, handled by sending a comment line as a heartbeat every 15 to 30 seconds. Serve it over HTTP/2 to avoid the browser's 6-connection-per-domain limit on HTTP/1.1.

How many SSE connections can one server hold?

On an event loop runtime, tens of thousands per box is routine, since an idle connection costs a few tens of kilobytes and a file descriptor. The practical limits are the same as any push fleet: fan-out architecture for getting events to the right server, file descriptor limits, and handling the reconnect wave when a server restarts.

Quick review

Client opens single HTTP connection with Accept:
text/event-stream. Server keeps it open and pushes events
Unidirectional:
server → client only. Client cannot send messages over the same connection
Event format:
'data: {json}\n\nid: 123\nevent: update\n\n'. Built-in reconnection with Last-Event-ID header
Browser EventSource API:
const es = new EventSource('/stream'); es.onmessage = e => console.log(e.data)
Built on HTTP/1.1. Works through proxies and firewalls naturally. No protocol upgrade needed
Connection limit:
browsers limit 6 HTTP/1.1 connections per domain. HTTP/2 removes this limit
Scale SSE:
stateful connection per user. Need sticky sessions or a pub/sub relay (Redis) to broadcast to all server instances
the trade-off

Unidirectional only. If client needs to send data too, combine SSE with separate POST requests or switch to WebSocket.

in the room

Live dashboards, news tickers, notifications, stock prices, any scenario where server pushes and client only reads.