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.

AdvancedHow-to10 min readUpdated October 30, 2026

In this guide
  1. What the back/forward cache is
    1. It is not the HTTP cache
    2. What happens to running code
  2. Why it matters to you
  3. What keeps a page out of the cache
  4. How to test a page
    1. In Chrome DevTools
    2. With Lighthouse and PageSpeed Insights
    3. In the field
  5. Fix the common blockers
    1. Replace unload
    2. Use beforeunload only when it is needed
    3. Keep data fresh after a restore
    4. Close connections, then open them again
    5. Avoid window.opener
  6. The no-store decision
  7. What it does to your analytics and speed numbers
  8. Common mistakes
  9. Check your own page
  10. Sources

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:

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

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.

BlockerWhat happensWhat to do
An unload listenerweb.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 pageHistorically 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 listenerFirefox will not store the page. Chrome and Safari do.Add it only while there are unsaved changes
A window.opener linkA 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 connectionPausing 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 2XXMDN and Lighthouse list these as reasons.Redirect after a form, and fix errors
Third-party scriptsThe 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

  1. Open the page in Chrome.
  2. In DevTools, open the Application panel and find Back/forward cache (under Background services).
  3. 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:

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:

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

Common mistakes

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

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

Related guides

All guides →