quest_gtm_bfcache_page_views_ga4.exe
_
□
×

bfcache Google Analytics Change: GTM Now Counts the Back Button

bfcache Google Analytics change: since November 9, 2026, GTM counts back button restores as GA4 page views. Who is affected, double counting, how to check.

gtm ga4 tracking data-quality bigquery guide

In a few weeks, someone will show you a GA4 page view chart going up for no obvious reason and ask which campaign did so well. It might not be a campaign. On November 9, 2026, Google Tag Manager announced that tags counting page views on history change will also record pages restored from the bfcache, the browser’s back/forward cache. In plain terms: for bfcache Google Analytics tracking, the Back button, which often flew under the radar, is about to start producing page_view events.

On the substance, that’s good news: an undercount is being fixed. It’s bad news if you compare November with October without knowing, and a real source of duplicates for sites that had already rigged their own workaround. Here’s what changes, who is affected, and how to measure the impact on your data before the year-end reporting crunch.

What Google Tag Manager changes on November 9, 2026

The Google Tag Manager release note of November 9, 2026 is titled “Back/forward cache support for page view on history change events.” It says that web measurement tags subscribed to page views on history change will record a page view when a page is restored from the bfcache, mirroring what they already do on non-cached back and forward navigation (GTM release notes).

Two details matter. First, the note uses the future tense (“will be updated”): this is an announced rollout, not a switch flipped at the same minute for every container. Second, it gives no impact figure. Measuring it is on you, and that’s what the rest of this guide covers.

bfcache in two minutes

When you leave a page, the browser normally destroys it. With bfcache, it pauses it instead, JavaScript included, and keeps a full snapshot in memory. Hit Back and the browser pulls the snapshot out: the page reappears instantly, with no network request and no reload. Chrome has done this since version 96, and so do Firefox and Safari (web.dev).

For the user, it feels like magic. For a measurement tool, it’s a blind spot: no reload means no page load, which means no page_view. The only signal is the pageshow event, whose event.persisted property is true when the page comes out of the cache. Web.dev doesn’t sugarcoat it: many analytics libraries don’t count these restores, so you’re probably already undercounting in browsers that use bfcache.

Before and after: same journey, two counts

Take a mundane journey: a category page, a product page, then the Back button.

StepBefore (bfcache)After the rollout
Land on the category pagepage_viewpage_view
Click through to the product pagepage_viewpage_view
Back, page restored from bfcachenothingpage_view
Back, page not cachedpage_viewpage_view

The last row explains Google’s logic: depending on whether the browser had kept the page in cache, the same gesture did or did not produce a page view. The change aligns both cases. Your measurement becomes more consistent, but its baseline moves.

bfcache Google Analytics impact: who is affected?

Not everyone is in the same boat. The deciding factor is whether a page view is triggered by the browser history.

Sites running default enhanced measurement

In a GA4 web stream, enhanced measurement sends a page_view on every page load and every browser history change (pushState, replaceState, popstate). The “Page changes based on browser history events” option is on by default (GA4 help). If you’ve never touched it, you’re very likely affected, even on a classic multi-page site that has nothing of a single-page app.

Single-page apps and their history triggers

On a single-page app (React, Vue, Next.js with client-side routing), two setups often coexist: enhanced measurement, or a GTM “History Change” trigger that pushes a virtual page_view, as the official guide recommends. The release note targets tags subscribed to history page views; it doesn’t say whether a custom History Change trigger also gets a new event on restore. I won’t make the answer up: test it in Preview mode (method below) before drawing conclusions.

Home-made pageshow listeners: the real risk

This is the case that worries me most. Teams that read web.dev added a pageshow listener to catch restores, roughly like this:

window.addEventListener('pageshow', function (event) {
  if (event.persisted) {
    window.dataLayer.push({ event: 'bfcache_restore' });
  }
});

Behind it sits a GA4 page_view tag fired on bfcache_restore. Once Google’s rollout lands, this setup and the native tag may both count the same restore. The result: two page_view events for a single Back click, and inflated pages per session that nobody can explain. If this code lives on your site, check it first, usually in the dataLayer, as covered in the GA4 data layer mistakes that wreck your data.

What moves, in one table

Your setupExpected effect
Enhanced measurement with history on (default)More page_view events on Back navigation
Single-page app with a GTM History Change triggerTest in Preview mode, the note doesn’t say
Home-made pageshow listener sending a page_viewDouble counting risk
History option off, no custom setupMost likely nothing changes

