WebSocket vs SSE — r/webdev Decision Tree
You need “realtime.” The thread splits into WebSocket evangelists and people who say SSE is enough. Both can be right. The decision is about direction of traffic, proxy friendliness, and failure modes — not which logo looks more senior on a resume.
Decision tree
Need client → server messages at high rate (game input, collab cursors, chat send)?
└─ yes → WebSocket (or WebTransport later)
└─ no → only server → client push?
└─ yes → SSE often enough
└─ also need binary frames / custom protocols?
└─ yes → WebSocket
└─ no → SSE
Chat receive can be SSE; chat send can stay as HTTPS POST. Many products never need a full-duplex socket.
SSE in practice
// client
const es = new EventSource("/api/stream", { withCredentials: true });
es.addEventListener("invoice.paid", (ev) => {
const data = JSON.parse(ev.data);
// ...
});
es.onerror = () => {
// browser auto-reconnects; backoff is built-in
};
// server (sketch)
res.writeHead(200, {
"Content-Type": "text/event-stream",
"Cache-Control": "no-cache",
Connection: "keep-alive",
});
res.write(`event: invoice.paid\ndata: ${JSON.stringify(payload)}\n\n`);
Pros: HTTP/1.1 friendly mental model, automatic reconnect, works with many CDNs/auth cookie patterns, trivial to reason about as a response stream.
Cons: Client→server is not the channel (use fetch); some proxies buffer SSE; connection limits per browser origin (HTTP/1.1 ~6); binary is awkward (base64).
WebSocket in practice
const ws = new WebSocket("wss://example.com/ws");
ws.onmessage = (m) => { /* ... */ };
ws.send(JSON.stringify({ type: "ping" }));
Pros: True bidirectional, binary frames, lower overhead for chatty duplex protocols, wide library support.
Cons: Sticky sessions / load balancer config, custom heartbeat and reconnect logic, auth often via first-message token because headers are limited in browsers, harder to debug than HTTP logs.
Infra realities that flip the choice
| Constraint | Favors |
|---|---|
| Need to push through aggressive corporate proxies | SSE or long poll over plain HTTPS |
| Serverless functions with short max duration | Neither forever — use managed realtime (Ably, Pusher, CF Durable Objects) or short SSE with external pub/sub |
| Fan-out to huge audiences | SSE/HTTP from edge + pub/sub; raw WS to one origin can hurt |
| Existing JWT cookie session | SSE often simpler (EventSource + cookies) |
| Multiplex many streams on one connection | WebSocket |
Hybrid patterns (boring and effective)
- SSE for notifications + REST for commands — dashboards, billing events, job progress.
- WebSocket for the collaborative surface only — leave the rest of the app on HTTP.
- Do not upgrade everything to WS because one page needs presence indicators.
Failure modes to plan for
- SSE silent stall: send comment heartbeats (
: ping\n\n) so intermediaries do not close idle connections. - WS half-open: application pings + idle timeout; never assume TCP will notice quickly.
- Auth expiry mid-stream: refresh strategy for both; WS may need a re-auth message.
- Horizontal scale: both need a pub/sub backend (Redis, NATS, Kafka). The browser protocol does not replace the message bus.
Short verdict
If the client mostly listens, start with SSE. If both sides chatter continuously or you need binary, use WebSocket. If you are on short-lived serverless without a realtime platform, fix the architecture before picking a browser API. The wrong choice is “WebSocket by default because realtime.”