ESC

Type to search the knowledge base.

Page Lifecycle Events

active, passive, hidden, frozen, discarded — Page Lifecycle states and the events you should actually use.

intermediate3 min read
  • browser
  • page-lifecycle
  • visibility
  • bfcache
  • performance

Mobile browsers aggressively suspend, freeze, and discard background tabs. If you only know load/unload, you’ll lose analytics, leak connections, and block bfcache. The Page Lifecycle model names states and preferred events.

Docs: Page Lifecycle API, MDN Page Lifecycle (related visibility).

States (conceptual)

active → passive → hidden → frozen → discarded
         ↘ terminated (crash / hard kill)
State User-facing
active Foreground, input possible
passive Visible but another window has focus (desktop)
hidden Not visible
frozen JS timers paused; may resume (bfcache-like freeze)
discarded Gone from memory; next view is a full reload

Not every browser exposes every transition equally; code to progressive enhancement.

Events to prefer

Prefer Avoid
visibilitychange Relying only on blur
pagehide unload
pageshow Assuming always cold start
freeze / resume (where available) Ignoring freeze
document.addEventListener('visibilitychange', () => {
  if (document.visibilityState === 'hidden') {
    navigator.sendBeacon('/ingest', sessionSummary());
    pauseVideo();
  } else {
    resumeVideo();
  }
});

window.addEventListener('pagehide', (e) => {
  // e.persisted → may enter bfcache
  teardownRealtime({ keepForBfcache: e.persisted });
});

Why unload is toxic

unload breaks bfcache eligibility in many cases and is unreliable on mobile (tab kill without firing). Use pagehide + sendBeacon / fetch keepalive.

Practical policies

  1. Analytics: flush on visibilitychange hidden / pagehide.
  2. WebSockets: close or heartbeat carefully when hidden; reconnect on visible / pageshow persisted.
  3. Media: pause when hidden.
  4. Timers: don’t assume setInterval runs in background.
  5. Auth: revalidate on resume from bfcache — bfcache.

discarded

Chrome may discard background tabs under memory pressure. Next navigation reloads document. Design for restartable state (persist drafts to IDB/localStorage).

Interview out-loud

“Page Lifecycle covers active through frozen and discarded. I use visibilitychange and pagehide instead of unload so mobile and bfcache work. On pageshow with persisted I revalidate session and reconnect.”

Testing freeze/discard

Chrome’s Lifecycle helpers and “Discard tab” experiments (where available) help validate resume paths. Manually: background the tab for minutes on Android, return, confirm sockets reconnect and forms still have drafts from IDB. Assume timers did not run while frozen.

Analytics integrity

Session duration and bounce metrics go wrong when you only listen to unload. Mobile may kill the tab without firing it. Prefer visibilitychange for “session engaged” heartbeats and pagehide for final beacons. Deduplicate with a session id in sessionStorage so restored bfcache pages do not look like brand-new sessions unless you intentionally define them that way.

Further depth

Teams often under-invest in this topic until an incident or CWV regression. Schedule a one-hour drill: reproduce the failure mode in DevTools, list the top three mitigations for your stack, and file tickets with owners. Revisit after the next major feature that touches networking, rendering, auth, or third parties — those are the moments regressions land. Keep primary documentation links in the runbook so on-call is not searching chat history at 2am.

Concrete artifacts to leave behind: a short architecture note, a CI assertion or header snapshot, and a dashboard panel (lab or field) that would have caught the last bug. Teaching the rest of the team the mental model matters as much as the one-line fix.

Further reading

Related guides