demo · v149

Heartbeat Recovery

Many WebSocket clients send periodic heartbeats (ping/pong or application-level keepalives) to keep the connection alive and detect server-side disconnects. When Chrome 149 closes the WebSocket on BFCache entry, the heartbeat timer must be cancelled on freeze and restarted with a fresh connection on restore — otherwise the restored page sends heartbeats to a dead socket.

Simulate a BFCache freeze/restore cycle and watch the heartbeat lifecycle. A correctly-written handler cancels the heartbeat interval in pagehide, reconnects in pageshow(persisted:true), and restarts the heartbeat. An incorrect handler keeps the old interval running and detects the dead socket eventually via a missed pong.
Heartbeat interval: 1.5s

WebSocket State

SIMULATED
Heartbeat not started
HB interval:
Last ping:
Missed pongs: 0

BFCache State

Active
Freeze count: 0
Restore count: 0

Handler Mode

Correct: cancels interval on freeze, reconnects + restarts on restore.
0msSimulation ready. Connect to begin.
Heartbeats sent0
Missed pongs0
Reconnects0
HB after dead socket0
// Correct pattern: cancel heartbeat on freeze, restart on restore

let ws = null;
let heartbeatTimer = null;
const HB_INTERVAL = 15000; // 15 seconds

function connect() {
  ws = new WebSocket('wss://example.com/ws');
  ws.addEventListener('open', () => startHeartbeat());
  ws.addEventListener('close', () => stopHeartbeat());
  ws.addEventListener('message', (e) => {
    if (e.data === 'pong') resetMissedPongs();
  });
}

function startHeartbeat() {
  stopHeartbeat(); // always clear before starting
  heartbeatTimer = setInterval(() => {
    if (ws?.readyState === WebSocket.OPEN) {
      ws.send('ping');
    }
  }, HB_INTERVAL);
}

function stopHeartbeat() {
  clearInterval(heartbeatTimer);
  heartbeatTimer = null;
}

// Chrome 149: WebSocket is closed on BFCache entry
window.addEventListener('pagehide', (e) => {
  if (e.persisted) {
    stopHeartbeat(); // cancel — socket is closed by Chrome 149
  }
});

window.addEventListener('pageshow', (e) => {
  if (e.persisted) {
    connect();      // reconnect on restore
    // startHeartbeat() is called by the 'open' event
  }
});

see also

implementation reference

Need the exact API surface, compatibility boundaries, errors, lifecycle, and source links? Read the matching gendn reference ↗