Skip to content
SnapFrom

The Snapshot Waiting Behind the Back Button

· · 7 min read

Hit back on most sites and the page you land on has to rebuild itself: new network requests, new JavaScript execution, new render. Hit back on some sites and it just reappears, mid-scroll, form fields still filled in, as if no time passed. The difference is not luck. The second page was never actually gone - the browser froze it in memory the moment you navigated away, and handed the same live tab back to you[1].

What the freeze actually holds

The mechanism is called the back/forward cache, bfcache for short. MDN’s definition is specific about what it stores: “a complete snapshot of a page as the user navigates away from it”, one the browser “can then quickly restore” instead of “needing to repeat the network requests required to load the page”[1]. It is not a copy of the HTML. “The snapshot contains the entire page in memory, including the JavaScript heap; in-progress code is paused when the user navigates away and resumed when they return to the page”[1]. A setInterval still counting down when you left is still counting down, frozen mid-count, when you come back. web.dev’s phrasing is the same underneath: bfcache “pause[s] any pending timers or unresolved promises”, including “almost all pending tasks in the JavaScript task queues”, and resumes them on restore[2].

That is also why bfcache costs more than an ordinary cache. “A regular HTTP cache entry … contains only responses to previous requests”; bfcache keeps a live tab standing by instead, and MDN names the trade directly: “bfcache entries require more resources, and create complexity in terms of how to represent in-progress code”[1]. A browser cannot keep every tab you have ever left open in this state forever, so it disqualifies pages for specific reasons - and until 2025, two of those reasons were near-absolutes.

The two blockers everyone learned

The first is an event handler. Pages that attach an unload listener predate bfcache by design: “many pages on the internet operate under the (reasonable) assumption that a page won’t continue to exist after the unload event has fired”[2], which is exactly what bfcache breaks - the page keeps existing, just paused. “On desktop, Chrome and Firefox have chosen to make pages ineligible for bfcache if they add an unload listener”[2].

The second is a response header. “When Cache-Control: no-store is set on the page resource itself … browsers have chosen not to store the page in bfcache”[2]. The header was built for the HTTP cache, to mark a response too sensitive or too dynamic to keep around, but browsers read it as a signal for bfcache too. A banking dashboard, an order-confirmation page, anything marked no-store lost the instant-back experience along with the disk cache it was actually trying to opt out of.

Both of those sentences are still the correct mental model for Firefox and Safari. Neither is still complete for Chrome.

Chrome quietly rewired the header in 2025

Chrome’s own changelog for this dates the switch precisely: “completing the final rollout to 100% of users over March and April 2025”[3]. Cache-Control: no-store no longer removes a page from bfcache eligibility in Chrome outright. It shrinks it.

The budget drops from ten minutes to three: “the bfcache timeout for Cache-Control: no-store pages is also reduced to 3 minutes (from 10 minutes used for pages which don’t use Cache-Control: no-store)”[3]. Chrome also watches harder while the page sits there - it will “evict a page from the bfcache on changes to cookies, or other authorization methods”, the case that matters being someone logging out in a different tab while the logged-in page is frozen in this one[3]. And the older, harder blockers did not move: a page “using WebSocket, WebTransport, or WebRTC” is still ineligible no matter what the header says, and a page whose own fetch call comes back with Cache-Control: no-store gets evicted too, “as it may contain sensitive data”[3].

So the rule for Chrome in September 2026 is not “no-store blocks bfcache” and not “no-store is fine now” - it is “no-store gets three minutes instead of ten, with a few open connection types that still lock the door outright.”

What that looks like on real sites

Cache-Control on a document is public information - every response carries it, whether or not the operator has ever thought about bfcache. Requesting the front pages of eight sites with a plain client, on 11 September 2026, put a number on how many still opt out at all:

$ curl -sI https://github.com/           | grep -i cache-control
cache-control: max-age=0, private, must-revalidate

$ curl -sI https://www.nytimes.com/      | grep -i cache-control
cache-control: s-maxage=300,no-cache

$ curl -sI https://www.amazon.com/       | grep -i cache-control
cache-control: no-cache

$ curl -sI https://en.wikipedia.org/     | grep -i cache-control
cache-control: s-maxage=1200, must-revalidate, max-age=0

$ curl -sI https://www.reddit.com/       | grep -i cache-control
cache-control: private, no-store

$ curl -sI https://www.bbc.com/          | grep -i cache-control
cache-control: public, stale-if-error=90, stale-while-revalidate=30, max-age=30

