visibilitychange and Page Hide
visibilitychange, pagehide, and pageshow: flush analytics, pause media, and survive mobile backgrounding.
- browser
- visibilitychange
- pagehide
- page-lifecycle
- analytics
Mobile users background your tab constantly. blur/focus are incomplete; unload is unreliable and bfcache-hostile. The durable pair is visibilitychange for “can the user see this?” and pagehide / pageshow for navigation lifecycle including bfcache.
Docs: Page Visibility, pagehide, Page Lifecycle.
visibilitychange
document.addEventListener('visibilitychange', () => {
if (document.visibilityState === 'hidden') {
pauseRealtime();
flushAnalytics();
} else if (document.visibilityState === 'visible') {
resumeRealtime();
}
});
document.hidden / visibilityState reflect whether the page is visible. Use for:
- Pausing video and WebGL
- Throttling polling
- Sending beacons while the page can still network
pagehide / pageshow
window.addEventListener('pagehide', (event) => {
// event.persisted === true → page may enter bfcache
navigator.sendBeacon('/exit', payload());
if (!event.persisted) {
closeExclusiveLocks();
}
});
window.addEventListener('pageshow', (event) => {
if (event.persisted) {
// restored from bfcache — not a cold load
revalidateSession();
}
});
| Event | Fires when |
|---|---|
pagehide |
Page is being unloaded or put into bfcache |
pageshow |
Page shown, including bfcache restore |
visibilitychange |
Visibility flips (tab switch, minimize) |
sendBeacon and keepalive
navigator.sendBeacon('/ingest', blob);
// or
fetch('/ingest', { method: 'POST', body, keepalive: true, credentials: 'include' });
Prefer these on hidden/pagehide paths so the request outlives the page.
Don’t use unload
// Avoid
window.addEventListener('unload', () => { /* ... */ });
Breaks bfcache eligibility and often never runs on mobile process death. See bfcache.
SPA notes
Client-side route changes may not fire pagehide. Hook your router for soft navigations and still keep visibility handlers for tab switches.
Interview out-loud
“I flush analytics and pause media on visibilitychange hidden, use pagehide with sendBeacon instead of unload, and on pageshow persisted I revalidate because bfcache restored the page with old JS state.”
Double-firing guards
visibilitychange to hidden and pagehide can both fire on the same leave. Use a flushed flag so analytics doesn’t double-count sessions. Reset the flag on pageshow when appropriate.
Media and realtime matrix
| Resource | on hidden | on visible / pageshow persisted |
|---|---|---|
<video> |
pause | optional resume |
| WebSocket | close or ping backoff | reconnect + resubscribe |
setInterval poll |
stop | restart |
| WebRTC | mute/pause tracks | renegotiate if needed |
| Draft form | persist to IDB | rehydrate |
Write the matrix into the team wiki; different features forget different rows.
Related
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
- BFCache Back Forward CacheHow the back/forward cache freezes pages for instant history nav, what blocks it, and how to restore state safely.
- Page Lifecycle Eventsactive, passive, hidden, frozen, discarded — Page Lifecycle states and the events you should actually use.
- Browser DevTools Network PanelRead waterfalls, timing phases, headers, throttling, and initiator chains in the Network panel like a production debugger.
- Browser DevTools Performance PanelRecord main-thread timelines: long tasks, style/layout/paint, frames, and how to turn flame charts into INP fixes.
- Browser Extension Messaging BasicsHow content scripts, background/service workers, and pages talk: runtime messaging, ports, and security boundaries.