Performance

How to ship bfcache for ecommerce catalogues in 2026

Performance

Bfcache for ecommerce catalogues is how a shopper gets the collection back instantly after they tap a product and hit back. This note is for Magento, Shopify and WooCommerce multi-page catalogues I ship, not for a React rewrite that never leaves one document.

I am Alan Vo, a Gold Coast web developer. Eighteen years of storefronts taught me that the back button is the comparison loop. Google's back/forward cache guide is the rulebook. This is the store version: what to kill, which headers to stop sending, and how to refresh a cart without making the PLP ineligible.

Why bfcache for ecommerce catalogues matters in 2026

Bfcache for ecommerce catalogues matters in 2026 because Chrome is finishing the unload deprecation this month, and collection to product to back is still the hop most shoppers repeat. web.dev's guide, last updated in September 2026, says 1 in 10 desktop navigations and 1 in 5 mobile navigations are back or forward. On a furniture grid that is the sofa they just opened.

A restore is not an HTTP cache hit. The browser keeps the page in memory, pauses timers, and shows that snapshot again. No TTFB. No second LCP download. Google's Core Web Vitals playbook lists eligibility as a high-leverage LCP and CLS win because the layout is already settled.

Chrome's unload deprecation timeline, updated 14 July 2026, puts 100 percent of Chrome page loads on milestone 154, dated 22 September 2026. Two days from this note. Sites that still register unload for a beacon will lose that callback, and they have already been paying for it with a blocked restore on desktop Chrome and Firefox.

Their Nibs is a Shopify rebuild I shipped as contract work on an agency team. Published numbers: 31 percent more conversions, 48 percent more orders. The lift came from the purchase journey, including collection to product on a phone. Bfcache was not that rebuild. It is how I would now keep that hop free when they hit back.

This sits next to View Transitions API for ecommerce and Speculation Rules for ecommerce. Rules fetch the next document. Transitions animate the swap. Bfcache restores the document they already left. You can ship all three. You cannot replace any of them with Cache-Control: no-store on a public PLP.

How bfcache for ecommerce catalogues actually works

Bfcache for ecommerce catalogues actually works as a frozen memory snapshot of the outgoing HTML document, restored on a history navigation when nothing on that page, or in a same-origin iframe, blocked the freeze. Destroy is postponed, JavaScript is paused, and a later back click unpauses the same page.

bfcache for ecommerce catalogues testing on a phone and laptop together

pageshow fires after load and again on every restore. persisted is true only for a restore. That is the hit signal. pagehide with persisted true means the browser intends to cache. It is not a guarantee. Chrome's Page Lifecycle API also fires freeze and resume. I close IndexedDB there. I still measure hit rate from pageshow.

The blockers I see on live catalogues, in the order I audit them:

  1. An unload listener. Desktop Chrome and Firefox refuse the page. Safari may freeze it and skip the callback, which is why those beacons were already fiction. Lighthouse still has no-unload-listeners. Move the work to pagehide or visibilitychange.
  2. Cache-Control: no-store on the HTML document. Browsers have historically refused bfcache for that header even though bfcache is not the HTTP cache. Reserve no-store for login, account and pay. Public collections can use no-cache or max-age=0. Those do not block the freeze.
  3. Open IndexedDB, in-flight fetch or XMLHttpRequest, or a WebSocket. Chrome 149 and Safari no longer block on an open WebSocket. Other browsers still can. Close on pagehide. Reopen on pageshow.
  4. A same-origin iframe that registered unload. Chat and payment buttons do this. Chrome's notRestoredReasons API, shipped from Chrome 123, reports iframe id and name for same-origin frames. Cross-origin frames come back as masked.
  5. A leftover window.opener from target="_blank" without noopener. Modern browsers default to noopener. Old Magento CMS blocks do not.

Shopify's Hydrogen caching guide maps CacheNone() to no-store. Use it on Customer Account API routes and on a Storefront API customer query. Do not put it on a public collection loader. CacheShort() keeps a grid fresh. If HTML must stay out of a shared cache, private, max-age= still lets that shopper's browser keep the document.

WooCommerce's caching plugin guide keeps Cart, Checkout and My Account out of the page cache. That is the HTTP cache, not a reason to stamp no-store on every category. Adobe's Fastly troubleshooting wants origin Cache-Control max-age greater than zero on cacheable storefront HTML. An extension that emits no-store because a quote cookie exists will miss Fastly and miss bfcache.

On restore, refresh mini-cart, wishlist, logged-in price, and any GPT slot that should rotate. Refetch the cart when event.persisted is true. If the shopper logged out in another tab, reload. web.dev's cookie check on pageshow is the pattern I copy.

Stopwatch and notebook during a speed pass

Production checklist for bfcache for ecommerce catalogues

The production checklist for bfcache for ecommerce catalogues is: no unload, no no-store on public HTML, refresh cart on restore, and keep checkout out of the freeze conversation.

  1. Search the theme, GTM container and app embeds for unload and onunload. Replace with pagehide. Send analytics on visibilitychange when the document becomes hidden.
  2. Send Permissions-Policy: unload=() on collection and product templates. Chrome 115 added that opt-out. Do not keep the Chrome 117 opt-in after this month's rollout.
  3. Add beforeunload only while checkout has unsaved address fields, then remove it.
  4. Inspect document Cache-Control on a guest collection, an indexable Magento layered URL, a Shopify collection and a WooCommerce shop page. Fix no-store on a public grid. Leave it on cart, checkout, account and login.
  5. Close IndexedDB and abort in-flight grid fetches in pagehide. Reopen in pageshow. Do not open two connections because you also listened to resume.
  6. Add rel="noopener" on leftover target="_blank" product cards.
  7. On pageshow with persisted, refetch the cart endpoint and update the badge. Do not refetch the collection HTML.
  8. If the restore happens after logout, location.reload(). Keep history so forward still works.
  9. Run Chrome DevTools Application, Back/forward cache, on the live PLP and PDP. Fix every Actionable row before you argue with RUM.
  10. Pair with Speculation Rules on the same tiles if you already prerender. Cancel view transitions on tap. Bfcache is the return trip.

