WebSocket vs SSE — r/webdev Decision Tree

WebSocket SSE realtime

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

ConstraintFavors
Need to push through aggressive corporate proxiesSSE or long poll over plain HTTPS
Serverless functions with short max durationNeither forever — use managed realtime (Ably, Pusher, CF Durable Objects) or short SSE with external pub/sub
Fan-out to huge audiencesSSE/HTTP from edge + pub/sub; raw WS to one origin can hurt
Existing JWT cookie sessionSSE often simpler (EventSource + cookies)
Multiplex many streams on one connectionWebSocket

Hybrid patterns (boring and effective)

  1. SSE for notifications + REST for commands — dashboards, billing events, job progress.
  2. WebSocket for the collaborative surface only — leave the rest of the app on HTTP.
  3. 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.”