# Real Time Data Without WebSocket Limits

Source: https://www.egnworks.com/blog/webtransport-explained  
Author: Jacob Val  
Published: 2026-09-24  
Updated: 2026-09-24  
Category: Frontend Architecture  
Tags: WebTransport, Web Performance

> A deep technical guide to WebTransport, covering why WebSocket's single TCP stream causes head of line blocking, how QUIC changes that, bidirectional and unidirectional streams, unreliable datagrams, certificate hashes for local development, a comparison against WebSocket, and browser support.

---

WebSocket has been the only real option for a persistent, bidirectional connection between a browser and a server for over a decade, and it has one structural limitation baked in from the start, it runs over a single TCP stream. Every message shares that one stream, in strict order, so a single dropped packet stalls every message queued behind it, not just the one it belongs to. WebSocket also only ever speaks one language, an ordered, reliable stream of messages, with no way to send a quick, disposable update if losing an occasional one would actually be fine. WebTransport was built to fix both problems at once.

## Why WebSocket Was Built on a Single Stream

TCP guarantees ordered, reliable delivery of a single byte stream, and WebSocket is layered directly on top of one TCP connection. That guarantee is exactly why a lost packet anywhere in that stream blocks everything after it, TCP will not deliver byte 1,001 to the application until byte 1,000 has been retransmitted and received, regardless of whether byte 1,001 belongs to a completely unrelated message that arrived intact. This is called head of line blocking, and it is not a WebSocket specific bug, it is an inherent property of building anything on a single ordered TCP stream.

## What QUIC Changes Underneath

WebTransport runs over HTTP/3, which itself runs over QUIC rather than TCP. QUIC multiplexes multiple independent streams over one underlying UDP based connection, and critically, a lost packet on one stream only blocks that specific stream, every other stream continues delivering data uninterrupted. This is the structural fix head of line blocking needed, moving the guarantee of ordered delivery from the connection as a whole down to each individual stream.

## Opening a Connection

A `WebTransport` instance is constructed with an HTTPS URL that must include an explicit port and cannot include a fragment, and connecting is asynchronous.

```js
const transport = new WebTransport("https://example.com:4999/wt");
await transport.ready;
```

The constructor also accepts an options object. `requireUnreliable` refuses to fall back to HTTP/2 if HTTP/3 is not available, useful when a datagram based feature genuinely needs QUIC and would rather fail than silently degrade. `congestionControl` hints whether the connection should tune itself for `"throughput"` or `"low-latency"`, and `allowPooling` controls whether the connection can share an underlying network connection with other HTTP/3 sessions to the same server.

## Bidirectional Streams for Ordered Exchanges

A bidirectional stream behaves like a miniature version of what WebSocket offered, an ordered, reliable channel, except a connection can open many of them independently.

```js
const stream = await transport.createBidirectionalStream();
const writer = stream.writable.getWriter();
const reader = stream.readable.getReader();

await writer.write(new TextEncoder().encode("hello"));
```

Two separate bidirectional streams opened from the same `WebTransport` instance do not block each other, a slow or lossy exchange on one has no effect on the other, which is precisely the property a single WebSocket connection could never offer.

## Unidirectional Streams

A unidirectional stream commits to a single direction upfront, which fits a use case like a server continuously pushing state updates to a client with no need for a reply on that same channel.

```js
const stream = await transport.createUnidirectionalStream();
const writer = stream.getWriter();
await writer.write(new TextEncoder().encode("state update"));
```

Incoming unidirectional streams opened by the server arrive through `transport.incomingUnidirectionalStreams`, which a client reads from without ever needing to write back on that particular stream.

## Reading Streams the Server Opens

A server can open its own bidirectional streams toward the client, arriving through `transport.incomingBidirectionalStreams`, an async iterable a client reads from as new streams appear.

```js
const reader = transport.incomingBidirectionalStreams.getReader();

while (true) {
  const { value: stream, done } = await reader.read();
  if (done) break;
  handleIncomingStream(stream);
}
```

