Recipes
Three kinds of page live here. The framework recipes show how to set react-websocket up in the framework or server you already use. The real-world guides build a whole app, client and server, and show what happens when the network misbehaves. The patterns at the end are short and self-contained: copy what you need.
Frameworks
Each recipe follows one of the repository's examples, so you can run what you read: where the client goes, where the WebSocket server goes, and the catches particular to that framework.
Next.js
Hooks in Client Components, a client that Server Components can import, Suspense for the first connection, and a custom server for the socket.
'use client' · react-websocket/core · custom server · browser()
Astro
Islands that share one connection, state for islands that hydrate late, and a connection that survives page changes.
client:visible · client:only · <ClientRouter /> · transition:persist
Remix 3
No React needed: the client from core, small bindings that stand in for the hooks, and client entries on one socket.
react-websocket/core · bindings · clientEntry · queueTask
Hono
A chat room with a Hono server on Node.js, a Vite and React client, message types both sides import, and a bot in Node.
upgradeWebSocket · Vite proxy · createWebSocketState · createWebSocketRequests · migrate() · heartbeat
Tornado (Python)
A broadcast chat with a Python server run by uv: origin checks, pings that drop dead connections, and a heartbeat for the browser.
WebSocketHandler · check_origin · websocket_ping_interval · uv
Real-world guides
Each guide's code is complete and type-checked against the library, and was run in Chrome against its server, with stand-ins for third-party APIs where a guide uses one.
AI apps
AI chatbot
Streamed answers that survive reconnects and reloads, a Stop button that stops the model, and tools the user approves. The server calls Claude.
createWebSocketRuns · useAgentRun · createWebSocketResponder · useAgentApproval
AI image and video generator
A queue with live positions, progress and previews, jobs that carry on through reloads, and one gallery on every device.
createWebSocketRuns · useAgentRun · createWebSocketState · heartbeat
Voice assistant and text to speech
Microphone audio up, the answer down, interruptions cut back to what was heard, and narration that streams and downloads.
volatile · maxBuffered · priority · createWebSocketRequests
Multiplayer games
Multiplayer party game
A trivia host screen and phones: rooms with a join code, a countdown every screen agrees on, answers counted once, and phones that rejoin after they sleep.
createWebSocketRequests · createWebSocketPresence · createWebSocketLatency · createWebSocketState
Real-time action game
The game example, file by file: a fixed-tick server, prediction and interpolation, an adaptive send rate and a 60 fps canvas.
react-websocket/timeline · createWebSocketSendRate · createWebSocketLatency · react-websocket/chaos
Collaboration
Team chat
Channels where every message arrives once and in order, an outbox that survives reloads, unread counts on every device, and one connection for all tabs.
createWebSocketFeeds · react-websocket/outbox · createWebSocketPresence · react-websocket/tabs
Collaborative whiteboard
Live cursors, strokes others watch as you draw, a snapshot for late joiners, drawing offline, and notes merged by Yjs.
createWebSocketPresence · createWebSocketSendRate · createWebSocketFeeds · react-websocket/outbox
Live data
Trading dashboard
A 500-symbol watchlist that streams only the rows on screen and renders once per frame, an order book that stays exact through lost messages, and orders placed exactly once.
createWebSocketTopics · createWebSocketFeeds · useInView · batch: 'frame'
Live notifications
One connection for every tab, an unread badge that resumes from the last notice after a reload, reads that survive going offline, and sign-out everywhere.
react-websocket/tabs · createWebSocketFeeds · react-websocket/outbox · useWebSocketOutbox
Delivery tracking
A courier marker that glides between GPS fixes without React renders, an ETA on the server's clock, and proof of delivery that gets through a dead zone.
react-websocket/timeline · createWebSocketLatency · createWebSocketSendRate · react-websocket/outbox
Many message types on one socket
Pub/sub topics, Django Channels groups and similar features all deliver different kinds of message over the same socket. Tag each message on the server with a discriminant field, type it as a union, and route on the client:
Each component listens for what it cares about, and they all share one connection. To validate the union at runtime, pass a schema.
Request/response
WebSockets have no built-in replies. A common convention is to send an id with each request and have the server echo it in the reply. This helper wraps that in a promise with a timeout:
If the socket isn't open, the request is queued and sent on connect. The timer starts immediately, so pick a timeout that covers a reconnect. If the connection drops after the request was sent, the server may never reply; the timeout turns that into a rejection.
Publishing with an acknowledgement
send() returns once the message is handed to the socket or queued. It can't tell you whether the server received the message, or whether it accepted it. Pub/sub clients such as Faye return a promise from publish() that settles when the server answers. createWebSocketRequests gives you the same promise: the server answers each publish with its id, and the promise resolves on that answer, or rejects on a refusal, a timeout or a lost connection.
Every publish carries two identifiers. id belongs to the request: the library generates a new one on every call, so the answer finds its promise. key is yours: keep it when you retry, and the server publishes each key once.
What a failure means:
- Refused. The server got the message and said no, and
errorturned its answer into aPublishRefused. Show the reason. Sending it again gets the same answer. - Anything else. A
TimeoutError, a lost connection, or a request dropped from the send queue. The server may have published the message or not, so it's unconfirmed. Retrying with the samekeyis safe either way. - Lost connections fail at once. An answer can only arrive on the connection the publish went out on, so when that connection closes, the promise rejects right away instead of waiting for
timeout. - Offline. A publish made while disconnected waits in the send queue and goes out on the next connection. Its
timeoutcounts from the call, so pick one that covers a reconnect, or pass{ volatile: true }as the second argument ofrequest()to fail at once instead. - Acknowledged isn't received. The answer means the server accepted the message and handed it out, not that every subscriber has it. Subscribers that were offline catch up through numbered feeds.
On the server, answer every publish with its id: refused when it isn't allowed, published once it's handed out. Publish each key once, and answer a repeat with published again so the client's retry resolves:
A request lives in memory: a reload loses it, and nothing retries it for you. For messages that must arrive, such as an order, use the offline outbox instead. It keeps each message in IndexedDB and sends it again on every new connection until the server acknowledges it. It has no promise per message, though, and no refusal: the server acknowledges everything it has handled, allowed or not, or the outbox would send a refused message forever. Report a refusal in a message of its own.
Following a changing URL
With useWebSocket(url), a new URL closes the old connection and opens the new one. Build the URL from state:
Each distinct URL is its own shared connection. If the URL comes from a text field, debounce it so you don't connect on every keystroke.
To switch rooms on one connection instead, keep a single client and let topic subscriptions send the join/leave messages:
Calling send from an effect yourself gets two things wrong. The server forgets rooms when the connection drops, so nothing joins them again after a reconnect. And send queues while disconnected, so a leave for a room you switched away from while offline goes out after the reconnect, for a room the new connection never joined. Topics join every held room again each time the socket opens, and never queue join or leave.
Presence and typing indicators
Send typing events at most every couple of seconds, and keep the list of who's typing in state:
Polling over a socket
The server decides when to push. If you need to ask on an interval, call send from your own timer:
Optimistic sends
send() never throws for a disconnected socket: it queues the message and returns false. Show the message immediately, mark whether it actually went out, and confirm it when the server echoes it back:
Queued messages go out in order when the connection opens. To bound memory during a long outage, set queue: { limit }. close() and giving up both clear the queue, so messages still marked queued at that point were never sent.
To await the server's answer as a promise instead, with refusals and retries, see Publishing with an acknowledgement.
Pausing a connection
enabled: false releases a component's hold on the connection. When no other component holds it, it closes; enabled: true connects again:
A null URL works the same way: useWebSocket(isLoggedIn ? url : null).
Content hidden by React's <Activity> releases its connections automatically.
Logging and telemetry
The logger option receives the client's debug messages: connection attempts, retry delays, heartbeat timeouts and dropped messages. Route it to your telemetry, and listen to events for structured data:
In development, logger: console.debug is often all you need.
Hook URL or shared client?
Start with useWebSocket(url) for a socket used by one component. Move to a shared client when several components, non-React code, or a URL function are involved.