Server-Sent Events: A Simpler Alternative to WebSockets for One-Way Streams
Not every real-time feature needs a bidirectional socket. If the server is the only side sending updates, Server-Sent Events do the job with a fraction of the moving parts.
Published July 6, 2026Server-Sent Events (SSE) let a server push a stream of updates to a browser over a single, long-lived HTTP connection. The client opens it once with the EventSource API, and the connection stays open, with the server writing new messages to it whenever it has something to send. There's no handshake beyond a normal HTTP request, no separate protocol, and no special server infrastructure — it's plain HTTP with a response that never finishes, formatted as a specific text stream the browser knows how to parse.
What the wire format looks like
An SSE response sets Content-Type: text/event-stream and then writes plain text lines, each event separated by a blank line:
HTTP/1.1 200 OK
Content-Type: text/event-stream
Cache-Control: no-cache
Connection: keep-alive
data: {"price": 104.22, "symbol": "ACME"}
data: {"price": 104.30, "symbol": "ACME"}
event: alert
data: {"message": "Price threshold crossed"}
Each block starting with data: becomes a message event on the client. An optional event: line lets you name a custom event type so the client can listen for specific kinds of updates separately from the default message event. There's also an id: field that lets the client resume from the last message it saw if the connection drops, and a retry: field that tells the browser how long to wait before reconnecting.
The client side is almost nothing
const stream = new EventSource('/updates');
stream.onmessage = (event) => {
const data = JSON.parse(event.data);
updatePriceDisplay(data);
};
stream.addEventListener('alert', (event) => {
showAlert(JSON.parse(event.data));
});
stream.onerror = () => {
console.log('Connection lost, browser will retry automatically');
};
That automatic retry is the detail that makes SSE genuinely low-maintenance compared to raw sockets. If the connection drops — a proxy timeout, a network blip, a server restart — the browser reconnects on its own using the retry interval from the stream, and if the server sent an id: field with each event, it includes a Last-Event-ID header on reconnect so the server can replay anything the client missed. Building that same resilience on top of raw WebSockets is code you have to write yourself.
Why not just use WebSockets for everything
The WebSockets protocol gives you a full-duplex channel: both sides can send messages at any time, framed as a binary or text protocol distinct from HTTP after the initial upgrade handshake. That's the right tool when the client needs to send frequent messages back — a chat app, a multiplayer game, a collaborative editor. But for a stock ticker, a live dashboard, a notification feed, or a progress bar on a long-running job, the client isn't sending anything after the initial request. Reaching for a WebSocket in that situation means running a separate protocol, handling your own reconnection and message-ID tracking, and often needing dedicated infrastructure (a WebSocket-aware load balancer, sticky sessions) that a plain HTTP endpoint doesn't require. SSE rides on ordinary HTTP, which means it works through standard proxies and load balancers, gets the same caching and auth headers as any other request, and shows up in your existing HTTP status code handling and logging without special-casing.
Real limitations
SSE is one-way by design: there's no channel for the client to talk back except by making a separate, ordinary HTTP request. It's also text-only at the protocol level — you can send binary data base64-encoded inside a data field, but that's not what the format is built for, and it adds overhead a binary WebSocket frame doesn't have. Browser connection limits matter too: HTTP/1.1 caps concurrent connections to a single origin at around six, and an open SSE stream occupies one of them for as long as it's connected, which can starve other requests to the same origin on older HTTP/1.1 setups. Serving SSE over HTTP/2 removes this problem, since HTTP/2 multiplexes many streams over one connection, so a production SSE deployment is worth pairing with an HTTP/2-capable server. Internet Explorer never supported EventSource natively, though that's rarely a constraint on projects built today.
When to reach for it
Pick SSE whenever the data flow is fundamentally one-directional and you'd otherwise be polling: live scores, stock prices, build/deploy progress, log tailing, notification badges, AI response streaming (this is in fact how most streaming chat completions are delivered under the hood). Pick WebSockets when the client needs to send data back on the same channel in real time. And if you're not sure yet, SSE's simplicity — it's a GET request and a while loop on the server — makes it the cheaper thing to build first and replace later if a genuine two-way requirement shows up. The MDN Server-Sent Events documentation covers the full event stream format and browser support details in more depth than fits here.