WebTransport
WebTransport runs over HTTP/3 and QUIC. For real-time apps it adds one thing WebSockets can't do: datagrams, messages that are never queued or retransmitted. A lost cursor position or input doesn't hold up the ones after it, the way a lost packet holds up everything behind it on a WebSocket's TCP connection.
react-websocket/webtransport puts it behind the API you already use:
Hooks, reconnects, the queue, topics, feeds and everything else work as before.
- Ordinary sends go on one reliable, ordered stream, so they behave exactly like WebSocket messages.
volatilesends go as datagrams:game.send({ type: 'aim', angle }, { volatile: true }). One too big for a datagram, about 1,200 bytes, goes on the stream instead. A datagram can be lost even while connected, so send what must arrive, such as a shot, withoutvolatile, or passdatagrams: falseto keep every message on the stream. The library's own messages that must arrive, such as outbox entries, request cancels and replies, feed and AG-UI resyncs, always go on the stream.- The URL is the client's, with
ws:andwss:turned intohttps:, which WebTransport needs. Passurlto map it differently.
In Chrome, against a local HTTP/3 server, the session opened in 22 ms, stream messages and datagrams arrived as sent, and a server closing the session with code 4001 reached shouldReconnect as 4001.
Falling back to WebSocket
Some browsers don't have WebTransport, and some networks block the UDP it runs on. So when WebTransport isn't there, or a session can't be established within timeout (5 seconds by default), the client connects with a WebSocket to its own URL instead. Datagrams then become ordinary messages. It remembers: later connections go straight to WebSocket, without waiting for another attempt to fail.
Your server then has to answer both, at the same URL: WebSocket on TCP, and WebTransport on UDP. In Chrome, with nothing listening for WebTransport, the client fell back in 22 ms. With the address black-holed, it fell back after its 1.5 s timeout, and the next connection opened over WebSocket in 22 ms.
fallback: false turns this off, and fallback: SomeWebSocket picks the class. Transport.fallingBack says whether connections go over WebSocket at the moment.
The server's side
WebTransport has no message framing of its own, so the client uses a small one. A server needs to:
-
Accept a WebTransport session at the URL's path.
-
Read one bidirectional stream, which the client opens right after the session starts. Each message on it, in both directions, is framed as:
-
Read datagrams, each one message: 1 byte of type, then the payload. Send datagrams back the same way, for messages that may be dropped.
-
Close the session with a WebTransport close code and reason to close the connection: the client reports them as a WebSocket close code and reason. A session closed without a code reports
1005, like a WebSocket closed without a status.
The client opens its stream without writing to it. Some server libraries only report a stream once data arrives on it, aioquic's event API among them, so a server built on one can't send first, such as a welcome message: wait for the client's first message, or send what comes first from an open listener on the client.
Libraries with WebTransport servers include aioquic (Python), webtransport-go (Go), and @fails-components/webtransport (Node.js).
Development certificates
WebTransport needs TLS, even on localhost. Instead of a trusted certificate, a development server can use a self-signed ECDSA (P-256) certificate valid for at most 14 days, and the browser can trust it by its SHA-256 hash:
transport is passed to the WebTransport constructor as it is.
Things to know
bufferedAmountcounts the bytes handed to the stream and not yet written, somaxBufferedworks as on a WebSocket. Datagrams aren't counted: a full datagram queue drops them by itself.- Order. Stream messages arrive in order. Datagrams can arrive out of order, or not at all, also relative to stream messages.
- Subprotocols aren't part of WebTransport. They're passed on to the WebSocket fallback.
- Server rendering. Like any
WebSocketoption, nothing connects during a server render.
See also
createWebTransportSocket- Real-time games: volatile input and
maxBuffered