$ curl -sI https://stackoverflow.com/    | grep -i cache-control
cache-control: private

$ curl -sI https://www.cnn.com/          | grep -i cache-control
cache-control: max-age=60, public

One out of eight, reddit.com, sends no-store on the document itself. The other seven use no-cache, private or a max-age value - directives that govern the ordinary HTTP cache and were never part of this rule at all. no-cache in particular is easy to mistake for no-store on a skim; the two names sound like variants of the same instruction and describe unrelated behaviour. Only the literal string no-store triggers bfcache’s shorter budget in Chrome, and on this sample, that is a narrow slice of the web’s own front doors.

The other blocker is being deleted outright, this year

Where 2025 softened the header, 2026 is removing the event handler as a mechanism entirely, not just as a bfcache disqualifier. MDN’s current guidance already reads “developers should avoid using this event”[4] and documents why it never worked well in the first place: “especially on mobile, the unload event is not reliably fired”, including the ordinary case of a user switching apps and later closing the browser from the app switcher, where unload never fires at all[4].

Chrome’s fix for the unreliability is to stop delivering the event. The rollout is dated and it is mid-flight right now: Chrome “started to roll this out to all origins, over 8 milestones (or about 32 weeks)” in 2026, and by Chrome 151 on 28 July 2026 it “affects 60 percent of Chrome page loads for all sites across the web platform”[5]. A site that still attaches an unload listener to save form state or fire a beacon is, for a growing share of its Chrome visitors this year, attaching a handler that silently does not run - whether or not anyone touches the code. The recommended replacements are events built for this from the start: visibilitychange, “the last reliable time to save app and user data”, and pagehide as a fallback[5]. MDN’s own advice matches: use visibilitychange first, pagehide next, both because they fire more reliably and because, unlike unload, “some browsers (such as Firefox) will not place pages in the bfcache if they have unload listeners”[2][4].

Checking your own numbers instead of guessing

Neither change is visible from outside a site the way a response header is. A page can carry an invisible unload listener from a third-party analytics snippet, or lose bfcache to a fetch response nobody remembers setting no-store on. Chrome’s answer is an API built for exactly this gap. PerformanceNavigationTiming.notRestoredReasons “reports information on why the current document was blocked from using the bfcache on navigation”, specifically so “developers can use this information to identify pages that need updates to make them bfcache-compatible”[6].

The report names the blocker directly rather than leaving a site to guess. A page blocked by a leftover unload listener gets back an entry shaped like { "reasons": [{ "reason": "unload-listener" }], "url": "example.com" }[6], alongside documented categories for fetch, websocket, navigation-failure and others - plus a masked category the browser uses when “the exact reason is hidden for privacy purposes”[6]. Reading it once, on a page a site actually cares about restoring instantly, answers a question that used to require reading someone else’s spec and guessing which sentence still applied.

Where that leaves the reader

Two independent things that used to make the back button reload a page have both moved in the last eighteen months, and the browser did not ask permission for either. Cache-Control: no-store went from an outright bfcache block to a three-minute allowance, finished in Chrome in April 2025[3]. The unload event that has blocked bfcache for as long as bfcache has existed is now being switched off as a feature in its own right, at 60 percent of Chrome page loads as of this July and climbing[5]. Neither change required a site to do anything, and most site owners have not noticed either one happened.

The header change is not a free pass - three minutes is still a real limit, not the ten a normal page gets, and anything using a WebSocket keeps the old behavior regardless[3]. The event change is closer to free: code built around unload was already unreliable on mobile before Chrome touched it[4], and the two replacement events are the ones MDN has recommended since before this rollout started. What used to require reasoning through two separate specs to know whether a page could ever be instant on the way back is now one field to read: open notRestoredReasons on the page and see the actual name Chrome logged, rather than the rule that applied to it last year.

