Glossary

Why Beforeunload vs Pagehide Decides If Analytics Survive

Taras Shynkarenko
Taras Shynkarenko
Updated: 6 min read
Why Beforeunload vs Pagehide Decides If Analytics SurviveWhy Beforeunload vs Pagehide Decides If Analytics Survive

TL;DR, Quick Answer

6 min read

The choice between beforeunload vs pagehide matters because beforeunload does not reliably fire on mobile and, in Firefox, disqualifies a page from the back-forward cache. Pagehide fires on the same navigations without that bfcache cost, and MDN recommends visibilitychange first, pagehide as the fallback, paired with sendBeacon to flush analytics before a page goes away.

Why does beforeunload vs pagehide matter for flushing analytics?

Choosing correctly between beforeunload vs pagehide decides whether an analytics event actually reaches the server before a user leaves the page. MDN documents beforeunload as not reliably fired, especially on mobile: a user who switches to another app and later closes the browser from the app manager never fires the event at all. Use pagehide or visibilitychange to flush data instead of beforeunload, and reserve beforeunload for the one job it still does, warning a user about unsaved changes. Getting this wrong is one of the quieter reasons a real user monitoring setup undercounts exits.

What does the beforeunload event actually do?

The beforeunload event fires just before a page unloads and can show a browser-native confirmation dialog to warn a user about unsaved changes. MDN's own guidance is to add the listener only when there are unsaved changes to protect, then remove it once those changes are saved, instead of leaving it attached for the life of the page. That narrow use case is also the only one it is still suited for, since its reliability as a general-purpose exit signal is not there.

A hand swiping through open apps on a phone, the app-switcher moment that skips exit events on mobile.

Why is beforeunload unreliable on mobile?

Beforeunload is unreliable on mobile because a browser closed from the task switcher, or an app backgrounded and never reopened, skips the event entirely, which MDN calls out directly with the app-switch-then-close scenario. A desktop tab closed with the window controls fires the event; the same sequence on a phone, backgrounding the app and closing it from the app manager, skips the event entirely. Any analytics call that depends solely on beforeunload firing loses data on exactly the platform where sessions end this way.

Same close, different outcome
Desktop tab close
  • User clicks the window controls
  • Beforeunload fires
Mobile app switch
  • User backgrounds the app, then closes it from the app manager
  • Beforeunload never fires
MDN's app-switch example shows the same exit path skipping the event entirely on mobile.

How does beforeunload affect the back-forward cache?

Beforeunload's effect on the back-forward cache differs by browser: Firefox will not place a page in bfcache if it has a beforeunload listener attached, while web.dev's bfcache guide notes that beforeunload no longer disqualifies a page from bfcache in other modern browsers, though it did previously. web.dev still calls the event "unreliable, so avoid using it unless absolutely necessary," and recommends adding the listener conditionally, only while unsaved changes exist, instead of on every page load. A page that skips bfcache reloads from scratch on a back navigation instead of restoring instantly from memory.

A person closing a laptop, the moment a page hides before the browser shows a different one.

What does the pagehide event do differently?

The pagehide event fires when the browser hides the current page while presenting a different page from session history, such as a click on the back button, and unlike beforeunload and unload, a pagehide listener does not make a page ineligible for bfcache. MDN recommends visibilitychange as the more reliable signal first, with pagehide as the fallback for browsers where visibilitychange is not available. Attach the flush logic to pagehide when a page needs a bfcache-safe listener that still fires on the same navigation moments beforeunload was meant to catch, which is also the moment a session timeout clock would otherwise keep running against a page nobody is looking at.

EventFires reliably on mobile?Blocks bfcache?Best use
unloadNo, MDN calls it "extremely unreliable" on mobileYes, on desktop Chrome and FirefoxAvoid; legacy code only
beforeunloadNo, per MDN's app-switch exampleNot in modern browsers, per web.devWarn about unsaved changes only
pagehideFallback signal per MDNNoFlush analytics when visibilitychange is unavailable
visibilitychangeMDN's recommended primary signalNoFlush analytics on tab hide, first choice

Where does sendBeacon fit alongside pagehide?

sendBeacon fits alongside pagehide as the delivery mechanism, since it queues an asynchronous POST request that the browser keeps trying to send even as the page goes away, without delaying the navigation the user is already making. MDN recommends pairing it with the visibilitychange event as the primary trigger and pagehide as the fallback, instead of firing it from unload or beforeunload. A single sendBeacon call is capped at roughly 64 KiB; a payload larger than that needs fetch() with the keepalive option instead.

How should an analytics script combine these events?

