Every back navigation on the web is a moment where users expect a page they already know -- and still have to wait if the browser reloads it from scratch. This is exactly where the back/forward cache comes in, bfcache for short: when a user leaves a page, the browser freezes its complete state in memory and restores it almost instantly on a back or forward click, instead of running the entire loading chain again. The lever is large, because on desktop around one in ten (Chrome for Developers) and on mobile around one in five (Chrome for Developers) navigations is a back or forward move, and in the March 2024 dataset of the Chrome UX Report already 6.77 percent (Chrome UX Report) of all page loads were served directly from the bfcache. This is especially topical in 2026 because Chrome rolled out the bfcache for pages with the Cache-Control: no-store header to 100 percent (Chrome for Developers) of users in spring 2025 and added the notRestoredReasons API as a diagnostic tool. This article explains how the bfcache works, which blocks prevent restoration, how to test eligibility in DevTools, and why a targeted frontend optimization lifts field metrics here -- including the link to the Core Web Vitals of 2026.
Key takeaways
- The back/forward cache freezes a page's complete state -- DOM tree, JavaScript heap and scroll position -- in memory when the user leaves, and restores it almost instantly on a back or forward click instead of reloading the page.
- Because around one in ten navigations on desktop and one in five on mobile is a back or forward move, a high hit rate noticeably lifts a domain's field-measured metrics.
- Typical blockers are legacy unload handlers, open WebSocket, WebRTC or WebTransport connections, a window.opener that is not reset to null, and -- with limits -- the Cache-Control: no-store header.
- Since the full rollout in spring 2025, Chrome also admits pages with Cache-Control: no-store into the bfcache, as long as no cookie change or sensitive processing stands in the way.
- Eligibility can be tested in the Back-Forward Cache panel of DevTools and diagnosed in the field via the notRestoredReasons API -- the basis for removing blockers in a targeted way.
What the Back/Forward Cache Is
To understand the benefit, it helps to compare it with a normal navigation. When a user clicks back without bfcache, the browser starts the entire work from scratch: build the connection, load the HTML, discover the CSS, fonts, images and scripts within, load those, run the JavaScript and render the page. The bfcache avoids this by keeping a complete snapshot of the page on exit -- the built-up DOM tree, the JavaScript heap and even the scroll position. When the user returns, the browser simply swaps this frozen state back in. Unlike a classic HTTP cache, which keeps individual files, the bfcache preserves the whole, already fully rendered page in working memory. The related principle of preparing a next page in advance is described in our article on Speculation Rules for instant navigation; the bfcache, by contrast, takes care of the way back.
For pages to use the bfcache cleanly, there are two central events. The pagehide event fires when the page is left and -- if possible -- placed into the bfcache; the pageshow event fires on every display, so both on the first load and on restoration. Whether a page was freshly loaded or fetched back from the cache is revealed by the event.persisted property: if it is true, the page came from the bfcache (MDN Web Docs). This is exactly where a common source of error lies -- and at the same time the point where dynamic content can be refreshed in a targeted way after a restoration.
Using pageshow and persisted correctly
Why bfcache Lifts Field Metrics
The bfcache works on two levels: it improves the directly experienced speed and at the same time lifts the values measured in the field. Because a restoration effectively has no load time, it counts in field data as one of the fastest page loads of all. The Chrome UX Report has reported a dedicated navigation type for the bfcache since the March 2024 dataset, and the analysis shows a strong statistical correlation between bfcache navigations and an instant Largest Contentful Paint (Chrome for Developers). For heavily frequented pages with many back moves, this can noticeably raise the share of passing Core Web Vitals without anything changing in the actual page build. How lab and field data interact here is explored in our article on RUM versus CrUX field data.
The business effect is documented. In a controlled test, Yahoo! JAPAN News increased its ad revenue on mobile by 9.0 percent (web.dev) and page views by 2.26 percent (web.dev) after the bfcache was enabled; the hit rate rose from 0.04 to 54.07 percent (web.dev) in the process. The reason is simple: whoever jumps back to the results list faster sees more pages and bounces less often. The bfcache also improves not only the load time but often the responsiveness right after navigation, which benefits Interaction to Next Paint.
A page that is already there on the back click does not just feel faster -- it lengthens the session, because the way back to the overview is no longer a hurdle.
What Blocks Restoration
As effective as the bfcache is, it only takes effect when a page is eligible for it. Certain patterns force the browser to discard the page entirely and reload it instead. The historically most common blocker is the unload handler: if a page registers an unload event, it is considered non-cacheable in Firefox and Chrome desktop (web.dev). Google has begun to deprecate the unload event entirely, because it is unreliable and prevents the bfcache -- modern alternatives are pagehide and the visibilitychange event. A beforeunload handler was also a problem for a long time and should only be registered conditionally, not permanently.
| Blocker | Why bfcache is skipped | Clean solution |
|---|---|---|
| unload handler | considered non-cacheable, being deprecated | switch to pagehide and visibilitychange |
| open WebSocket / WebRTC / WebTransport | an active connection does not survive freezing | close in pagehide, rebuild in pageshow |
| in-progress fetch or XMLHttpRequest | the response would go nowhere | finish or abort requests before leaving |
| window.opener not null | a reference to another window blocks | open outgoing links with rel=noopener |
| Cache-Control: no-store | only allowed with limits, evicted on cookie change | review the header, set no-store only where needed |
Handling these blocks is improving step by step. Open WebSocket connections, for example, blocked the bfcache in almost all browsers for a long time; since Chrome 149 (web.dev) a page with an open WebSocket connection is nonetheless cached under certain conditions and the connection is paused on exit. Still, it remains reliable to close open connections yourself in the pagehide event and rebuild them in the pageshow event. Many of these blockers stem from embedded third-party scripts anyway, which is why a lean inventory helps, as our article on trimming third-party scripts shows. A lean page structure also supports this -- how to reduce the DOM size is described in the related article.
The unload event is a phase-out model
Cache-Control: no-store -- the 2025 Breakthrough
For a long time a firm rule applied: pages with the Cache-Control: no-store header never landed in the bfcache. This affected many logged-in areas, carts and other pages with personal data that set this header out of caution -- even though most back navigations there reveal no sensitive data at all. This header is by far the largest single blocker: it prevented the bfcache on around 17 percent (Chrome for Developers) of history navigations on mobile and 7 percent (Chrome for Developers) on desktop.
Chrome changed this gradually from version 116 onward and completed the rollout over March and April 2025 (Chrome for Developers) to 100 percent (Chrome for Developers) of users; as a browser feature the step is documented in Chrome Platform Status (Chrome Platform Status). Since then, no-store pages may also enter the bfcache -- but with a safety net. If a cookie or another authorization changes while the page is in the cache, Chrome evicts it immediately and loads fresh on the back click, so that no one sees stale or foreign data. In addition, the dwell time of such pages in the cache is shortened to 3 minutes (Chrome for Developers), compared with 10 minutes (Chrome for Developers) for ordinary pages. Important: this relaxation initially applies only to Chrome; other browsers may still treat no-store as a blocker.
Set no-store deliberately
Test and Diagnose Eligibility
Whether a page is bfcache-eligible can be checked directly. Chrome DevTools has a dedicated Back-Forward Cache panel under Application with a Run Test button: it navigates the current page away and back and then lists every reason that would have prevented restoration (Chrome for Developers). This is the fastest way to find blockers in the lab in a targeted way and to re-check after every change.
window.addEventListener("pageshow", (event) => {
if (event.persisted) {
// page came from the bfcache -- refresh only dynamic values
refreshCartCount();
}
});
window.addEventListener("pagehide", () => {
// close the open connection so the page stays cacheable
socket?.close();
});For practice the field data matter even more, because not all situations can be reproduced in the lab. For that there is the notRestoredReasons API, which has shipped since Chrome 123 (Chrome for Developers). It hangs off the PerformanceNavigationTiming class and reports after a navigation whether the page came from the bfcache and, if not, which frames were blocked and why (MDN Web Docs). This shows from real user data which blocker actually strikes, instead of guessing in the lab.
const [entry] = performance.getEntriesByType("navigation");
if (entry && entry.notRestoredReasons) {
console.log(entry.notRestoredReasons);
// reports blocking reasons, e.g. "unload-handler" or "websocket"
}- Test in the DevTools Back-Forward Cache panel after every change
- Remove unload handlers and replace them with pagehide or visibilitychange
- Close open WebSocket, WebRTC or WebTransport connections in pagehide
- Neutralize window.opener via rel=noopener on outgoing links
- Set Cache-Control: no-store only where it is strictly needed
- Use the notRestoredReasons API to check in the field which blockers really occur
The Shop Journey: from Category to Product and Back
Hardly any pattern benefits from the bfcache as directly as the typical shop journey. Users open a category page, click into a product, jump back to the list, open the next product, jump back again -- often several times in a row. Without bfcache the category page reloads on every back click, including filters, sorting and the scroll position, which is lost in the process. With bfcache the exact previous state appears instantly: the same scroll height, the same active filters, no flicker. Because around one in five (Chrome for Developers) navigations on mobile is such a back move, the bfcache eligibility of the category template decides a large part of the experienced speed here. How strongly fast navigation affects conversion and revenue is explored in our article on the relationship between page speed, conversion rate and revenue.
The core in one sentence
How We Establish bfcache Eligibility
In practice we approach the bfcache like any frontend measure: measure first, then act. First we check each central template -- home, category, product, cart, login area -- in a performance analysis, using the DevTools panel and the notRestoredReasons API, for its bfcache eligibility. Then we remove the blockers found: we replace legacy unload handlers with pagehide, close open connections cleanly, neutralize window.opener and review every Cache-Control: no-store header for its justification. Next we make sure that dynamic values such as cart or login state are refreshed in a targeted way after a restoration via event.persisted. Finally we measure the effect in the field data of the Core Web Vitals, instead of relying on lab values alone. For us the bfcache is part of a larger range of services around load time and navigation.
The appeal of the bfcache is that it targets a moment users take for granted: the way back. A page that is already there on the back click feels more effortless than any load animation, however well optimized. Because the bfcache is built into modern browsers anyway -- in Chrome since version 96 (web.dev) and in Firefox and Safari for even longer -- using it costs no rebuild, only the removal of the blockers. That is exactly what we check and handle as part of frontend optimization, so that back navigations on your website or in your shop are instant. How text compression additionally affects load time is shown in our article on Brotli and Zstandard.
Sources and studies