Every time I start a project that needs live data — a multiplayer game state, a dashboard that updates without refreshing, a notification feed — the same question comes up: which real-time protocol? In 2024 the answer was usually "WebSocket, unless you only need server pushes." In 2026 there's a genuine third option. WebTransport hit Baseline in March, meaning it now works in every major browser — Chrome, Firefox, Edge, and Safari. That changes the comparison. Here's how the three stack up, with real numbers and a decision framework I actually use.
The three protocols in thirty seconds
Before the deep dive, here's the shape of each one:
- WebSocket — a persistent, full-duplex TCP connection. Both sides can send messages any time. The workhorse of real-time web since 2011.
- SSE (Server-Sent Events) — a one-way stream from server to client over plain HTTP. Dead simple, built-in reconnection, works through every proxy and CDN you'll ever meet.
- WebTransport — built on HTTP/3 and QUIC. Multiple independent streams, optional unreliable datagrams, no head-of-line blocking. The newest and most capable, but also the most complex to deploy.
WebSocket — the reliable default
WebSocket is 15 years old and it works. You open a single TCP connection with an HTTP upgrade
handshake, and then both sides send framed messages — binary or text — whenever they want.
Per-message framing overhead is tiny (2–14 bytes), and with a good server like
uWebSockets.js you can hold 200,000+ concurrent connections on a single box with
sub-50 ms tail latency.
I used WebSocket (via Supabase Realtime, which tunnels through WebSocket underneath) for the multiplayer state sync in ZimBet. Every player in a crash round sees the multiplier tick in real time, and everyone needs to be able to send a cash-out event back at any moment. That bidirectionality is exactly what WebSocket is built for.
Where it falls short: WebSocket runs on TCP, which means head-of-line (HOL) blocking. If one packet is lost, every message behind it waits for the retransmit — even messages on a completely unrelated logical stream. For most apps this is invisible. For a fast-paced game sending 60 position updates per second, that stall is a visible hitch. WebSocket also requires explicit reconnection logic on the client; if the connection drops, you have to handle it yourself.
SSE — the one you're probably underrating
Server-Sent Events don't get the hype, but they quietly power some of the most-used real-time features on the web. ChatGPT's streaming token output? SSE. Live sports score widgets? Often SSE. Any "push from server, no client messages needed" use case is SSE territory.
The beauty is simplicity. SSE is just a long-lived HTTP response with Content-Type:
text/event-stream. That means it works through every proxy, CDN, and load balancer that
understands HTTP — which is all of them. The browser's EventSource API gives you
automatic reconnection with Last-Event-ID for free: if the connection drops, the
browser reconnects and asks the server to resume from where it left off. You get that for zero
lines of application code.
The trade-offs: SSE is text-only (UTF-8) and strictly one-way. If the client
needs to send messages back, you end up pairing SSE with plain fetch or
POST requests — which works fine for something like a dashboard with occasional
filter changes, but gets awkward for a chat app where the client sends as often as it receives.
Per-message latency is slightly higher than WebSocket (roughly 5–10 ms vs 1–3 ms due to text
parsing overhead), though in practice network round-trip time dwarfs both numbers.
WebTransport — the new contender that's finally real
WebTransport became Baseline in March 2026 when Safari 26.4 shipped support, joining Chrome (97+), Firefox (114+), and Edge (97+). That's the web platform's way of saying: "this works across all major browsers and is safe to use without polyfills." It's no longer experimental — it's a real option.
WebTransport runs on HTTP/3, which means QUIC, which means UDP under the hood. That gives it three things the other two can't offer:
- Multiple independent streams. You can open several bidirectional or unidirectional streams on one connection. A lost packet on stream A doesn't block stream B — no head-of-line blocking across streams.
- Unreliable datagrams. You can send fire-and-forget messages with no retransmission. For player position updates in a game, you don't want the engine waiting for a stale position to be re-delivered — you want the latest one. Datagrams give you that.
- Multiplexed connection. Streams and datagrams share one QUIC connection. No extra handshakes, no extra ports.
Here's what a minimal WebTransport client looks like:
const wt = new WebTransport("https://example.com/game");
await wt.ready;
// Unreliable: fire-and-forget position updates
const dgWriter = wt.datagrams.writable.getWriter();
await dgWriter.write(new Uint8Array([x, y, z]));
// Reliable: open a bidirectional stream for chat
const stream = await wt.createBidirectionalStream();
const writer = stream.writable.getWriter();
await writer.write(new TextEncoder().encode("hello"));
The catch: you need an HTTP/3-capable server with a valid TLS certificate.
That's not a npm install away — you're looking at something like
aioquic (Python), quiche (Rust), or a Go QUIC server. Proxy and CDN
support for HTTP/3 is growing fast but isn't as universal as HTTP/1.1. And global browser
support, while Baseline, sits around 75 % of all traffic when you count older devices and
browsers that haven't updated. Most teams shipping WebTransport today run a WebSocket fallback
alongside it.
Side-by-side comparison
The quick-reference table I keep in my notes:
| WebSocket | SSE | WebTransport | |
|---|---|---|---|
| Direction | Bidirectional | Server → client | Bidirectional + datagrams |
| Transport | TCP | HTTP (TCP) | QUIC (UDP) |
| Per-msg latency | ~1–3 ms | ~5–10 ms | ~1–3 ms (streams) / sub-ms (datagrams) |
| HOL blocking | Yes | Yes | No (per-stream) |
| Auto reconnect | No (manual) | Yes (built-in) | No (manual) |
| Browser support | ~99 % | ~97 % | ~75 % (Baseline Mar 2026) |
| Proxy / CDN | Good | Excellent | Growing |
The decision framework
After shipping projects with all three, here's the mental model I use:
- Only the server pushes data? → SSE. Dashboards,
notification feeds, live scores, LLM streaming. It's simpler to deploy, simpler to scale,
and the built-in reconnection saves you real engineering time. Pair it with
fetchfor the occasional client-to-server request. - Client and server both send frequently? → WebSocket. Chat, collaborative editing, turn-based or slow-paced multiplayer. It's universally supported, every hosting platform handles it, and the tooling ecosystem is mature. If WebSocket is good enough — and for most apps it is — it's the right pick.
- Latency-critical with mixed reliability needs? → WebTransport (with WebSocket fallback). Fast-paced multiplayer games, real-time media, anything where you need unreliable datagrams or independent streams. The deployment cost is higher, but the performance ceiling is too.
And the one people forget: polling. If your data changes every few seconds and
you have fewer than a thousand concurrent users, a setInterval +
fetch every 3 seconds is simple, stateless, and perfectly fine. Don't reach for a
persistent connection until you actually need one.
What I've picked in practice
For ZimBet's crash game, WebSocket (via Supabase Realtime) was the obvious choice — every player sends a cash-out at an unpredictable moment, and the server broadcasts the rising multiplier to everyone. Bidirectional, persistent, and the tick rate is low enough (10–20 Hz) that TCP's head-of-line blocking never mattered.
For LanLink's offline file transfer I didn't use
any of these three — WebRTC DataChannels were the right tool because the connection is
peer-to-peer with no server in the middle. But if I were building the signaling layer today,
I'd likely use SSE for the server-push side and POST for the client offers,
rather than a full WebSocket — because the signaling traffic is one-way most of the time.
If I were starting a fast-paced browser FPS from scratch today — something like the Three.js shooter I wrote about — I'd seriously prototype with WebTransport. Unreliable datagrams for position and input, a reliable stream for chat and game events, no HOL blocking between them. The server setup is more work, but the latency characteristics are genuinely better for the 60-Hz-input kind of game.
Bottom line
There's no single "best" real-time protocol — there are three good ones with very different cost-to-benefit ratios. SSE is underrated and should be your first instinct for server-push. WebSocket is the safe, proven default for bidirectional work. WebTransport is the future for latency-critical, multi-stream use cases, and as of 2026 it's finally real enough to ship. Pick the simplest one that covers your actual requirements, add a fallback if your audience includes older browsers, and move on to the interesting part of your project.