What Are Server-Sent Events (SSE)?
Server-Sent Events stream real-time updates over a single HTTP connection. How SSE works, when to use it, and how it compares to WebSockets.
Server-Sent Events (SSE) is a browser standard for receiving a one-way stream of text events from a server over a single, long-lived HTTP connection. The browser side uses the EventSource API — a handful of lines of JavaScript — and the server side sends plain text formatted according to a simple spec. It powers live scoreboards, notification feeds, and the ChatGPT-style token-by-token typing effect that has become ubiquitous in AI chat interfaces.
How it works
On the server, the response sets Content-Type: text/event-stream, keeps the connection open, and writes data incrementally. Each event is a block of text lines followed by a blank line:
data: {"score": 42}\n\n
Named events and IDs are optional extras:
event: notification
id: 1042
data: {"message": "Your report is ready"}\n\n
On the client, the EventSource constructor takes a URL and starts listening:
const source = new EventSource("/api/events");
source.addEventListener("message", (e) => {
const payload = JSON.parse(e.data);
console.log(payload);
});
source.addEventListener("notification", (e) => {
showToast(JSON.parse(e.data).message);
});
source.addEventListener("error", () => {
console.log("connection lost, will retry automatically");
});
The browser handles reconnection automatically. If the connection drops, EventSource retries after a short delay (default ~3 seconds) and sends the Last-Event-ID header so the server can resume from where it left off. You get resilience for free.
What SSE is great for
- Streaming AI responses. Every major LLM API — including the Claude API and OpenAI — streams tokens over SSE. The typing effect is just the browser appending each
data:chunk to the DOM as it arrives. - Live notifications. Push alerts to logged-in users without polling or WebSocket overhead.
- Progress tracking. A long-running job (file processing, report generation) can stream percentage updates to the UI without polling.
- Live feeds. News tickers, sports scores, price updates — any data where the server has information the browser needs as soon as it’s available.
SSE vs. WebSockets vs. polling
SSE and WebSockets are both real-time solutions, but they’re optimized for different problems. Polling is the baseline that both replace.
| Technique | Direction | Protocol | Auto-reconnect | Complexity | Best for |
|---|---|---|---|---|---|
| Polling | Client → server (repeated) | HTTP | — | Low | Simple, infrequent updates |
| SSE | Server → client | HTTP | Yes (built-in) | Low | Live feeds, notifications, AI streaming |
| WebSocket | Both | WS (upgrade) | No (manual) | Medium | Chat, games, collaborative tools |
| WebRTC | Peer-to-peer | QUIC/UDP | No | High | Video calls, P2P data |
The key differences between SSE and WebSockets:
- Direction. SSE is server-to-client only. The browser cannot send messages back over the same connection. WebSockets are fully bidirectional.
- Protocol. SSE is plain HTTP/1.1 (or HTTP/2). WebSockets require an
Upgradehandshake to a separate protocol. That difference matters for infrastructure: SSE works through most reverse proxies and CDNs without special configuration; WebSocket support has to be explicitly enabled. - Reconnection. SSE reconnects automatically. With WebSockets, you write the retry logic yourself.
- Multiplexing. Over HTTP/2, multiple SSE streams share a single TCP connection with no extra effort. Check the HTTP/3 landscape if QUIC-based streaming is relevant to your architecture.
Choosing between SSE, WebSocket, and polling
Use SSE when:
- Communication is one-way (server pushes, client only reads).
- You want minimal setup —
EventSourceis two lines in the browser. - You’re streaming AI token output or a long-running job’s progress.
- You need something that works reliably through corporate proxies.
Use WebSockets when:
- You need the client to send messages back frequently (chat, collaborative editing, multiplayer games).
- You need binary frames or sub-message-level framing control.
Use polling when:
- Updates are infrequent (every few minutes), and the simplicity of a
setInterval+fetchis worth the latency trade-off. - You’re working in an environment where persistent connections are unreliable or expensive.
A note on the event loop
Because EventSource is event-driven, all callbacks fire as tasks on the JavaScript event loop. A slow message handler will block subsequent events, just like any other long-running callback. If processing a streamed chunk involves heavy computation, defer it with setTimeout or move it to a Web Worker.
Server implementation notes
Most frameworks support SSE with a small amount of plumbing. The key requirements: set the correct Content-Type, disable any response buffering, flush after each event, and close the stream when the client disconnects (watch for the close event on the request). In Node.js, check req.on("close", ...) and clear any intervals or streams you opened.
The takeaway
Server-Sent Events occupy a sweet spot: simpler than WebSockets, far more efficient than polling, and deeply integrated with the browser’s HTTP stack. If your feature needs the server to push data — notifications, live scores, or a streaming AI response — SSE is often the right first choice. Reach for WebSockets only when you need the client to talk back on the same channel.
Keep reading
Takina · · 4 min read requestIdleCallback Explained
requestIdleCallback runs low-priority JavaScript when the browser is idle, without blocking rendering, input, or the main thread.
Takina · · 4 min read Dynamic import() in JavaScript: Code-Splitting Explained
JavaScript's dynamic import() loads a module on demand and returns a promise, letting you split bundles and defer code until it's actually needed.
Takina · · 5 min read Finding and Fixing Memory Leaks in JavaScript
A JavaScript memory leak happens when a reference outlives its usefulness and the garbage collector can't reclaim it. Common causes and how to find them.