Performance
Back-forward cache on ecommerce product pages in 2026
Bfcache for ecommerce is the browser keeping a full snapshot of your product or collection page in memory when the shopper taps forward, so a back button can restore the previous screen without another HTML download. Collection to product and back is one of the most common gestures on a catalogue. This note is for Magento, Shopify and WooCommerce storefronts I ship, not for a single-page app checkout rewrite.
I am Alan Vo, a Gold Coast web developer. Eighteen years of multi-page catalogues taught me that shoppers blame the store when back feels slow, even if the PDP LCP looked fine on first load. Google’s back/forward cache guide on web.dev is the rulebook. This is the store version: what blocks eligibility, how cart state should refresh, and how to measure restore rate without fooling analytics.
Why bfcache for ecommerce matters on catalogue pages
Bfcache for ecommerce matters on catalogue pages because back and forward navigations are a large share of real sessions. Chrome usage data quoted on web.dev suggests about one in ten desktop navigations and one in five mobile navigations are back or forward. On a jewellery PLP where someone opens three rings and returns to the grid, that is not edge case traffic. It is the browse loop.
Without bfcache, every back navigation is a fresh document load. The browser may revalidate HTML, re-parse CSS, re-run theme JavaScript, and re-request images that were on screen a second ago. With bfcache, the browser pauses the old page, keeps a memory snapshot including the JavaScript heap, and on back makes it visible again. Navigation can feel instant because no network round trip is required for the document itself.
That is different from HTTP cache. A well-tuned CDN can make repeat GETs fast, but bfcache restores the entire rendered page state, not only cached bytes for subresources. For a heavy Magento PDP with dozens of scripts, the gap is obvious in the field.
Bfcache does not replace Speculation Rules for ecommerce on the forward hop. Speculation warms or prerenders the next URL. Bfcache makes the previous URL cheap when the shopper reverses direction. I use both on the same catalogue when Chromium allows it. Safari and Firefox also implement bfcache, though blockers and measurement differ by engine. Progressive enhancement still applies: fix eligibility for everyone, then tune refresh logic.
Their Nibs is a Shopify rebuild I shipped as contract work on an agency team. Published numbers: 31% more conversions, 48% more orders. That lift came from the full purchase journey, not from a bfcache audit alone. Bfcache is how I would now make the return hop from a PDP to the collection feel as tight as the forward path after rebuild work.
How bfcache for ecommerce actually works in the browser
Bfcache for ecommerce actually works as an automatic browser optimization, not a setting in your CMS. When the user navigates away, the browser may enter the page into the back/forward cache instead of destroying it. JavaScript timers and many pending tasks pause while the page is frozen. If the user returns soon, the pageshow event fires with event.persisted === true and the page resumes.
The primary observation hooks are pagehide and pageshow, documented on MDN for pageshow. Chromium also exposes freeze and resume through the Page Lifecycle API. For hit-rate measurement, pageshow with persisted is the portable signal.
Soft navigations inside a single-page app do not use bfcache for the in-app route change. Most Shopify, WooCommerce and Hyva catalogues I maintain are multi-page HTML. Bfcache applies to real <a href> navigations between PLP and PDP. That matches how shoppers use filters, sort, and product tiles.
Embedded iframes ride along with the main frame snapshot. A review iframe or legacy payment widget on the PDP can block the whole page from entering bfcache if it holds open connections or uses APIs that are unsafe to freeze. Treat third-party embeds as eligibility risks, not as someone else’s problem.
Chrome provides the NotRestoredReasons API so you can see why a page was not stored. That is more actionable than guessing from Lighthouse alone. Field tools and RUM can log persisted on pageshow to build a restore rate for product templates.
Production checklist for bfcache for ecommerce
Bfcache for ecommerce eligibility is mostly about removing foot-guns and refreshing state after restore. I run this list on PLP, PDP, cart, and account templates.
- Remove every
unloadlistener. web.dev is explicit: never useunload. It predates bfcache and makes pages ineligible in Chrome and Firefox on desktop. Usepagehideinstead. Audit theme JS, tag containers, chat widgets, and A/B snippets. Lighthouse’s no-unload-listeners audit helps but does not catch runtime additions. - Consider
Permissions-Policy: unload=()on anonymous catalogue HTML so third parties cannot attach unload handlers later. That policy is described in the same web.dev bfcache article. - Stop sending
Cache-Control: no-storeon public PLP and PDP HTML unless the response truly must never be stored anywhere. Sensitive account pages may needno-store. Anonymous product pages usually wantno-cacheor shortmax-agewith revalidation, which does not block bfcache the same way. Checkout and post-login screens are the common exception. - On
pageshowwhenevent.persistedis true, refresh cart count, header mini-cart, stock badges, and promo banners. The restored page is a memory snapshot. An add-to-cart on another tab, or a sale ending, will not appear until you fetch fresh JSON or revalidate a fragment. - Close IndexedDB connections, WebSockets, and in-flight
fetchduringpagehideif your theme opens them on catalogue pages. Open them again onpageshowafter restore. web.dev lists open IndexedDB and pending XHR as reasons browsers refuse to cache. - Use
rel="noopener"ontarget="_blank"links sowindow.openerdoes not block bfcache for the opener or opened page. - Keep
beforeunloaddialogs off unless the shopper has unsaved checkout edits. Add the listener only when needed and remove it after save, as web.dev recommends. - For ads, do not disable bfcache with
no-storejust to refresh impressions. Refresh slots onpageshowwhenpersistedis true. GPT and similar libraries document bfcache-aware refresh patterns in the web.dev ads section. - After enabling view transitions on ecommerce or heavy animation, clear
view-transition-namevalues onpagehideso restored grids do not keep stale names. - Verify in Chrome DevTools Application panel back/forward cache testing, then confirm on a real phone with back from PDP to PLP.
What breaks bfcache for ecommerce storefronts
Bfcache for ecommerce breaks first when marketing tags attach unload to every page view. Old analytics snippets and some heatmap tools still do. The page looks fine in lab tests while back navigation stays slow in Chrome. Search the bundle for addEventListener("unload" and replace with pagehide or remove.
The second break is Cache-Control: no-store on catalogue HTML because someone copied security headers from checkout to the whole site. Magento Varnish and some WordPress security plugins have done this to me on staging. PDPs never enter bfcache until the header is scoped.
Stale cart and price UI is a functional break, not a cache block. The shopper adds on the PDP, hits back, and the collection still shows an empty mini-cart until scroll. Fix with a lightweight cart endpoint on pageshow restore, or Broadcast Channel between tabs. Do not force location.reload() on every restore unless you accept a flash and lost scroll position.
Open WebSockets for live stock or chat can disqualify the page while the socket stays open. Pause or close on pagehide. Reconnect on pageshow if the shopper is still on that SKU.
Personalised HTML for logged-in wholesale pricing is tricky. Bfcache snapshots what was rendered. If the session expired while the page sat frozen, restore can show wrong nets. For B2B, disable bfcache on account-specific templates or reload when session cookies fail a check on pageshow.
Checkout and payment iframes often use APIs that block freezing. Keep bfcache tuning on browse templates first. I do not expect bfcache on a three-step checkout with an active payment session.
How to measure bfcache for ecommerce in the field
You measure bfcache for ecommerce with the share of back navigations where pageshow reports persisted, plus template-level NotRestoredReasons in Chromium, not with a single lab Lighthouse score. CrUX and RUM can split navigation types when your beacon reads performance.getEntriesByType("navigation")[0].type after back.
Log a custom dimension when event.persisted is true on product and collection URLs. Compare LCP and INP for restored navigations versus cold loads. Restored pages often show very low LCP because paint is immediate. Do not treat that as a new SEO win on its own. It is a UX win on the browse loop.
Watch analytics for double counting. Most modern GA4 flows handle prerender separately; bfcache restore is a visibility change, not a full new session. Still audit GTM tags that fire on every pageshow. I gate non-essential pixels on if (!event.persisted) or dedupe with a short-lived flag.
Track origin CPU and memory if you increase simultaneous frozen pages during heavy tab use. Bfcache entries do not live forever. The browser evicts under pressure. A high NotRestoredReasons rate for InsufficientResources means the PDP JavaScript heap is too large. That ties back to reduce JavaScript on checkout pages discipline on browse templates too.
Compare mobile and desktop restore rates. Mobile back volume is higher in Chrome’s stats. If mobile restore rate lags desktop, suspect no-store, heavier third parties on the mobile theme, or open connections from app-like widgets.
Related work on this site
Collection to product and back is the loop on Their Nibs, a Shopify fashion storefront where published results were conversion and order lifts after rebuild, and on Skanvi, WooCommerce furniture with large stills and a long consideration path. Retail conversion is Magento-scale catalogue work where FPC and header cart fragments already matter. Bfcache makes the reverse navigation cheap once HTML and scripts are eligible. Metric context for LCP and INP on the forward hop sits in Core Web Vitals and INP for online stores.
FAQ
What is bfcache for ecommerce in plain terms?
Bfcache for ecommerce in plain terms is the browser remembering a full product or collection page when the shopper leaves, so tapping back can show that page again from memory without reloading HTML. It is automatic when the page meets eligibility rules. You optimize by removing blockers and refreshing cart or price data after restore.
Does bfcache for ecommerce work on Shopify, WooCommerce and Magento?
Bfcache for ecommerce works on any multi-page HTML storefront when templates avoid unload handlers, open connections, and inappropriate no-store headers on public catalogue responses. Platform does not disable bfcache. Your theme, tags, and cache headers do. Checkout and logged-in account pages often stay ineligible by design.
Will bfcache for ecommerce show a stale shopping cart?
Bfcache for ecommerce can show a stale cart until you refresh state on pageshow when event.persisted is true. The snapshot reflects the moment the shopper left. Fetch the mini-cart fragment or cart JSON on restore. Do not rely on a full reload unless session integrity requires it.
How is bfcache for ecommerce different from Speculation Rules?
Bfcache for ecommerce speeds up back and forward by restoring a page already visited. Speculation Rules prefetch or prerender a page the shopper has not opened yet. Forward speculation and backward cache solve different hops on the same journey. I tune both on Chromium-heavy traffic without expecting Safari to prerender the same way.
Keep reading
Contact if you want this kind of work on a live store.