Measuring the impact in GA4 and BigQuery

The QA run in DebugView and Tag Assistant

The test takes five minutes in Chrome:

  1. Open GTM Preview mode and GA4 DebugView.
  2. Load page A, click through to page B, then hit Back.
  3. In Chrome DevTools, Application tab, “Back/forward cache” section, confirm the page really was restored from the cache (Chrome documentation). If it wasn’t, the test proves nothing: an unload handler or a Cache-Control: no-store header is enough to keep a page out of bfcache.
  4. Count page A’s page_view events in DebugView: one on return is healthy; two means you have a duplicate to hunt down.

The BigQuery query: spotting the step

In the interface, the increase will drown in November’s seasonal noise. In the BigQuery export, one pattern gives it away: the A, B, A sequence within a session. Before the change, a Back click restored from cache left no trace; afterwards, it produces that third page_view. Tracking the share of these sequences day by day reveals a step on the date the rollout reached your property.

WITH pv AS (
  SELECT
    event_date,
    user_pseudo_id,
    (SELECT value.int_value FROM UNNEST(event_params) WHERE key = 'ga_session_id') AS session_id,
    event_timestamp,
    (SELECT value.string_value FROM UNNEST(event_params) WHERE key = 'page_location') AS page_location
  FROM `my-project.analytics_123456789.events_*`
  WHERE _TABLE_SUFFIX BETWEEN '20261019' AND '20261130'
    AND event_name = 'page_view'
),
seq AS (
  SELECT
    *,
    LAG(page_location, 2) OVER (
      PARTITION BY user_pseudo_id, session_id
      ORDER BY event_timestamp
    ) AS page_two_back
  FROM pv
)
SELECT
  event_date,
  COUNT(*) AS page_views,
  COUNTIF(page_location = page_two_back) AS aba_returns,
  ROUND(100 * SAFE_DIVIDE(COUNTIF(page_location = page_two_back), COUNT(*)), 2) AS pct_returns
FROM seq
GROUP BY event_date
ORDER BY event_date;

The pattern isn’t unique to bfcache: a user clicking the category link again produces the same sequence. That’s why you watch the trend of the percentage, not its value. If pct_returns jumps by a few points from one day to the next with no site release, you’ve found your rollout date. To adapt the query to your tables, the foundation is the same as in the 10 essential BigQuery queries for GA4.

To go further, you can tag restores yourself, as web.dev suggests, with a navigation type parameter sent as a custom dimension. Handy for a one-off audit, as long as you don’t send a second page_view along the way.

The metrics that will move

More page views, but not more sessions or users: pages per session goes up mechanically. Path explorations will show back-and-forth moves that always existed but stayed invisible. And if other tags (Google Ads remarketing, Meta pixel) fire on the same page views, their volume follows. If a metric moves without explanation, cross-check with the method in GA4 missing data: Google bug or your tracking?: telling a Google change from a tracking problem often comes down to the exact date of the break.

What to do: keep it, filter it, or remove the duplicate

Keep the native behavior

My take: don’t try to neutralize the change. A Back navigation is a genuine page view, and your numbers were slightly underestimated on Chrome, Firefox and Safari until now. Turning off the history option in your GA4 stream to get the old volumes back would cost you far more, especially on a single-page app.

Remove the home-made pageshow listener

If you had your own workaround, remove it, or at least pause the tag it fires while you confirm the native tag is taking over. Keeping both guarantees double counting. It’s the same kind of cleanup as the one forced by the gtag(‘config’) change in GTM in October 2026: Google is taking back control of behaviors people used to patch on the side.

Annotate and warn people

Add a GA4 annotation on the date you see the step, and tell whoever reads the dashboards: marketing, leadership, product. A few percent more page views right before Black Friday, wrongly credited to a campaign, costs more than one line of explanation sent in time.

QA checklist

CheckWhereExpected result
”Page changes based on browser history events” optionGA4 web stream, enhanced measurementKnow its exact state
Home-made pageshow listenerSite code, custom GTM tagsRemoved or paused
Actual bfcache restoreChrome DevTools, Application tabPage restored from cache
Number of page_view events on BackGA4 DebugViewExactly one
Step in the A, B, A patternBigQuery exportRollout date identified
Annotation and team heads-upGA4, team channelDone before the November review

The November 9 change is small, technical and mostly healthy. The only real danger is discovering it in a steering committee. Run the Back button test today: it takes five minutes and will save you from explaining growth that doesn’t exist.