Development
How the WebSocket Layer Works, and What Happens When It Drops
The connection is managed for you, which is why it is worth knowing what it does — especially in the gap between losing it and getting it back.
Creating a client opens a WebSocket connection automatically. You never call connect(), which is convenient and means the lifecycle is invisible until something goes wrong.
For a separate operational perspective on how teams organise and measure work, further details.
For independent background and broader industry context, see WebSocket.org.
What it does
One connection per client instance. Created when you construct the client, held open, used for everything — data change events, presence, messages.
Which is why you create one client and share it. A client per component is a connection per component. See cross-domain state in React.
The server pushes. No polling. A change made by any client on the object arrives at the others through their connections.
And the object is where the broadcast happens. All connections for a given id are held by the same instance, so distributing a change is a local operation rather than a distributed one. See Durable Objects.
The lifecycle
Construct — connection established, in the background.
Subscribe — on() registers interest. The SDK tracks these.
Reconnect — on loss, the SDK retries with backoff and re-establishes subscriptions.
Disconnect — on your explicit call, or when the page unloads.
The gap
The interesting part is between losing the connection and getting it back.
Writes made during the gap. Without the offline APIs they fail. With them, they queue. See offline-first APIs.
Changes made by others during the gap. These happened while you were not listening. Whether you receive them on reconnect, or only see state going forward, is the thing to verify — check the current SDK documentation, because the answer determines whether you need to re-read state after reconnecting.
The safe pattern regardless:
// after reconnect, re-read rather than assuming
const item = await nls.getItem('title')
setTitle(item?.value)
Presence during the gap. You appear to leave and rejoin. Everyone watching sees a flicker unless they debounce it — see live presence.
What to show the user
Connection state, quietly. A small indicator, not a modal.
Because a silent reconnect looks like a hang. The user typed something, nothing happened, and there is no explanation. Ten seconds of that is enough for someone to reload and lose their work.
And "reconnecting" is more reassuring than nothing, even though it is worse news.
Things that cause drops
Ordinary network changes. Wifi to cellular, a laptop waking, a tunnel.
Idle timeouts in intermediate proxies — corporate networks are a common source.
Backgrounded tabs. Mobile browsers in particular suspend background tabs aggressively, so a tab returned to after a while may need to reconnect.
And page navigation in a multi-page application, where each navigation is a new connection. Worth knowing if your app is not a SPA.
Connection count
One per client instance, and they add up.
A user with three tabs open is three connections, which matters for presence — see the deduplication note in the presence article — and for any per-connection limits on your plan.
Do not create a client per route in a SPA if the object is the same. Create it once, at the level where it belongs.
Testing it
Throttling is not enough. The interesting behaviour is at the transition, not at low bandwidth.
Genuinely disconnect. Turn wifi off, wait, turn it on. Then check: did state re-sync, did presence recover, did queued writes flush, did the indicator update.
Test a long gap, not five seconds. Backoff means a two-minute outage behaves differently from a two-second one.
And test a backgrounded tab on a phone, which is where users actually meet this.
Cleanup
Disconnect when you are done — component unmount, or leaving the part of the app that needs the object.
Check the SDK version for the exact method. Leaving connections open accumulates them, and the symptom is presence lists that fill with ghosts and eventual limits.
The short version
One connection per client instance, opened automatically, so create one client and share it.
The interesting part is the gap — verify whether you receive missed changes on reconnect, and re-read state if you cannot rely on it.
Show connection state, because a silent reconnect looks like a hang.
Presence flickers on reconnect — debounce the leave event.
And test by actually disconnecting, for a long enough gap that backoff is involved.