TL;DR, Quick Answer
6 min readThe 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.

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.
- User clicks the window controls
- Beforeunload fires
- User backgrounds the app, then closes it from the app manager
- Beforeunload never fires
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.

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.
| Event | Fires reliably on mobile? | Blocks bfcache? | Best use |
|---|---|---|---|
| unload | No, MDN calls it "extremely unreliable" on mobile | Yes, on desktop Chrome and Firefox | Avoid; legacy code only |
| beforeunload | No, per MDN's app-switch example | Not in modern browsers, per web.dev | Warn about unsaved changes only |
| pagehide | Fallback signal per MDN | No | Flush analytics when visibilitychange is unavailable |
| visibilitychange | MDN's recommended primary signal | No | Flush 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.
document.visibilityState === "hidden" as the primary signal.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
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
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


What the Numbers Say About Average Bounce Rate by Industry
Nine tracked industries return a documented average bounce rate by industry ranging from 35.76% to 48.38%, sourced from Databox data dated September 2024.


Why Pageviews vs Sessions vs Users Never Match on One Report
Comparing pageviews vs sessions vs users shows three separate counts that roll up into each other and rarely land on the same number twice.


How Session Timeout Rules Quietly Inflate Your Analytics
A session timeout closes an idle visitor's session, and the default 30-minute rule is why the same traffic reports different counts.


How to Tell What Is a Good Conversion Rate for Your Site
Benchmarks answer what is a good conversion rate differently for ecommerce, SaaS and lead gen, and a single median number hides more than it reveals.


What a Session Is in Web Analytics
In web analytics, a session is one group of a visitor's interactions, closed by inactivity, midnight, or a campaign change.


How Session Replay Works, and What It Cannot See
A session replay rebuilds a visit from DOM mutations and input events, not video. See what it captures, what masking hides, and how it differs from heatmaps.
Related Articles


Working Through the Average Order Value Formula Step by Step
The average order value formula divides revenue by orders, and a single discount code or return policy can quietly distort every number a team reports.


What Average Session Duration Actually Measures
In classic analytics, average session duration gives zero recorded time to the very last pageview of every session, which quietly drags the average down.


How Browser Fingerprinting Identifies You Without a Cookie
A combination of canvas rendering, installed fonts, screen size and timezone is what browser fingerprinting turns into an identifier that survives deletion.

