Server-Sent Events, or SSE, let a server push a continuous stream of updates to a browser over a single, ordinary HTTP response, with no upgrade handshake, no new port, and no client library beyond what the browser already ships. For the large share of real-time features that only need to go one direction, from server to client, SSE delivers most of the benefit of WebSockets with a fraction of the moving parts. It is not a WebSocket replacement for chat or multiplayer games, but for notifications, live dashboards, and progress streams it is usually the simpler, more reliable choice.
How it works
An SSE response is just HTTP with a specific content type and a stream that never closes:
GET /events HTTP/1.1
Accept: text/event-stream
HTTP/1.1 200 OK
Content-Type: text/event-stream
Cache-Control: no-cache
Connection: keep-alive
data: {"status": "processing"}
data: {"status": "complete"}
id: 42
The client side is a few lines:
const source = new EventSource("/events");
source.onmessage = (event) => {
const payload = JSON.parse(event.data);
updateUI(payload);
};
source.onerror = () => {
// EventSource retries automatically; this fires on each drop
console.log("connection lost, browser is reconnecting");
};
EventSource handles reconnection on its own. If the connection drops, the browser retries after a server-suggested delay, set with a retry: field and defaulting to a few seconds, and sends the last received event ID back in a Last-Event-ID header so the server can resume from where it left off.
SSE vs the alternatives
| Factor |
SSE |
WebSockets |
Long polling |
| Direction |
Server to client only |
Bidirectional |
Request and response |
| Protocol |
Plain HTTP |
Upgrade to ws:// |
Plain HTTP |
| Auto-reconnect |
Built into the browser |
Manual |
Manual |
| Proxy and firewall friendliness |
High |
Variable |
High |
| Binary data |
No, text only |
Yes |
Yes |
| Best for |
Notifications, live feeds, progress |
Chat, gaming, collaboration |
Simple, infrequent updates |
When to reach for it
- The client only receives, never sends mid-stream. Order status, build progress, live scores, and stock tickers are all one-way. SSE fits exactly.
- You want reconnection handled for you.
EventSource retries and resumes automatically; a hand-rolled WebSocket client has to reimplement that.
- You are behind infrastructure that is picky about upgrades. Some proxies and older load balancers mishandle the WebSocket upgrade; plain HTTP streaming avoids the problem entirely.
- You need bidirectional, low-latency messaging. That is not SSE's job, see long polling vs WebSockets for the bidirectional comparison.
- You need to push binary data. SSE is text-only, UTF-8 encoded, so use WebSockets or a chunked binary stream instead.
Common mistakes
Not setting Cache-Control: no-cache. Some intermediary caches and browsers will buffer or cache the stream without this header, delaying or breaking delivery.
Forgetting the double newline between events. The SSE wire format requires a blank line to terminate each event. Omitting it merges events or stalls parsing on the client.
Ignoring Last-Event-ID on the server. The whole point of automatic reconnection is resuming where the client left off. If the server does not honor Last-Event-ID and replay missed events, reconnects silently drop data.
Running SSE behind a proxy with a short idle timeout. A proxy that closes idle connections after thirty seconds will cut the stream even though nothing is wrong. Send periodic comment lines as a keepalive to keep it open.
FAQ
Is SSE actually simpler than WebSockets?
Yes, for one-way updates. There is no upgrade handshake, no separate client library, and the browser handles reconnection and event IDs natively through EventSource.
Can SSE send data from the client to the server?
No. SSE is one-way. Pair it with regular HTTP requests, such as a normal POST, for the rare cases where the client also needs to send data.
Does SSE work over HTTP/2?
Yes, and it works well. HTTP/2 multiplexing means an SSE stream does not consume a dedicated TCP connection the way it can under HTTP/1.1, which previously limited the number of concurrent SSE streams a browser could hold to one origin.
How is SSE different from long polling?
Long polling opens a new request after every response; SSE keeps one response open indefinitely and keeps writing to it. SSE has lower overhead and lower latency for frequent updates. See long polling vs WebSockets for the fuller comparison.
Where to go next