5.2 The lifecycle flusher: recipe, bfcache, and why unload is out
One flush per hidden transition, bfcache restores identified precisely via persisted pageshow, and the layered case against unload.
Intermediate
The lifecycle rules from the previous page compile into one small class: flush exactly once per hidden transition, notice back/forward-cache restores without hallucinating them, and never touch
unload. This page builds that class, then defends each design decision against the failure modes that real analytics SDKs have shipped with.
The recipe
The flusher wires three listeners around one latch. The
visibilitychange handler checks the state and flips the latch before flushing — so a re-entrant event or the pagehide that follows cannot double-send. The
pagehide listener is a fallback for the rare ordering where it arrives without a prior visibility change; the latch makes it a no-op in the normal sequence. And
pageshow with
persisted === true is the only restore signal — for reasons the next section makes precise:
Output
Ready to run.
The output is the contract in miniature: one flush for the tab-switch, no second flush when pagehide follows the hidden transition, nothing on plain tab-switch-back, and the restore hook firing exactly once for the persisted pageshow. The injectable
getVisibility and
send options exist so tests can drive the state machine — in production the defaults read the real document and call the real
navigator.sendBeacon.
bfcache: why restores must be identified precisely
A page restored from the back/forward cache resumes mid-execution: timers, listeners, and module state survived the freeze. Two consequences follow. Analytics must count the restore as a new pageview — tagged as a restore, or dashboards double-count nothing but undercount returns — and anything session-scoped may need re-initializing. The trap is that
visibilitychange → visible fires on every ordinary tab-switch-back too; only
pageshow with
persisted distinguishes an actual bfcache restore from a user coming back from lunch. The flusher therefore unlatches its state on the visible transition (so the next hidden can flush) but defers the restore callback to the persisted pageshow — splitting the two concerns that naive SDKs merge into one spurious 'restore' per tab switch.
Why unload and beforeunload are absent
The recipe registers no
unload or
beforeunload listeners, and the reasoning is layered.
Reliability: neither fires in the scenarios where mobile pages actually die — app switches and OS kills — so code keyed on them is silent exactly when it matters most.
Compatibility cost: pages that register unload handlers are excluded from bfcache in major engines, trading away the fastest possible back-navigation for a signal that arrives unreliably anyway; browsers have been progressively deprecating the event on those grounds (a browser-policy evolution — not spec text — that current engine documentation tracks).
Redundancy: everything unload could still deliver here, the hidden transition already delivers, earlier and with a spec guarantee behind it. When a migration genuinely requires touching the old events, the flusher's
flushNow() is the escape hatch — callable from anywhere, including a guard you control, without the lifecycle core ever depending on those events.
Quick check
The page fires visibilitychange → visible after a plain tab switch back. What should the flusher do?
// inside the visibilitychange handler
if (document.visibilityState === 'visible') {
}
Key takeaways
-
One flush per hidden transition: a latch set before
flush()makes visibilitychange-then-pagehide idempotent. -
pagehide stays as a latched fallback;
flushNow()is the manual escape hatch. -
bfcache restore =
pageshowwithpersisted === true— never the visible transition alone. - No unload/beforeunload: unreliable on mobile, bfcache-hostile, redundant given the hidden-transition guarantee.
- Injectable getVisibility/send make the whole state machine testable without a browser's cooperation.
Thanks for your feedback.
Mark this learning resource complete to track your learning progress.