This is what lets a server push an arbitrary number of independent, ordered channels toward a client over time, each one isolated from the others, rather than a client having to pre-negotiate how many streams it expects to receive.

## Surviving a Network Change

Because QUIC identifies a connection by a connection ID rather than the traditional combination of source IP, source port, destination IP, and destination port a TCP connection relies on, a WebTransport session can survive a network change that would have killed a TCP based WebSocket outright. Switching from Wi-Fi to a cellular connection mid session changes the device's IP address, which forces a TCP connection, and any WebSocket riding on it, to close and reconnect from scratch. QUIC recognizes the same connection ID arriving from a new address and continues the session, which matters specifically for a mobile client that changes networks far more often than a desktop one ever does.

## Datagrams for When Order Doesn't Matter

`transport.datagrams` exposes a duplex stream for sending and receiving individual, unreliable, unordered messages, closer to raw UDP than anything WebSocket ever offered.

```js
const writer = transport.datagrams.writable.getWriter();
await writer.write(new Uint8Array([1, 2, 3]));
```

A datagram can arrive out of order or not arrive at all, and nothing retransmits it automatically. This is a deliberate tradeoff that fits data where the newest value matters and an old, late arriving one is worthless anyway, a player position update in a fast paced game being the canonical example, where a stale position is not worth the latency cost of guaranteeing its delivery.

## Certificates for Local Development

`serverCertificateHashes` lets a connection authenticate a server by pinning a specific certificate hash rather than relying on the public certificate authority system, which matters for connecting to a server without a publicly trusted certificate, such as a local development instance or a machine not reachable by a normal certificate authority's validation process.

```js
const transport = new WebTransport("https://192.168.1.10:4999/wt", {
  serverCertificateHashes: [
    { algorithm: "sha-256", value: certificateHashBytes },
  ],
});
```

The pinned certificate has to be short lived, valid for under two weeks, and use a modern elliptic curve algorithm, which keeps this mechanism usable for development and testing without opening a path to indefinitely trusting a certificate that never rotates.

## Comparing WebTransport to WebSocket

| | WebSocket | WebTransport |
| --- | --- | --- |
| Underlying transport | TCP | QUIC over UDP |
| Head of line blocking | Affects the whole connection | Isolated to a single stream |
| Multiple independent channels | One connection, one stream | Many concurrent streams from one connection |
| Unreliable, unordered messages | Not supported | Supported through datagrams |
| Protocol maturity and library support | Extensive, over a decade old | Newer, smaller ecosystem |

## What WebTransport Does Not Replace

A simple chat application or a basic real time notification feed rarely needs multiple independent streams or unreliable datagrams, and WebSocket's simpler, single stream model and much larger ecosystem of libraries and server implementations still fit that case perfectly well. WebTransport earns its added complexity specifically when an application genuinely has multiple, independent data flows that should not block each other, or a class of data where losing an occasional message is an acceptable, even preferable, tradeoff against latency.

## Browser Support

WebTransport is supported in Chrome, Edge, Firefox, and now Safari as well, and counts as broadly available today, having reached that point relatively recently. A server also has to speak HTTP/3, which is a real deployment requirement beyond just the browser side supporting the API, unlike WebSocket where nearly any HTTP server can be extended to support it with a widely available library.

## Conclusion

WebTransport moves real time browser to server communication off a single TCP stream and onto QUIC, which is what makes independent, non-blocking streams and genuinely unreliable datagrams possible in the first place. Bidirectional and unidirectional streams cover the ordered, reliable case WebSocket already handled, just without one slow stream dragging every other one down with it, and datagrams cover a case WebSocket structurally could not, data where arriving late is worse than not arriving at all.

## References

[MDN: WebTransport API](https://developer.mozilla.org/en-US/docs/Web/API/WebTransport_API)

[MDN: WebTransport() Constructor](https://developer.mozilla.org/en-US/docs/Web/API/WebTransport/WebTransport)

[MDN: WebTransport.datagrams](https://developer.mozilla.org/en-US/docs/Web/API/WebTransport/datagrams)

[Can I Use: WebTransport](https://caniuse.com/webtransport)
