WebSocket Real-Time Apps: Production Patterns
Build resilient WebSocket clients that survive disconnects, network changes, and backpressure.
Reconnect with Backoff
``js
class ReliableSocket {
constructor(url) { this.url = url; this.attempt = 0; this.connect(); }
connect() {
this.ws = new WebSocket(this.url);
this.ws.onopen = () => { this.attempt = 0; };
this.ws.onclose = () => {
const delay = Math.min(30000, 1000 * 2 ** this.attempt++);
setTimeout(() => this.connect(), delay + Math.random() * 1000);
};
}
}
`
The jitter avoids thundering herds when a server restarts.
Heartbeats
Idle connections silently die behind NATs. Send a ping every 25-30 seconds:
`js
setInterval(() => ws.readyState === WebSocket.OPEN && ws.send('"ping"'), 25000);
`
The server replies with pong; if no pong arrives in 10 seconds, consider the connection dead and reconnect.
Resume State
After reconnect, replay missed events. Patterns:
- Server-assigned message IDs; client requests "since X" - CRDT or sync-engine like Yjs handles this transparently
Backpressure
WebSocket has bufferedAmount. Pause large pushes when it grows:
`js
if (ws.bufferedAmount > 1_000_000) await waitForBuffer(ws);
`
Use Sec-WebSocket-Protocol
The subprotocol field versions your wire format. Bumping from chat-v1 to chat-v2` lets servers handle both during deploy.
Server-Sent Events as Alternative
For server-to-client only streams, SSE auto-reconnects and works through proxies more reliably than WebSocket. Worth considering for notifications and live updates.
For HTTP retry patterns see [modern fetch API patterns](/blog/modern-fetch-api-patterns).