An analytics script should listen for visibilitychange and check for document.visibilityState === "hidden" as the primary flush trigger, add pagehide as a fallback for browsers or situations where visibilitychange does not fire, and call sendBeacon in both handlers to send the payload without blocking navigation. Flowsery's lightweight script ships under 10 KB precisely so this listener logic adds negligible weight to a page that is already trying to leave. Skipping straight to beforeunload for this job is the one shortcut that costs the most data on mobile, and it is the same shortcut that quietly deflates a bounce rate vs exit rate count when the final event on a page never reaches the server.

Flushing analytics before a page goes away
1
Listen for visibilitychange. Check for document.visibilityState === "hidden" as the primary signal.
2
Add pagehide as a fallback. Covers cases where visibilitychange does not fire.
3
Send with sendBeacon. Queues an async POST that does not delay navigation.
4
Leave beforeunload for unsaved changes only. Add it conditionally, remove it once saved.
The order MDN and web.dev recommend for a reliable, bfcache-safe exit flush.

Frequently Asked Questions

Is beforeunload still useful for anything?

Beforeunload is still useful for warning a user about unsaved changes through a browser-native confirmation dialog. MDN's own recommendation is to attach the listener only while unsaved changes exist and remove it once they are saved, instead of using it as a general page-exit signal.

Why does pagehide not block the back-forward cache the way beforeunload once did?

Pagehide does not block bfcache because it was designed as part of the Page Lifecycle API specifically to signal a page transition without the side effects unload and beforeunload carry. MDN states plainly that, unlike unload and beforeunload, a pagehide listener does not make a page ineligible for bfcache.

Should visibilitychange replace pagehide entirely?

Visibilitychange should be the primary signal, with pagehide kept as a fallback, per MDN's own guidance. Some browsers or contexts do not fire visibilitychange in every exit scenario, so pagehide catches the cases visibilitychange misses instead of replacing it outright.

What happens if an analytics call uses fetch instead of sendBeacon on page exit?

A plain fetch call started during page exit can be cancelled before the browser finishes sending it, since the page is already unloading, and the resulting AbortError is a side effect of the exit rather than a failed request. sendBeacon exists specifically to avoid that: the browser accepts the request and keeps trying to deliver it independent of whether the page has already gone away, up to its roughly 64 KiB limit.

Does the back-forward cache affect analytics at all?

The back-forward cache restores a page from memory on a back or forward navigation instead of reloading it, which means a page's JavaScript does not re-run and any pageview logic that depends on a fresh page load does not fire again. A pageshow event with event.persisted === true is the signal that a restore happened, which analytics logic needs to check for separately from a first load.

Why does Firefox treat beforeunload differently from other browsers for bfcache?

Firefox disqualifies a page from bfcache if it has a beforeunload listener attached, a stricter stance than browsers that stopped disqualifying beforeunload pages for bfcache after previously doing so. The safest choice across browsers is still to add a beforeunload listener only when unsaved changes exist, since that keeps the listener off the page during the navigations where bfcache eligibility matters most.

Flowsery
Flowsery

Start FREE Trial

Real-time dashboard

Goal tracking

Cookie-free tracking

What happens if an analytics payload is larger than sendBeacon's 64 KiB limit?

A sendBeacon call capped at roughly 64 KiB rejects a larger payload outright, so it never reaches the server. Switching to fetch with the keepalive option handles payloads above that size while still surviving a page that is already unloading.

Is the unload event a safer fallback than beforeunload?

Unload is worse, not safer. MDN calls it extremely unreliable on mobile, and it still blocks bfcache on desktop Chrome and Firefox, the exact cost pagehide was built to avoid. Treat it as legacy code to avoid rather than a fallback to reach for.

Does adding pagehide and visibilitychange listeners make a page heavier?

Flowsery ships its script under 10 KB specifically so this listener logic adds negligible weight to a page that's already trying to leave. The cost sits in the sendBeacon payload size, not in the event listeners themselves.

How does flushing on pagehide affect session timeout tracking?

Pagehide fires the moment a page hides behind another one in session history, which is also the moment a session timeout clock would otherwise keep running against a page nobody is looking at. Flushing there closes out the session at the right instant instead of letting idle time accumulate against a tab the user already left.

Was This Article Helpful?

Let us know what you think!

See us more often in Google

One click marks Flowsery as a preferred source, so our articles sit higher in your Top Stories, AI Mode, and AI Overviews.

Before you go...

Flowsery

Flowsery

Revenue-first analytics for your website

Track every visitor, source, and conversion in real time. Simple, powerful, and cookie-free.

Real-time dashboard

Goal tracking

Cookie-free tracking

Related Glossary Terms

Related Articles