Back/forward cache: making the Back button instant
The back/forward cache keeps a whole page in memory so Back and Forward are instant. What blocks it, how to test your pages, and how it changes your numbers.
In this guide
What the back/forward cache is
A visitor follows a link, looks at the next page, and presses Back. The browser can show the earlier page in two ways. It can load the page again, using whatever the HTTP cache still holds and running every script from the start. Or it can restore the page from the back/forward cache.
web.dev describes the bfcache as an in-memory cache that stores a complete snapshot of a page, including the JavaScript heap, as the visitor navigates away. With the whole page in memory, the browser can restore it without going to the network at all. The visitor sees the page as they left it.
Firefox and Safari have had a bfcache for many years. According to web.dev, Chrome has enabled it for all users, on desktop and mobile, since version 96.
It matters because people go back a lot. web.dev cites Chrome usage data showing that 1 in 10 navigations on desktop and 1 in 5 on mobile are a Back or Forward.
It is not the HTTP cache
The two caches do different jobs:
- The HTTP cache keeps copies of files that the server sent. When a page loads again, the browser still has to build it again. See caching explained and HTTP caching headers.
- The bfcache keeps the finished page, with its scripts and their state.
web.dev points out that it is rare for every file a page needs to come from the HTTP cache. A bfcache restore is therefore faster than even a well-tuned reload.
What happens to running code
A page in the bfcache is paused. web.dev explains that browsers pause pending timers, unresolved promises and the other waiting tasks, and carry on only if the page is restored. That is usually harmless. It can cause trouble when the paused code is part of something shared with other tabs, such as a database transaction. For that reason, browsers generally do not store a page that is in the middle of such work.
Why it matters to you
- Visitors. Going back from a product to the list it came from takes no time. This is the kind of visit that decides whether a shopper keeps browsing. It also saves data, because nothing is downloaded again.
- Layout shifts. The 2025 Web Almanac notes that a restored page comes back in its earlier state, which can avoid the layout shifts that a fresh load might cause.
- Your measurements. A restore is not a page load. Analytics and speed data change when it is on, as the section on measuring explains.
- Lighthouse. Lighthouse 13.5 has a diagnostic called Page prevented back/forward cache restoration. It carries no weight in the score, but it names the blockers.
What keeps a page out of the cache
Browsers differ, and the rules change. The table lists the common blockers and the usual fix. Treat it as a checklist for testing, not as a promise about every browser.
| Blocker | What happens | What to do |
|---|---|---|
An unload listener | web.dev says Chrome and Firefox on desktop treat such pages as ineligible. Other browsers may store the page but not run the listener. | Use visibilitychange or pagehide |
Cache-Control: no-store on the page | Historically every browser has treated it as a blocker. Chrome may now allow some such pages when it is safe. | Use it only on pages with sensitive data |
A beforeunload listener | Firefox will not store the page. Chrome and Safari do. | Add it only while there are unsaved changes |
A window.opener link | A page opened with window.open(), or one that opened a window, cannot be stored safely. | Use rel="noopener" |
An open IndexedDB connection, a running fetch() or XHR, an open WebSocket or WebRTC connection | Pausing these could affect other tabs. | Close them on pagehide, open them again on pageshow |
| A page that did not finish loading, or came from a POST, or has a status other than 2XX | MDN and Lighthouse list these as reasons. | Redirect after a form, and fix errors |
| Third-party scripts | The Web Almanac says some interfere with a safe restore. | Test with your tags switched on |
MDN lists many more reasons, including pages that use device or permission interfaces, such as the microphone, MIDI or Web Share, and pages whose service worker changed while they were stored. Some are specific to one browser. The DevTools test in the next section is the quickest way to see which ones apply to your page.
How to test a page
In Chrome DevTools
- Open the page in Chrome.
- In DevTools, open the Application panel and find Back/forward cache (under Background services).
- Click the button that tests it.
Chrome opens a temporary page and returns to yours. You can also use the browser's Back and Forward buttons. If the page was served from the cache, DevTools says so. If not, it lists the reasons in three groups: Actionable, which you can fix, such as an unload listener; Pending Support, which Chrome does not support yet; and Not Actionable, which something outside the page causes. Open a reason to see which frame is responsible. The names of the panel and its button have changed between versions, so use a recent Chrome.
With Lighthouse and PageSpeed Insights
In Lighthouse's navigation mode, the Page prevented back/forward cache restoration diagnostic does the same thing. It opens a temporary page, goes back to your page through the browser history, and checks whether the page was restored. The table it shows has a column for the failure reason and one for the failure type:
- Actionable: your page can change to fix it.
- Not actionable: the circumstances, not your code, caused it.
- Pending browser support: the browser does not support the case yet.
Lighthouse cannot run this test in the old headless Chrome and says so in a warning.
In the field
Lab tests tell you whether a page can be restored. They do not tell you how often it is. For that, count Back and Forward navigations and note which ones were restored. This is adapted from web.dev:
window.addEventListener('pageshow', (event) => {
const type = performance.getEntriesByType('navigation')[0].type;
if (event.persisted || type === 'back_forward') {
// send this to your own analytics
report('back_forward', { restored: event.persisted });
}
});event.persisted is true when the page was restored from the bfcache. The pageshow event works in all current browsers: Chrome 3, Firefox 6 and Safari 5, according to MDN's compatibility data.
Do not expect 100%. web.dev lists navigations that never use the cache: the visitor quits and restarts the browser, duplicates a tab, or closes a tab and brings it back. Browsers also discard stored pages after a while to save memory. The ratio is useful as a trend and for finding pages where something blocks the cache for most visitors.
Chrome also has an experimental way to see the reasons for real visits. The notRestoredReasons property of the navigation timing entry lists them, and the same entry covers frames inside the page. MDN's example looks like this:
const [nav] = performance.getEntriesByType('navigation');
console.log(nav.notRestoredReasons);
// { reasons: [{ reason: "unload-listener" }], children: [], url: "..." }MDN marks it as experimental, and its compatibility data lists it for Chrome 125 and later and not for Firefox or Safari. It returns null for a navigation that is not a history navigation.
Fix the common blockers
Replace unload
The unload event predates the bfcache, and many pages assume that they are gone after it fires. That is why browsers treat it with caution. MDN says to avoid it, and adds that it is not reliable, especially on mobile, and that Chrome has stopped firing it by default. A page that still depends on it must opt in with the unload directive of the Permissions-Policy header.
Use visibilitychange instead. MDN calls it the last event that is reliably fired, and the best place to save state or send analytics:
// Before: blocks the cache in some browsers and may never run
window.addEventListener('unload', () => {
navigator.sendBeacon('/log', analyticsData);
});
// After
document.addEventListener('visibilitychange', () => {
if (document.visibilityState === 'hidden') {
navigator.sendBeacon('/log', analyticsData);
}
});Third-party scripts often add unload listeners without telling you. That is another reason to check the page with all your tags on. See third-party scripts.
Use beforeunload only when it is needed
A "you have unsaved changes" warning is a legitimate use. Add the listener when the first unsaved change appears and remove it once the changes are saved, instead of leaving it on every page.
Keep data fresh after a restore
A restored page is not reloaded, so anything on it may be out of date. A cart count, a price, or a signed-in state can be wrong. web.dev gives two cases. A shopper changes the cart and goes back to find the old numbers. A user signs out on a shared computer, and the next person presses Back and sees the private page.
When event.persisted is true, update what could have changed:
window.addEventListener('pageshow', (event) => {
if (!event.persisted) return;
refreshCartCount(); // your own function
if (!isStillSignedIn()) { // your own check
location.reload();
}
});web.dev's own example checks for a cookie. A cookie marked HttpOnly cannot be read by scripts, so use a readable marker, or ask your server.
Close connections, then open them again
Close the things that could affect other tabs when the page is hidden, and open them again when it returns:
window.addEventListener('pagehide', () => {
socket?.close();
db?.close();
});
window.addEventListener('pageshow', (event) => {
if (event.persisted) reconnect(); // your own function
});MDN adds a detail about WebSockets. In some browsers an open socket does not block the cache. The browser closes it as the page is stored, and Chrome fires the error and close events when the page returns. If your code already reconnects after those events, it keeps working.
Avoid window.opener
A page that was opened by window.open() keeps a link to the page that opened it, and that link prevents storage. Add rel="noopener" to links that open new tabs. In Chromium, a target="_blank" link has behaved as if it had noopener since version 88, so the old problem is mostly gone for links. Calls to window.open() still need care.
The no-store decision
Cache-Control: no-store tells every cache not to keep a copy, and it is the blocker most often set by accident. The 2025 Web Almanac found it on 23% of sites, up from 21% the year before.
Decide page by page:
- Pages with data nobody else may see, such as account settings or health records. Keep
no-store. Losing the bfcache is the price of that guarantee. - Pages that must be up to date but are not secret. Use
no-cacheormax-age=0. They make the browser check with the server on a normal load and do not block the bfcache. A restore does not check, so refresh the page's data onpageshowif it changes by the minute. - Shop pages. WooCommerce keeps its cart, checkout and account pages out of caches. In the current code it also removes
no-storefor visitors who are not logged in, so that they can still use the back/forward cache. See WooCommerce speed. - Public pages. Do not send
no-store. Look at the headers your server, your CMS and your CDN really send, because a security plugin or a default can add it to every page. See how to check in HTTP caching headers.
Lighthouse also lists a separate reason for pages whose own scripts requested a resource that came back with Cache-Control: no-store. If you see it, look at your API calls as well as the page.
What it does to your analytics and speed numbers
- Pageviews drop. web.dev says most analytics libraries do not count a restore as a new pageview. If you want them counted, send a pageview when
event.persistedistrue. - Load-time numbers get worse. The loads a restore replaces were some of your fastest, since Back and Forward are repeat visits. With them gone, the average moves slower even though visitors are better off. web.dev suggests splitting load metrics by navigation type:
navigate,reload,back_forwardorprerender. - Do not confuse `back_forward` with a restore. web.dev warns that the navigation type only labels page loads. A restore reuses a page that was loaded earlier.
- Core Web Vitals. web.dev says the Chrome UX Report treats restores as separate page visits, and that the
web-vitalslibrary has reported them since version 1. See how to measure Core Web Vitals and the Chrome UX Report.
Common mistakes
- Sending `no-store` everywhere "to be safe". It costs the cache on every page.
- Adding `unload` for analytics. It may not run, and it may block the cache.
- Fixing your own code and forgetting the tags. Chat widgets and trackers are common blockers.
- Leaving stale or private data on a restored page. Refresh on
pageshow. - Expecting a hit ratio of 100%. Some Back navigations can never use the cache.
- Testing in one browser. The rules differ. A page that restores in Chrome may not in Firefox.
Check your own page
Run the free audit and look in the diagnostics for Page prevented back/forward cache restoration. If it is listed, read the failure reasons and start with the ones marked actionable. Then confirm in DevTools in your own browser, with your normal tags on. If the audit does not list it, the test restored your page, or the tool could not run the test, so check DevTools anyway.
Sources
- Back/forward cache on web.dev: how the cache works, the blockers, the test, and the effect on analytics and metrics.
- Window: unload event, Document: visibilitychange event and Monitoring bfcache blocking reasons on MDN, with the compatibility data for
pageshowandnotRestoredReasons. - WooCommerce: class-wc-cache-helper.php: how a shop's cart pages treat caching and
no-store. - Test back/forward cache in the Chrome DevTools documentation: the panel and its three kinds of cause.
- Performance chapter, Web Almanac 2025:
unloadhandlers,no-store, and what can block the cache. - The Lighthouse bf-cache audit, version 13.5: how the test is run and how its results are shown.
Start here
Free website audit
Live Google data for any public page: mobile and desktop.
Mobile + desktop · Lighthouse lab test · Real-user Chrome data when Google has it
Rather have this done for you? Speed optimization · Book a call