Heartbeat and reconnection

Connections die without announcing themselves. A client closes their laptop, loses wifi, or has their network drop for thirty seconds. TCP will eventually notice, but eventually can be minutes. Until then, your server still thinks the client is there and keeps sending messages into the void. Heartbeat is how you catch it sooner.

after/websocket/heartbeat.ts
typescript
import { WebSocketServer, WebSocket } from 'ws';

interface TrackedSocket extends WebSocket {
  isAlive?: boolean;
}

export function installHeartbeat(wss: WebSocketServer) {
  wss.on('connection', (ws: TrackedSocket) => {
    ws.isAlive = true;
    ws.on('pong', () => {
      ws.isAlive = true;
    });
  });

  const interval = setInterval(() => {
    for (const client of wss.clients as Set<TrackedSocket>) {
      if (client.isAlive === false) {
        client.terminate();
        continue;
      }
      client.isAlive = false;
      client.ping();
    }
  }, 30_000);

  wss.on('close', () => clearInterval(interval));
}

Ping every 30 seconds. If the client does not pong back, terminate the connection.

Read the loop carefully. Every 30 seconds, for every connected client, we check if it was alive on the last cycle. If not, we terminate it. Then we mark it as potentially dead and send a ping. If the client is still there, it replies with a pong and we flip the flag back to alive before the next cycle. Simple, effective, and catches every dead socket within a minute.

On the client side, the other half of this story is reconnection. When a socket closes, wait a short random delay and reconnect. Add exponential backoff if it keeps failing. Real-world clients lose connections all the time. Reconnect is a user experience feature, not an edge case.

Validation checklist: Run the websocket server

Loading practice…

Checkpoint: Realtime WebSockets checkpoint

Loading practice…