WebSockets vs Server-Sent Events
How WebSockets and SSE differ in transport, direction, reconnection, and operations, with guidance on when each one is the right pick for real-time web apps.
What you'll learn
- ✓What WebSockets and SSE actually are
- ✓When one-way push is enough
- ✓Reconnection and backpressure differences
- ✓Proxy, auth, and ops considerations
- ✓How to choose without overengineering
Prerequisites
- •Basic HTTP and JavaScript familiarity
Every few years a team picks WebSockets for a problem that only needed a one-way stream, then spends a quarter wrestling with sticky sessions and load balancer quirks. SSE is the unglamorous option that often fits better. Knowing which to reach for starts with seeing what each protocol actually is.
What and Why
WebSockets upgrade an HTTP connection into a long-lived, bidirectional, binary-or-text channel. After the handshake, either side can send a frame at any time. Server-Sent Events are plain HTTP responses that never close, streaming text/event-stream data from server to client only.
WebSockets shine when both sides talk: chat with typing indicators, collaborative editing, multiplayer games. SSE shines when the server has news and the client only needs to listen: live dashboards, notifications, AI streaming completions, build logs. Most “real-time” features in business apps fall in the second category and never need true bidirectional traffic.
Mental Model
A WebSocket is a phone call: both people can talk, and you have to manage the etiquette of who speaks when. SSE is a radio broadcast: the station transmits, you tune in. If you only need to listen, you do not need a phone.
The transport layer matters too. SSE rides on regular HTTP/1.1 or HTTP/2, so caches, proxies, gzip, and HTTP/2 multiplexing all work the way you expect. WebSockets use a distinct Upgrade handshake; every proxy, load balancer, and WAF in your path needs to understand it.
Hands-on Example
SSE on the server is a normal handler that flushes lines.
// Node, Express
app.get('/stream', (req, res) => {
res.set({
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive',
});
const send = (data) => res.write(`data: ${JSON.stringify(data)}\n\n`);
const t = setInterval(() => send({ t: Date.now() }), 1000);
req.on('close', () => clearInterval(t));
});
The browser side is one line of API.
const es = new EventSource('/stream');
es.onmessage = (e) => console.log(JSON.parse(e.data));
A WebSocket version needs a separate server library and a different client API.
const ws = new WebSocket('wss://api.example.com/socket');
ws.onmessage = (e) => console.log(e.data);
ws.onopen = () => ws.send(JSON.stringify({ subscribe: 'orders' }));
WebSocket:
client <===== full-duplex frames =====> server
(single TCP, after HTTP Upgrade)
binary or text, app-defined framing
SSE:
client <----- text/event-stream ----- server
(single HTTP response, never closes)
server -> client only
browser auto-reconnects with Last-Event-ID SSE bakes in reconnection. If the connection drops, EventSource reconnects and sends a Last-Event-ID header so the server can resume. WebSockets leave reconnection to you, including exponential backoff and resubscribing to whatever the client was listening to.
Common Pitfalls
- Picking WebSockets for one-way data. You inherit operational pain you do not need. If the client never sends meaningful frames, SSE is simpler.
- Forgetting heartbeats. Idle connections get killed by NATs and load balancers after a minute or two. WebSockets need ping/pong; SSE needs periodic comment lines (
: ping\n\n). - No backpressure handling. Both protocols will buffer until they fall over. Drop or coalesce on the server side when the client is slow.
- Sticky sessions everywhere. Once you scale out, both protocols need either sticky routing or a shared pub/sub layer like Redis. Plan that on day one.
- Auth in the URL. Tokens in WebSocket URLs end up in logs. Use a short-lived ticket or a cookie-based session.
Practical Tips
For AI streaming and notifications, start with SSE; it is one fetch handler and one EventSource. For collaborative editing and games, start with WebSockets and pick a library that handles reconnect and message acks. Limit message size on both ends. Add request IDs to every frame so you can correlate logs across services. Test through your real load balancer early; localhost will lie to you about timeouts and buffering.
If you are already on HTTP/2 or HTTP/3 everywhere, SSE benefits from multiplexing and modern flow control, removing the old “browser connection limit” argument against it.
Wrap-up
WebSockets and SSE solve overlapping but different problems. Ask “does the client need to talk back over the same channel” and let that answer pick the protocol. The boring choice is usually the right one, and the boring choice for one-way streams is SSE.
Related articles
- Web Content Security Policy Explained
A practical tour of CSP: what each directive does, how nonces and hashes work, how to roll out a policy safely with report-only mode, and the mistakes that quietly weaken it.
- Web Cookies vs localStorage vs sessionStorage
Compare the three main browser storage mechanisms by lifecycle, capacity, security, and use case so you pick the right tool for tokens, preferences, and state.
- Web CORS Explained In Depth
Understand Cross-Origin Resource Sharing: simple vs preflight requests, credentials, and the headers that decide whether your browser will let your fetch succeed.
- Web JWT vs Session Authentication
Compare JSON Web Tokens with classic server-side sessions across storage, revocation, scaling, and security so you can choose the right model for your app.