Optimizing the Back/Forward Cache (bfcache)
AI generated
60fps
ms
Web Performance / Browser APIs
Optimizing the Back/Forward Cache (bfcache)
Why back navigation is instant sometimes, and not other times

The Back/Forward Cache keeps a complete page state in memory so that navigating back or forward doesn't require a full reload. This article covers common blockers that prevent bfcache, how the Chrome DevTools test tool helps track them down, and just how large the measurable performance gain actually is.

13 min read Back/Forward Cache Navigation Performance

1. What is the Back/Forward Cache

The Back/Forward Cache, bfcache for short, is a browser optimization that freezes an entire page, including its JavaScript heap, DOM tree, scroll position, and running timers, in memory instead of fully destroying it when the user navigates away. When the user then navigates back or forward, the frozen page is simply reactivated, with no new network request, no re-parsing of the HTML, and no re-execution of scripts.

This technique differs fundamentally from the regular HTTP cache, which only caches individual resources like images or stylesheets but doesn't preserve any running JavaScript state. Bfcache, by contrast, preserves the entire execution state of the page, which is why, for example, a half-filled form or an open dropdown selection appears exactly as it was left when the user returns.

2. How It Works: pageshow and pagehide Instead of unload

For a page to be cleanly eligible for bfcache, it needs to handle state transitions through the pagehide and pageshow events instead of relying on the older unload or beforeunload events. When the page is left, pagehide fires, and the event.persisted property indicates whether the page was actually frozen rather than destroyed.

When the user navigates back to the page, pageshow fires again, and here too event.persisted === true signals that this is a restoration from bfcache rather than a fresh page build. Applications that need to react to this state, for example to refresh live data such as stock levels, should trigger a targeted refresh at exactly this point.

3. Code Example: Correct Event Handling for bfcache Compatibility

The most common mistake that blocks bfcache is using an unload event listener, even if it's functionally empty or was only registered for legacy analytics purposes. Chrome treats any registered unload listener as a potential blocker and outright denies bfcache for the affected page.

The clean alternative is to attach any cleanup logic to pagehide and distinguish between actual destruction and freezing for bfcache, as shown in the example below.


// Wrong: reliably blocks bfcache in Chrome
window.addEventListener('unload', () => {
  navigator.sendBeacon('/analytics/leave', payload);
});

// Correct: bfcache-compatible via pagehide
window.addEventListener('pagehide', (event) => {
  if (!event.persisted) {
    // Page is actually being destroyed, not just frozen
    navigator.sendBeacon('/analytics/leave', payload);
  }
});

// React when restored from bfcache
window.addEventListener('pageshow', (event) => {
  if (event.persisted) {
    refreshStockLevels();
  }
});

4. Common bfcache Blockers at a Glance

Besides the unload listener, open WebSocket, WebRTC, or EventSource connections are among the most common blockers, since the browser can't safely freeze a page while it holds an active network connection. Likewise, the response header Cache-Control: no-store on the main document rules out any bfcache use for that page entirely, regardless of the page's other JavaScript behavior.

Other frequent causes include open IndexedDB transactions at the moment of navigation, pending requests for notification permissions or microphone and camera access, and certain plugin instances the browser considers unsafe to release. Any single one of these factors alone can already be enough to exclude the entire page from bfcache eligibility.

5. Using the Chrome DevTools bfcache Test Tool

Chrome offers a dedicated testing tool in the Application tab under the Back/forward cache section, which simulates with a single click whether the currently open page is eligible for bfcache. After running the test, the tool lists every concrete blocking reason individually, grouped into categories such as supported, under experiment, or currently unsupported.

This tool is far more reliable than manually testing with the back button, since it also surfaces subtle blockers such as a background push connection or a pending permission request that are easily missed during normal manual testing. It's worth running this test routinely for every page type after significant frontend changes.

6. Measurable Performance Gain on a Successful Restore

When a page is successfully restored from bfcache, the time to interactivity typically falls in the low double-digit millisecond range, while a full rebuild of the same page, depending on complexity, can take anywhere from several hundred milliseconds to a few seconds. This difference weighs much more heavily on mobile connections with high latency or limited bandwidth than on desktop with a fast wired connection.

Chrome's own research across navigation events has shown that a substantial share of all back and forward navigations historically bypassed bfcache, even though exactly these navigation patterns are particularly common. Every additional page made bfcache-eligible therefore reduces not just individual load time, but also server load, since no new request needs to be sent to the server at all.

7. Interplay With Other Browser APIs

