Back/Forward Cache Eligibility Gate
Treat BFCache compatibility as a launch gate: a page or platform feature is not ready until it is safe to freeze, keep in memory, restore on history navigation, and diagnose when restoration is denied.
The back/forward cache, usually shortened to BFCache, is not the HTTP cache. It is a browser-owned history-navigation cache for whole documents. When the user leaves a page, Chromium may freeze the document, keep its DOM and JavaScript heap in memory, and restore that exact state when the user presses Back or Forward. The fastest navigation is the one the browser doesn’t redo.
That speed changes the rules. Code that assumes navigation away destroys the document is wrong on a BFCache path. Code that cleans up in unload, keeps open resources alive, or forgets that a document can be non-fully-active may make the page ineligible. The gate means designing for that lifecycle first, then using DevTools and PerformanceNavigationTiming.notRestoredReasons to explain every miss.
Context
Chromium has several caches, and confusing them causes bad designs. The HTTP cache stores network responses. The code cache stores compiled script artifacts. BFCache stores a live page: the document, its DOM tree, its JavaScript heap, its scroll position, and enough browser-side state to put the user back where they were during a same-tab history traversal.
The browser decides whether a document is eligible when the user navigates away. If it is eligible, Chromium freezes or pauses activity that cannot run while the document is inactive. If the user returns through history, the browser restores the document instead of constructing a new one through the normal network and commit path. This makes Back and Forward feel instant, especially on pages whose original load needed substantial script, network, or layout work.
The pattern matters to web authors, Chromium feature teams, and downstream embedders for the same reason: a document that is not destroyed must remain safe. It may hold authenticated state, active JavaScript objects, pending timers, resource handles, or feature-specific browser objects. A new platform API that works only when a document is fully active, or that leaks cross-page state when frozen, is not BFCache-safe until it defines what happens during freeze and restore.
Problem
History navigation is common and user-visible, but many page and platform designs still assume the old lifecycle: navigate away, run cleanup, destroy the document, later load a fresh copy. BFCache breaks that assumption deliberately. It keeps the old document alive so the user’s next Back or Forward action is cheap.
The recurring problem is that eligibility is lost for reasons the team cannot see. An unload handler blocks caching. A response policy or open resource handle makes the document unsafe to store. A new web-platform feature fails to specify what happens when its owning document is frozen. Analytics records a “page load” that was really a restore, cleanup code runs at the wrong lifecycle event, or an enterprise embedder cannot explain why one frame restores and another does not.
Forces
- Instant history navigation vs. document lifecycle assumptions. Users expect Back and Forward to answer immediately, while old cleanup code expects navigation away to destroy the page.
- Memory retention vs. correctness. Keeping a whole document alive costs memory and can preserve sensitive state longer than a reload path would.
- Feature ambition vs. frozen-document safety. New APIs often want active connections, handles, or callbacks; BFCache requires a defined behavior when the owning document is not fully active.
- Compatibility vs. diagnosability. A page may fail the gate for one frame, one handler, or one resource, and the team needs a reason precise enough to fix.
- Staged rollout vs. ecosystem breakage. Browser changes to eligibility rules can make more pages fast, but they can also expose lifecycle bugs that sites have carried for years.
Solution
Make BFCache eligibility a hard review gate for page lifecycle code and for new Chromium web-platform features. A design passes only when it can answer four questions: what happens on navigation away, what state may survive, what must pause or close while the document is not fully active, and how a failed restore will be diagnosed.
Use pagehide and pageshow as the lifecycle boundary, not unload. The pagehide event fires when the page is being hidden or put into the cache, and pageshow fires when it is shown again; both expose a persisted value that tells code whether BFCache is involved. Cleanup that must run for a true destruction path belongs on the non-persisted branch. State refresh that must run after a BFCache restore belongs on pageshow when persisted is true.
For feature design, require behavior for non-fully-active documents before launch. A feature that owns a browser-side resource must say whether the resource is closed, suspended, detached, or marked ineligible when the document enters BFCache. A feature that can expose privacy-sensitive or origin-sensitive data must say what happens if the page is restored after the user has navigated elsewhere in the same tab. If the feature cannot be made safe, it should block BFCache deliberately and name that blocker in diagnostics.
For debugging, start from the browser’s reasons rather than guessing. Chrome DevTools includes a Back/Forward Cache diagnostic panel that attempts a navigation and lists restoration blockers. The platform API PerformanceNavigationTiming.notRestoredReasons exposes structured reasons for why the current navigation was not restored from BFCache, including frame-level information when available. That object is the operational counterpart to the review rule: every eligibility decision should be explainable in terms a test, dashboard, or agent can inspect.
An unload handler is the classic BFCache footgun. Prefer pagehide for teardown and pageshow for restore work, and branch on event.persisted when the BFCache path needs different behavior.
How It Plays Out
An enterprise browser fork injects a compliance script into every managed page. The script writes an audit beacon in unload, because the original design assumed the page was gone after navigation. Back navigation feels slow across the deployment, and DevTools reports unload as the blocker. The fix is to move the beacon to pagehide, use navigator.sendBeacon() only for the non-persisted path, and leave long-lived page state alone on the persisted path. Back navigation becomes instant again, and the audit path still runs when the page is actually leaving memory.
A Chromium feature team designs an API that keeps a browser-process handle associated with a document. The first design says nothing about history traversal. During review, BFCache analysis asks what happens when the document becomes non-fully-active. Keeping the handle live would allow callbacks into a frozen document; closing it silently would break restore; serializing it would preserve too much state. The team chooses an explicit ineligibility reason for the first launch, guards the feature behind a flag, and files follow-up work to make the handle suspendable before default-on exposure. The eligibility gate turns an implicit lifecycle bug into a named launch decision.
A downstream WebView2 integration sees inconsistent Back behavior after a Chrome milestone update. Some pages restore instantly, while a checkout flow always reloads. Rather than diffing the whole app, the team records performance.getEntriesByType("navigation")[0].notRestoredReasons after the failed traversal and finds a single cross-origin subframe with an open resource blocker. The team cannot change that third-party frame, but it can stop blaming Chromium’s navigation stack and can brief product support accurately: the top-level page is eligible; one child frame is not.
Consequences
Benefits. Back and Forward navigation become a performance feature the project can reason about rather than a lucky fast path. A page that follows the gate avoids lifecycle code that disables the cache by accident. A Chromium feature that passes the gate has specified its behavior while the document is frozen, which makes privacy review and compatibility review sharper. A downstream vendor has diagnostics that point to a reason instead of a vague “BFCache missed.”
Liabilities. BFCache trades CPU and network work for memory. Keeping documents alive increases memory pressure, and a browser under pressure may evict cached entries even when the page is otherwise eligible. The pattern also forces cleanup code to become more precise: some work belongs on true destruction, some on hide, some on restore, and some not at all. That split is harder than a single unload hook.
The gate can slow feature launch. A new API that touches browser resources, permissions, connections, storage, or origin state now has one more lifecycle to specify. That cost is intentional. A feature that cannot say what happens to a non-fully-active document is not ready for broad exposure, because BFCache makes that state routine rather than exceptional.
The diagnostic surface is only as useful as the reasons are stable. A notRestoredReasons value can be redacted or frame-scoped for privacy, and DevTools may show a richer local view than production telemetry exposes. Treat the reason as the starting point for a fix, not as a complete substitute for reproducing the navigation with tracing and frame context.
Notes for Agent Context
When adding page-lifecycle code for Chromium-targeted web content, never generate unload-based cleanup. Use pagehide and pageshow, check event.persisted, and keep the BFCache restore path distinct from a normal reload path.
When implementing or reviewing a Chromium web-platform feature, specify behavior for non-fully-active documents before launch. Decide whether each browser-side resource is suspended, closed, restored, or makes the document BFCache-ineligible, and expose a diagnostic reason when ineligibility is intentional.
When debugging slow Back or Forward navigation, inspect DevTools’ Back/Forward Cache panel and PerformanceNavigationTiming.notRestoredReasons before changing code. Do not assume a reload happened; a BFCache restore preserves DOM and JavaScript state, and a missed restore needs a named blocker.
Related Articles
Sources
The Chromium project’s BFCache overview in docs/bfcache.md is the primary project source for the implementation model and the rule that new features cannot defer BFCache analysis until after launch. Philip Walton and Barry Pollard’s web.dev guide to the back/forward cache gives the web-author lifecycle model, including the distinction between BFCache and the HTTP cache, pagehide / pageshow, event.persisted, and common blockers such as unload. Chrome DevTools documentation describes the Application-panel Back/Forward Cache test and its blocker report. Chrome’s notRestoredReasons documentation describes the structured PerformanceNavigationTiming.notRestoredReasons API. The blink-dev Intent to Experiment and Intent to Ship threads for desktop BFCache record the rollout, platform limits, enterprise-policy considerations, unload-handler risk, and compatibility work. Rakina Zata Amni’s W3C TAG guide, Supporting BFCached Documents, frames the standards-side requirement for APIs to work with non-fully-active documents.
Technical Drill-Down
- Chromium BFCache overview (pinned
962b29a) — project-level implementation notes and launch-review expectations for new Chromium features. web.dev— Back/forward cache — web-author lifecycle guidance, includingpagehide,pageshow,event.persisted, and common eligibility blockers.- Chrome DevTools — Test back/forward cache — the operational panel for reproducing a traversal and reading the blocker report.
- Chrome for Developers — Back/forward cache notRestoredReasons API — structured diagnostics exposed through
PerformanceNavigationTiming.notRestoredReasons. - blink-dev — Intent to Experiment: Back-forward cache for desktop — the desktop experiment record, including compatibility and standards considerations.
- blink-dev — Intent to Ship: Back-forward cache for desktop — the launch record for desktop BFCache rollout and its risk analysis.
- W3C TAG — Supporting BFCached Documents — standards-side guidance for API designers whose features interact with non-fully-active documents.