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.
Inhaltsverzeichnis
- 1. What is the Back/Forward Cache
- 2. How It Works: pageshow and pagehide Instead of unload
- 3. Code Example: Correct Event Handling for bfcache Compatibility
- 4. Common bfcache Blockers at a Glance
- 5. Using the Chrome DevTools bfcache Test Tool
- 6. Measurable Performance Gain on a Successful Restore
- 7. Interplay With Other Browser APIs
- 8. Best Practices for Classic Multi-Page Applications
- 9. Summary
- 10. Zusammenfassung
- 11. FAQ
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