Some browser APIs require extra care in combination with bfcache: a running IndexedDB transaction must be finished before the page is left, since open transactions can block bfcache, and pending fetch requests should ideally be finished or at least cleanly cancellable. The Media Session API state and active WebLocks can also affect bfcache eligibility depending on the browser version.

For forms with autosave functionality, it's advisable to attach the save logic to pagehide rather than beforeunload, since beforeunload listeners no longer categorically block bfcache in newer Chrome versions, but are still treated as a risk factor and can delay bfcache restoration in some cases. Consistently switching to the more modern lifecycle events pays off in the long run here.

8. Best Practices for Classic Multi-Page Applications

For classic, server-rendered multi-page websites, as commonly found in e-commerce, it's worth doing a systematic inventory of all included third-party scripts, since tracking and chat widgets frequently register unload listeners or persistent WebSocket connections without anyone noticing. A central overview of which script causes which bfcache blocker makes targeted fixes much easier.

In addition, any page whose content might have changed by the time the user returns, such as a cart with updated pricing or stock levels, should use the pageshow handler to refresh only the affected sections rather than reloading the entire page. That preserves the speed advantage of bfcache while still keeping the displayed data accurate.

9. Summary

The Back/Forward Cache is one of the most effective, yet also one of the most commonly and unintentionally blocked, performance optimizations in modern browsers, since a single forgotten unload listener or an open WebSocket connection can undo the entire effect. Regularly checking with the Chrome DevTools bfcache test tool should therefore be a standard part of every performance review.

Anyone who consistently avoids the most common blockers and correctly uses lifecycle events like pagehide and pageshow gets, in return, noticeably faster navigation for all users moving back and forth between pages during their session, a navigation pattern that's especially common on category and product pages in online retail.

Blocker Cause Fix
unload event listener Chrome denies bfcache for any registered listener Switch to pagehide with an event.persisted check
Cache-Control: no-store Header rules out bfcache for the page entirely Remove the header or switch to no-cache/max-age
Open WebSocket connection An active connection can't be safely frozen Close the connection cleanly in pagehide
Open IndexedDB transaction A running transaction blocks the page from freezing Finish the transaction before the page is left

Mironsoft

Web performance, Core Web Vitals, and load time optimization

Load times that don't make users bounce before the page is even visible?

We review existing websites for slow Core Web Vitals, bloated JavaScript bundles, and unnecessary render blockers, then build a performance foundation that stays measurable instead of just looking good once.

Performance Audit

Systematically measuring and fixing Core Web Vitals, load waterfall, and render blockers.

Bundle Optimization

Specifically reducing JavaScript and CSS bundle size and improving code splitting.

Monitoring Setup

Establishing continuous performance monitoring instead of a one-time snapshot.

10. Zusammenfassung

Back/Forward Cache

Technique

Entire page state frozen in memory

Most common blocker

unload event listeners and open WebSockets

Test tool

Chrome DevTools Application tab, bfcache section

Gain

Restoration in the low double-digit millisecond range

11. FAQ: Back/Forward Cache

1What exactly does the Back/Forward Cache store?
Bfcache freezes the entire page in memory, including the JavaScript heap, DOM tree, scroll position, and running timers, instead of destroying it when the user navigates away.
2Why does an unload event listener block bfcache?
Chrome treats any registered unload listener as a potential blocker, since it can't guarantee that logic inside it would be correctly re-triggered on a later restoration.
3Which event should I use instead of unload?
The pagehide event combined with checking event.persisted is the bfcache-compatible alternative for distinguishing between actual page destruction and freezing.
4How do I test whether my page is bfcache-eligible?
In Chrome DevTools, the Application tab has a Back/forward cache section with a test button that lists every concrete blocking reason.
5Does Cache-Control no-store always block bfcache?
Yes, this header rules out bfcache use for the affected page entirely, regardless of the page's other JavaScript behavior.
6How do I detect in code that a page was restored from bfcache?
In the pageshow event, the event.persisted property being true indicates a restoration from bfcache rather than a fresh page build.
7Do open WebSocket connections always prevent bfcache?
In most cases yes, since an active network connection can't be safely held during freezing. The connection should be closed before the page is left.
8How large is the measurable speed gain from bfcache?
A successful restoration typically falls in the low double-digit millisecond range, while a full rebuild of the same page can take several hundred milliseconds up to a few seconds.
9Should I still use beforeunload?
Beforeunload listeners are still considered a risk factor for bfcache and should be replaced with pagehide where possible, even though they no longer categorically block it in newer Chrome versions.
10How do I refresh stale data after a bfcache restoration?
In the pageshow handler, when event.persisted is true, you can trigger a targeted refresh of the affected data areas, such as stock levels or prices, without reloading the entire page.