Your unload Handler Is Dead: What Chrome and Edge Are Doing to Your Analytics
Chrome and Edge 152 are deprecating the unload event to unlock bfcache for everyone. If your analytics, session cleanup, or third-party libraries still use it, you are silently losing data for a growing chunk of your users.
Your unload Handler Is Dead
Edge 152 shipped August 27 and quietly started blocking unload from firing on 60% of page loads. Edge 154 takes that to 80%. Edge 155 goes to 100%. Chrome has been running the same rollout for months across roughly 8 milestones. As of September 2026, a meaningful chunk of your users are already experiencing silent failures wherever your code calls window.addEventListener('unload', ...).
This is not a surprise announcement — browser vendors have been telegraphing this for over a year. But "everyone knew it was coming" does not mean teams actually did the migration.
Why Browsers Are Doing This
The unload event was designed for a simpler era when navigating away from a page meant the page was gone. Modern browsers have the back/forward cache (bfcache), which freezes a page's full JavaScript state in memory so the back button is instant — no network request, no re-render, no hydration delay.
The problem is these two things are incompatible. A page with an unload handler cannot be safely frozen, because the browser would need to fire the event *and* keep the page alive — contradictory. So historically, browsers chose your event over bfcache eligibility. Now they are flipping that decision.
Chrome's telemetry put the impact at around 18 percentage points of reduced bfcache hit rate attributed to unload handlers. That is a significant portion of navigations that could be instant but aren't.
The key insight: visibilitychange fires at the transition from Active to Hidden — before the browser decides whether to freeze or terminate. When document.visibilityState === 'hidden', you still have time to act. That is your new cleanup hook.
What Actually Breaks
**Analytics.** The most common use case. Tracking event or flush call inside unload to record session duration, final scroll depth, and exit page. This has been unreliable on mobile for years — iOS Safari never guaranteed it — but worked well enough on desktop that teams kept it.
**Session cleanup.** Calling an endpoint on unload to close a server-side session or release a lock. These requests often don't complete because there is no guarantee they go out before the page is destroyed.
**Form dirty-state warnings.** If you're using unload for this, you actually want beforeunload — a separate event that is still supported. unload was the wrong hook here anyway.
**Third-party libraries.** The sneaky one. Your own code might be clean, but older versions of analytics SDKs, support chat widgets, and session replay tools have used unload internally. Check your bundled vendor scripts before assuming you're safe.
The Migration Playbook
Switch analytics to visibilitychange + sendBeacon
navigator.sendBeacon() is the right primitive here. It is designed exactly for the case of "send this HTTP request even if the page is going away." The browser queues it and delivers it independently of the page lifecycle.
document.addEventListener('visibilitychange', () => {
if (document.visibilityState === 'hidden') {
navigator.sendBeacon('/analytics', JSON.stringify({
sessionId,
timeOnPage: Date.now() - pageStartTime,
}));
}
});This covers tab switching, navigating away, locking the phone, and minimizing the browser. On a bfcache-eligible page it fires before the page is frozen — and when the user comes back, the page resumes without re-running your initialization code.
Use pagehide when you need to know if bfcache is involved
If you need to close resources like WebSocket connections or IndexedDB transactions, pagehide gives you a persisted flag:
window.addEventListener('pagehide', (event) => {
if (!event.persisted) {
// Page is being destroyed, not frozen
db.close();
socket.close();
}
// If persisted is true, page is going into bfcache.
// Leave connections open so they survive the freeze.
});If you close a WebSocket when persisted is true, you will have a broken connection when the user hits back. Leave it open, and use the pageshow event to handle restoration on resume.
The Escape Hatch
If you need breathing room — say, a third-party library you cannot update immediately — the Permissions-Policy header can re-enable unload for your origin:
Permissions-Policy: unload=*Treat this as a stopgap. You are trading instant back-button navigation for a deprecated event, and future browser versions will remove the policy option entirely. Put a date on removing it.
What to Do Right Now
**Grep your codebase**: search for
addEventListener.*unloadacross source files and vendor bundles. Do not forget inline scripts in server-rendered HTML.**Run the bfcache report**: Chrome DevTools → Application → Back/forward cache lists every reason your page is currently ineligible, including third-party scripts blocking it.
**Audit your analytics library version**: GA4, Segment, and most modern SDKs have already migrated internally. Older self-hosted or pinned versions may not have.
**Add the Permissions-Policy header** as a temporary measure if you need it, but put a calendar reminder to remove it before Edge 155 ships.
The migration is a half-day of work at most. The payoff is instant back-button navigation for every user on a modern browser — which is already most of your users.