<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: not the same</title>
    <description>The latest articles on DEV Community by not the same (@ntsame).</description>
    <link>https://dev.to/ntsame</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4241172%2Fd987f629-4da8-4226-99ad-2fb1f3e40fdf.png</url>
      <title>DEV Community: not the same</title>
      <link>https://dev.to/ntsame</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/ntsame"/>
    <language>en</language>
    <item>
      <title>Your Back button is losing you sales: fixing bfcache on e-commerce sites</title>
      <dc:creator>not the same</dc:creator>
      <pubDate>Thu, 12 Nov 2026 13:35:33 +0000</pubDate>
      <link>https://dev.to/ntsame/your-back-button-is-losing-you-sales-fixing-bfcache-on-e-commerce-sites-bcc</link>
      <guid>https://dev.to/ntsame/your-back-button-is-losing-you-sales-fixing-bfcache-on-e-commerce-sites-bcc</guid>
      <description>&lt;p&gt;A shopper opens a product, taps &lt;strong&gt;Back&lt;/strong&gt;, and the catalog loads from scratch: filters reset, scroll position lost, infinite scroll back to page one. On mobile, where people jump between the list and product pages a dozen times per visit, this is one of the quietest conversion killers we see in e-commerce audits.&lt;/p&gt;

&lt;p&gt;Browsers already have a fix for it. It's called the &lt;strong&gt;back/forward cache (bfcache)&lt;/strong&gt;: when the user navigates away, the browser keeps a snapshot of the whole page in memory, JS heap included, and restores it instantly on Back. No network, no re-render, same scroll, same filters.&lt;/p&gt;

&lt;p&gt;The catch: a lot of stores quietly opt out of it without knowing. Here's how to find out why and fix it.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Check if your page is eligible
&lt;/h2&gt;

&lt;p&gt;Chrome DevTools has a built-in test:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;DevTools → Application → Back/forward cache → Test back/forward cache&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;It navigates away and back, then lists the exact reasons the page wasn't restored. Run it on your catalog, product page and search results. Those are the pages where Back is used the most.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Get the reasons from real users
&lt;/h2&gt;

&lt;p&gt;DevTools tells you about your machine. For field data, Chrome exposes &lt;code&gt;notRestoredReasons&lt;/code&gt; on the navigation entry:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;nav&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;performance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getEntriesByType&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'navigation'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nav&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="s1"&gt;'back_forward'&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;nav&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;notRestoredReasons&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// send it to your analytics to see blockers across real sessions&lt;/span&gt;
  &lt;span class="nb"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nav&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;notRestoredReasons&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A back/forward navigation that shows up as a regular page load means bfcache was missed. Logging the reasons for a week usually shows one or two scripts responsible for most of the misses.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. The usual blockers
&lt;/h2&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;unload&lt;/code&gt; listeners
&lt;/h3&gt;

&lt;p&gt;The classic one. An &lt;code&gt;unload&lt;/code&gt; handler makes the page ineligible in most browsers, and it's often not even your code: old analytics snippets, chat widgets, A/B testing tools.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// ❌ blocks bfcache&lt;/span&gt;
&lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'unload'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sendStats&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// ✅ fires on navigation and keeps the page eligible&lt;/span&gt;
&lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'pagehide'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sendStats&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// ✅ even better for analytics: also covers tab switching and app close on mobile&lt;/span&gt;
&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'visibilitychange'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;visibilityState&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="s1"&gt;'hidden'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;navigator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sendBeacon&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'/collect'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To find who registers &lt;code&gt;unload&lt;/code&gt;, search your bundle and third-party tags, or check the DevTools test above. It names the frame.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;Cache-Control: no-store&lt;/code&gt; on public pages
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;no-store&lt;/code&gt; is one of the most common reasons pages are excluded from bfcache. It makes sense on the cart, checkout and account pages. It doesn't make sense on a catalog page that is the same for every visitor, but frameworks and caching plugins sometimes send it site-wide, for example once a session cookie is set.&lt;/p&gt;

&lt;p&gt;Check the response headers of your category pages:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl -sI https://your-store.com/category/shoes/ &lt;span class="o"&gt;|&lt;/span&gt; grep -i cache-control
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If public pages come back with &lt;code&gt;no-store&lt;/code&gt;, find where it's set (middleware, plugin, CDN rule) and limit it to pages with personal data.&lt;/p&gt;

&lt;h3&gt;
  
  
  Open connections
&lt;/h3&gt;

&lt;p&gt;Open WebSockets, WebRTC connections and some other live connections can prevent caching. Close them when the page is hidden and reopen them when it comes back:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="n"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;socket&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;WebSocket&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'wss://example.com/live'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'pagehide'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;socket&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;close&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'pageshow'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;persisted&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nf"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nf"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  4. Handle the restore properly
&lt;/h2&gt;

&lt;p&gt;Once bfcache works, remember the page comes back exactly as it was left, including stale data. On a store that usually means the cart badge:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'pageshow'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;persisted&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// restored from bfcache: refresh data that may have changed&lt;/span&gt;
    &lt;span class="nf"&gt;refreshCartCount&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nf"&gt;refreshStockBadges&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The same applies to analytics: a bfcache restore doesn't fire a new page load, so if you want those page views counted, send them from &lt;code&gt;pageshow&lt;/code&gt; with &lt;code&gt;event.persisted === true&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Don't break it with infinite scroll
&lt;/h2&gt;

&lt;p&gt;bfcache restores the in-memory page, so infinite scroll works fine &lt;strong&gt;when the cache hits&lt;/strong&gt;. When it misses, the user lands at the top of page one. A cheap fallback is to keep the state in the URL:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// after loading page 3 with filters applied&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="no"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;location&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;href&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;searchParams&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'page'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'3'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;history&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replaceState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then a cache miss still brings the shopper back to the same listing instead of the start.&lt;/p&gt;

&lt;h2&gt;
  
  
  Quick checklist
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Run the DevTools bfcache test on catalog, product and search pages.&lt;/li&gt;
&lt;li&gt;Replace &lt;code&gt;unload&lt;/code&gt; with &lt;code&gt;pagehide&lt;/code&gt; or &lt;code&gt;visibilitychange&lt;/code&gt;, including in third-party tags.&lt;/li&gt;
&lt;li&gt;Keep &lt;code&gt;Cache-Control: no-store&lt;/code&gt; only on pages with personal data.&lt;/li&gt;
&lt;li&gt;Close WebSockets on &lt;code&gt;pagehide&lt;/code&gt;, reopen on &lt;code&gt;pageshow&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Refresh the cart and stock on &lt;code&gt;pageshow&lt;/code&gt; when &lt;code&gt;event.persisted&lt;/code&gt; is true.&lt;/li&gt;
&lt;li&gt;Keep filters and pagination in the URL as a fallback.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Back navigation is something every shopper does many times per visit, and it's usually free to fix. If you're working on mobile performance more broadly, we covered LCP, INP and CLS in &lt;a href="https://ntsame.agency/blog/pagespeed-optimization/" rel="noopener noreferrer"&gt;our PageSpeed optimization guide&lt;/a&gt;, and other places where stores lose buyers in &lt;a href="https://ntsame.agency/blog/cro-checklist/" rel="noopener noreferrer"&gt;this CRO checklist&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;We're &lt;a href="https://ntsame.agency" rel="noopener noreferrer"&gt;not the same&lt;/a&gt;, a web development studio from Ukraine working on e-commerce and B2B projects.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>performance</category>
      <category>ecommerce</category>
    </item>
  </channel>
</rss>
