Why Does Your Site Reload When People Press Back?
Why does your site reload from scratch when someone hits back?
Because something on the page made it ineligible for the back and forward cache. Browsers can freeze a whole page in memory and restore it instantly when a user goes back. If your page blocks that, every back press becomes a full reload, and the user waits again for something they already had.
This is the cheapest performance win we know of, and the most commonly missed. It does not need a new framework, a CDN change, or a rebuild. It usually needs you to delete one event listener and change one header.
The reason it gets skipped is that it never shows up in a lab test. You load a page fresh, measure it, and everything looks fine. The cost only appears when a real person presses back, and no synthetic test does that.
What is the back and forward cache?
The back and forward cache, usually written bfcache, stores a complete snapshot of a page in memory when the user navigates away. That includes the JavaScript heap and the DOM. Going back restores it in place, so there is no network request, no parse, no layout, and effectively no wait.
It is different from the HTTP cache, which stores files. bfcache stores the running page. The distinction matters because a perfectly cached set of files still has to be parsed and executed again, and that is the part users feel.
If HTTP caching is the part you have not tuned yet, our guide to how browser caching actually works is the better starting point, then come back to this.
How much traffic does this actually affect?
More than most teams assume. According to Chrome usage data published on web.dev, 1 in 10 navigations on desktop and 1 in 5 on mobile are either back or forward. On a mobile heavy B2B site, that means roughly a fifth of all navigation events are the exact case bfcache exists to make instant.
Put that next to your traffic. If a fifth of navigations are currently full reloads that could be instant restores, no amount of image optimisation will close that gap, because the gap is not about weight.
This is also why the win shows up in business metrics rather than just lab scores, which is the part that gets budget approved.
Is there real evidence that fixing this pays?
Yes, and it is unusually well documented. Yahoo! JAPAN News ran a two week A/B test, serving bfcache eligible pages to one group and ineligible pages to another. web.dev published the results on July 25, 2023, written up by Yuriko Hirota.
Their bfcache hit rate went from 0.04% to 54.07%. Mobile ads revenue rose 9.0% and page views rose 2.26%. That is a controlled test, not a correlation, and the before number tells you how close to zero a large site can sit without noticing.
The two blockers they removed were the two most common ones in the world. They replaced unload event handlers with the pagehide event, and they changed a Cache-control header from no-store to no-cache.
What blocks bfcache most often?
An unload event listener is the biggest one. Chrome and Firefox make a page ineligible for bfcache if it registers an unload listener at all. The listener does not need to run. Its existence is enough, and it usually arrives from an analytics or tag script rather than your own code.
Cache-Control: no-store is the second. Pages served with it may not be eligible. Teams add it to anything that looks personalised, often defensively, often on templates that hold nothing private.
The rest are about live connections. An open IndexedDB connection, an in progress fetch or XMLHttpRequest, or an open WebRTC connection will block it. Open WebSockets used to as well, though web.dev notes that Chrome 149 and later and Safari now allow them.
The last common one is a non null window.opener. A page opened with a link that keeps a reference to its opener cannot be safely frozen, so it is excluded.
How do you find out why your own pages are blocked?
Use the notRestoredReasons API. The Chrome team added it specifically so developers can see, from real field traffic, which blockers are hitting real users. That is the difference between guessing from a local test and knowing from production.
Yahoo! JAPAN News used exactly this to get a full picture of their blockers rather than working from what they could reproduce by hand. It is the same reason we push clients toward real user data generally, a point we made in field data versus lab data.
For a quick manual check, Chrome DevTools has a back and forward cache test under the Application panel that navigates away and back and tells you whether the page was restored, and if not, why.
How do you replace an unload listener safely?
Use pagehide instead. It fires in the cases unload was meant to cover, it works when the page is being frozen, and it does not disqualify the page. The pageshow event is its partner on the way back, and its persisted property tells you whether the page came from bfcache.
Chromium browsers also expose freeze and resume events, which fire when the page is actually frozen and restored. They are useful for pausing timers and polling, but pageshow and pagehide are the pair with broad support, so start there.
The practical work is usually auditing third party scripts, not your own. Search your bundle and your tag manager for the string unload and check every hit. Older analytics snippets are the usual culprit.
What do you do about Cache-Control: no-store?
Decide whether the page genuinely holds private data. If it does, keep no-store and accept the cost, because the header is doing its job. If it does not, and most marketing pages do not, no-cache gives you revalidation without blocking bfcache.
no-store means never write this anywhere. no-cache means store it but always check with the server before reusing it. Teams conflate the two constantly, and the conflation is what quietly costs them a fifth of their navigations.
Check the header per route, not per site. It is common to find one over broad rule applied at the edge that catches every page because one login route needed it.
Will fixing this make your Core Web Vitals look worse?
Possibly, and you should know that before you present the results. bfcache restores are counted as navigations with near zero load time in some tooling and excluded in others. web.dev notes that bfcache reduces the number of measured page loads overall, which can skew the remaining distribution slower.
So your average can move in the unhelpful direction while every user gets a faster experience. That is a reporting artefact, not a regression, and it is worth writing down in advance so nobody panics at the next review.
Measure the bfcache hit rate as its own metric alongside your LCP work, and judge them separately.
What does a sensible rollout look like?
Start by measuring the current hit rate in the field with notRestoredReasons, so you have a before number. Then remove unload listeners across your own code and your tag manager. Then audit no-store route by route. Re measure after each step rather than shipping all three together.
Doing it in that order matters because unload usually accounts for most of the loss, and finding that out first tells you whether the header work is worth arguing about with whoever owns it.
Expect this to take a day of engineering time on a typical marketing site, most of it spent reading third party scripts rather than writing anything.
Where does this fit in the rest of your performance work?
Near the front, because the effort is small and the affected share of navigations is large. It pairs naturally with prefetching forward navigations, so the journey is fast in both directions rather than just on the way in.
If you want the forward half of that story, our piece on speculation rules and instant navigation covers it. bfcache handles back. Speculation rules handle next. Together they remove most of the waiting from a browsing session.
We do this kind of audit as part of every build, because it is the sort of thing that is nearly free at build time and awkward to retrofit later. If you want someone to check whether your site is leaving this on the table, we are happy to take a look. You can reach us at phoenix.studio.
Want a site that performs like this?
Tell us about your project. We will come back with a clear next step, no pressure.
This site is protected by reCAPTCHA and the Google Privacy Policy and Terms of Service apply.
Have a project like this?
Tell us where you want to go. We'll tell you how we'd get you there.