Sources

  1. bfcache - MDN Web Docs, Mozilla, accessed

    Supports: The back/forward cache, or bfcache, 'is a performance-enhancing feature available in modern browsers that enables instant back and forward navigation between previously-visited pages'. 'It does this by storing a complete snapshot of a page as the user navigates away from it; the browser can then quickly restore the snapshot if the user decides to return to it, rather than needing to repeat the network requests required to load the page.' 'The snapshot contains the entire page in memory, including the JavaScript heap; in-progress code is paused when the user navigates away and resumed when they return to the page.' 'A regular HTTP cache entry on the other hand contains only responses to previous requests. The bfcache therefore provides faster results than the HTTP cache.' 'The downside is that bfcache entries require more resources, and create complexity in terms of how to represent in-progress code.' 'Some code features (for example the unload handler) are not compatible, so their presence on a page blocks it from using the bfcache.' 'You can use the notRestoredReasons API to monitor whether pages are blocked from using the bfcache, and reasons why.'

  2. Back/forward cache - web.dev, Chrome for Developers, Google, accessed

    Supports: 'Although bfcache is not an HTTP cache, historically, when Cache-Control: no-store is set on the page resource itself (as opposed to any subresource), browsers have chosen not to store the page in bfcache so any pages using Cache-Control: no-store may not be eligible for bfcache. There is work underway to change this behavior for Chrome in a privacy-preserving manner.' On unload: 'The unload event is problematic for browsers because it predates bfcache and many pages on the internet operate under the (reasonable) assumption that a page won't continue to exist after the unload event has fired.' 'On desktop, Chrome and Firefox have chosen to make pages ineligible for bfcache if they add an unload listener.' 'Due to its unreliability, and the performance impact for bfcache, Chrome is looking to deprecate the unload event.' 'The Chrome team has added the NotRestoredReasons API to help expose the reasons why pages don't use bfcache.' 'It is important to realize that there are a number of scenarios, outside of the site owners control, when a Back/Forward navigation won't use the bfcache.' While a page sits in bfcache, 'browsers pause any pending timers or unresolved promises for pages in bfcache, including almost all pending tasks in the JavaScript task queues, and resume processing tasks if the page is restored from the bfcache.'

  3. Extending back/forward cache to pages with Cache-Control: no-store - Chrome for Developers, Google, accessed

    Supports: Chrome's change is described as 'completing the final rollout to 100% of users over March and April 2025'. 'The bfcache timeout for Cache-Control: no-store pages is also reduced to 3 minutes (from 10 minutes used for pages which don't use Cache-Control: no-store).' To protect sensitive data, Chrome will 'evict a page from the bfcache on changes to cookies, or other authorization methods' after events such as logout. Pages 'using WebSocket, WebTransport, or WebRTC' remain ineligible for bfcache even with Cache-Control: no-store. If a fetch made from the page 'returns Cache-control: no-store on its response, then this will also evict the page as it may contain sensitive data'. On restoration, Chrome restores 'the old page (including the JavaScript heap)' rather than reloading it, though 'not all content needs to be updated'.

  4. Window: unload event - MDN Web Docs, Mozilla, accessed

    Supports: 'Developers should avoid using this event.' 'Especially on mobile, the unload event is not reliably fired.' Example given: a mobile user visits a page, switches to a different app, and later closes the browser from the app manager - the unload event never fires in that sequence. 'Also, the unload event is not compatible with the back/forward cache (bfcache), because many pages using this event assume that the page will not continue to exist after the event is fired.' 'To combat this, some browsers (such as Firefox) will not place pages in the bfcache if they have unload listeners, and this is bad for performance. Others, such as Chrome, will not fire the unload when a user navigates away.' 'The best event to use to signal the end of a user's session is the visibilitychange event. In browsers that don't support visibilitychange the next-best alternative is the pagehide event, which is also not fired reliably, but which is bfcache-compatible.'

  5. Deprecating the unload event - Chrome for Developers, Google, accessed

    Supports: Chrome 'started to roll this out to all origins, over 8 milestones (or about 32 weeks)' in 2026. At Chrome 151 (28 July 2026) the rollout 'affects 60 percent of Chrome page loads for all sites across the web platform'. The recommended replacements: 'visibilitychange: To determine when the visibility of a page changes' and 'pagehide: To determine when the user has navigated away from the page', with visibilitychange described as 'the last reliable time to save app and user data'.

  6. Monitoring bfcache blocking reasons - MDN Web Docs, Mozilla, accessed

    Supports: 'The PerformanceNavigationTiming.notRestoredReasons property reports information on why the current document was blocked from using the bfcache on navigation.' 'Developers can use this information to identify pages that need updates to make them bfcache-compatible, thereby improving site performance.' The returned object reports 'reasons why bfcache usage was blocked' plus 'details such as frame id and name, to help identify iframes in the HTML'. An example entry: `{ "children": [], "id": null, "name": null, "reasons": [{ "reason": "unload-listener" }], "src": "", "url": "example.com" }`. Documented reason strings include 'masked' ('the exact reason is hidden for privacy purposes'), 'unload-listener' ('the Document registered an event listener for the unload event'), plus 'fetch', 'lock', 'navigation-failure', 'parser-aborted' and 'websocket', among others.