1. Direction & payload
| If… |
Prefer… |
| Updates rare; stale OK (minutes) |
Short poll / cache revalidation |
| Server → client text/JSON push; client actions via REST |
SSE (or long-poll if very low rate) |
| Continuous duplex (chat, multiplayer, typing) |
WebSocket (or managed realtime) |
| Binary media / custom framing |
WebSocket / WebRTC / file APIs — not SSE |
2. Client API (if server-push over HTTP)
| If… |
Client |
| GET + cookies (or no special headers) |
Native EventSource |
Need Authorization, POST body, non-GET |
fetch + stream parse (SSE wire optional) |
| Cross-origin cookies |
withCredentials: true + CORS credentials (not
*)
|
3. Delivery guarantees
| Product need |
Plan |
| Latest state is enough |
Reconnect; optional snapshots; gaps OK |
| No missed events after blip |
id: + honor Last-Event-ID + durable
log/cursor
|
| Exactly-once UI |
Idempotent handlers + server dedupe keys — not free from the
browser
|
4. Ops & scale (FE-visible)
-
Proxies: no response buffering; heartbeats; sane timeouts
(
text/event-stream, Cache-Control: no-cache).
-
HTTP/1.1: ~6 connections/origin — multi-tab SSE can starve REST
(Lawrence). Prefer HTTP/2+; one shared stream per app/tab set.
- Always
close() on unmount; branch error on readyState.
5. One-sentence decision template
We use [poll | SSE + EventSource | SSE wire + fetch | WebSocket]
because [direction/payload], with [cookie|Bearer/fetch] auth,
[gap-ok|Last-Event-ID resume], over [h1|h2], owning [proxy/heartbeat]
ops.