Do not disable bfcache to fix a stale mini-cart. That is the publisher mistake web.dev calls out for ads. Refresh the widget. Keep the snapshot.

What breaks bfcache for ecommerce catalogues

Bfcache for ecommerce catalogues breaks first on unload you did not write. Review apps, chat, heatmaps and older GTM tags still attach it. Chrome reports unload-listener on the top frame or masked for a cross-origin iframe. You cannot edit PayPal's iframe. You can stop loading it on the collection. The same diet as reduce JavaScript on checkout pages helps here.

no-store on the HTML is the second break. Magento extensions are the usual source. Fastly then cannot cache the page either. WooCommerce hosts that copy no-store onto every URL with a session cookie make the same mistake. WooCommerce only asks you to exclude cart, account and checkout. Hydrogen's CacheNone() is for customer data. A public grid that "must never be stale" still belongs on no-cache or a short max-age, then a pageshow cart refresh.

Compressed product photography on a light table

Open connections are the third. A live-search websocket, an IndexedDB product cache that never closes, or a long-polling stock endpoint will evict or block the freeze. Close on the way out.

Stale personal data is the fourth. A restore after logout that still shows a stored card nickname is a bug. Reload when the session cookie is gone. Yesterday's sale price for thirty seconds is usually fine. Another customer's name is not.

Analytics under-count is the fifth. Many tags skip pageshow. Pageviews drop, and someone "fixes" it with no-store. Count the restore if you need parity with CrUX. Do not block the restore to keep a dashboard pretty.

Soft navigations inside Hydrogen or Hyva do not use bfcache. The browser never left the document. That is expected.

How to measure bfcache for ecommerce catalogues

You measure bfcache for ecommerce catalogues with pageshow.persisted, Navigation Timing notRestoredReasons, and a CrUX navigation-type split, not with a Lighthouse screenshot of a cold PDP. web.dev's measurement section is the method I ship.

On every collection and product view, read performance.getEntriesByType('navigation')[0].type. Send back_forward as a failed restore candidate. On pageshow with persisted, send back_forward_cache. Hit ratio is cache events over those two. Do not expect 100 percent. Chrome discards snapshots to free memory. A closed tab or a restart will never restore. You are hunting pages where the document itself is the blocker.

notRestoredReasons is null on a non-history navigation and also null when there were no reasons. Null is not a hit. event.persisted is the hit. Reason strings change. Chrome 123 showed unload-handler in one build and unload-listener in the docs. Group them.

Chrome DevTools Application, Back/forward cache, is the lab pass. It lists Actionable rows. Lighthouse 10 added a similar audit. Run the panel on a phone UA as well: mobile Chrome may still try a restore with unload, desktop Chrome will not.

CrUX already treats a restore as a page visit. Google's CrUX versus RUM note is why in-house LCP looks worse than Search Console if you only hook load. The web-vitals library reports restore LCP as the time from pageshow to the next paint, and it resets INP and CLS. Annotate TTFB by navigation type. Do not average a restore into TTFB. There was no network.

Checkout on a phone with a simple form

I do not invent a conversion percentage for a memory snapshot. Their Nibs published +31 percent conversions after a storefront rebuild. The Magento retail conversion case published +$2.5m year-on-year sales. Those figures are journey work, not a promise that one removed unload listener will move revenue.

Watch field LCP and INP on collection templates after you ship. If LCP gets worse, you are probably counting only cold loads. If INP gets worse on restore, the pageshow handler is doing too much. Refetch the cart. Do not rebuild the grid.

Related work on this site

Related work on this site is catalogues where the back button is the comparison tool. The Their Nibs Shopify rebuild is a fashion storefront whose collection tile is the product. The Skanvi furniture storefront is WooCommerce with large stills. The retail conversion Magento programme is the catalogue where a blocked restore is a blocked browse. All three were contract work on an agency team. I would keep those PLPs eligible, refresh the mini-cart on pageshow, and leave checkout on no-store.

FAQ

Does bfcache for ecommerce catalogues replace Speculation Rules?

Bfcache for ecommerce catalogues does not replace Speculation Rules. Rules prefetch or prerender the next HTML. Bfcache restores the HTML the shopper already had. Use document-rule prerender on product links, then let the collection restore when they hit back. Exclude cart and checkout from both.

Will bfcache for ecommerce catalogues restore checkout?

Bfcache for ecommerce catalogues should not be your plan for checkout. Pay pages belong on no-store. Account and login belong there too. If a restore would show a stale wallet or another session, reload on pageshow or keep the page ineligible.

How do I test bfcache for ecommerce catalogues in Chrome?

You test bfcache for ecommerce catalogues in Chrome with DevTools Application, Back/forward cache, on the live PLP, then with notRestoredReasons in the field. The panel lists Actionable blockers. The API tells you which iframe still ships unload after you cleaned the theme.

Should I set no-store to keep bfcache for ecommerce catalogues fresh?

You should not set no-store to keep bfcache for ecommerce catalogues fresh. no-store opts the document out. Use no-cache or a short max-age on public HTML, and refresh cart, price and ads in pageshow when persisted is true.

CDN and hosting notes next to a keyboard

Keep reading

Contact if you want this kind of work on a live store.