The Invisible Data Crisis: Why Single Page Application Tracking Isn't Working for You
9 min read
You think you've nailed your analytics setup. You implemented a Data Layer, dropped your Google Tag Manager (GTM) snippet, and followed the standard documentation for your Single Page Application (SPA). You see sessions flowing into your reports, but here’s the sober observation: your data is a ghost town of missing interactions and misattributed conversions.
Simul Sarker
Founder & Product Designer of DataCops
Last Updated
May 17, 2026
A 92% bounce rate on a React app that converts fine. That is the screenshot someone sends me at least once a month, usually with a panicked "is GA4 broken?" message attached. I have debugged this exact thing on more sites than I can count, across React Router, Next.js, Vue, and a few SvelteKit builds.
Here is the honest read. Your single page application tracking is not working because GA4 was built for an internet that reloaded the whole page on every click. SPAs do not do that. The browser swaps the view, the URL changes, and the analytics script never gets the signal it was waiting for. One pageview, then silence.
That much is a known problem. Every guide on the first page of Google will show you how to fix it with a History Change trigger or the GA4 SPA snippet. They are not wrong. But they stop exactly where the interesting part begins.
This is not a "how to configure GTM" post. This is a post about what you are actually measuring once the configuration is done. Because fixing the trigger does not fix the data. It just means you now accurately record a dataset that is still structurally unreliable. DataCops exists because the real fix is architectural, not a snippet.
Quick stuff people keep asking
Why does Google Analytics show only one pageview for my SPA? GA4 fires a page_view on the initial document load. After that, your SPA changes routes with the History API instead of reloading. No reload, no new page_view. GA4 sees one visit that never moves.
Does GA4 automatically track single page applications? Partly. Enhanced Measurement has a "page changes based on browser history events" option, and when it works it catches route changes. When it does not work, you get duplicates, missing events, or page paths that lag one navigation behind. It is not reliable enough to leave unchecked.
How do I track page changes in a single page application with GA4? Two routes. Turn on the history-events option in Enhanced Measurement, or wire a History Change trigger in Google Tag Manager that fires a GA4 event on every route change. The GTM route gives you more control over timing and what data you attach.
Why is my bounce rate 100% in a React app? Because GA4 counts a session as engaged based on events and time. If only one page_view ever fires and the user navigates entirely client-side, GA4 sees a single hit and calls it a bounce. The user read four pages. GA4 recorded one.
What is a History Change trigger in Google Tag Manager? It is a trigger that listens for pushState, replaceState, and popstate events, the browser APIs SPAs use to change the URL without reloading. When the history state changes, the trigger fires, and you hang a virtual pageview tag off it.
How do I send virtual pageviews in a Next.js app? Hook into the router events. In the App Router, watch the pathname; in the Pages Router, listen to routeChangeComplete. On each change, push a page_view to the data layer with the new path. Do not fire it before the route finishes resolving, or the path will be wrong.
Why are events missing from GA4 in my Vue app? Usually a race condition. The route changed and your event fired before GTM or GA4 finished initializing, or before the data layer had the updated page context. The event left the browser tagged with stale or empty data, so it looks missing or lands on the wrong page.
The data you fix is still the wrong data
Here is the part nobody on the SERP says out loud.
Fixing SPA tracking is an under-collection problem and an over-collection problem at the same time, and the two do not cancel out.
Under-collection: when your trigger misfires, races, or is just not configured, you lose real navigations. Across blocked scripts and broken SPA routing, 25 to 35% of genuine human sessions never get recorded properly. Real people, reading real pages, invisible.
Over-collection: bots and automated agents are very good at one specific thing, executing that initial document load. The first page_view, the one GA4 fires on load, the one that always works? Bots trigger it reliably. They do not click around your SPA the way a human does, but they do not need to. They already counted.
So think about what that does to the mix. You lose a third of your humans to broken routing. You keep nearly all of your bots, because bots live in the part of tracking that never breaks. Of the data that survives, 24 to 31% is bot-influenced. Your dataset does not just shrink. It tilts toward non-human traffic.
And here is the trap. You install the History Change trigger. The duplicate pageviews stop. The bounce rate drops to something believable. Everyone relaxes. The dashboard looks fixed.
It is not fixed. You changed the measurement. You did not change the contamination ratio. You are now measuring a bot-tilted dataset accurately, and accurate measurement of bad data is arguably worse, because it looks trustworthy.
Picture a B2B SaaS team I will not name. Marketing analytics company, built a real product, ran a honeypot to see what their signup funnel actually attracted. 3,000 signups came in. 77% were fraudulent. 650 of those accounts traced back to a single device fingerprint. One machine, wearing 650 faces. Every one of those fake sessions executed a page load. Every one of them could fire a page_view. None of them ever bought anything. If that funnel sat on top of an SPA, those 650 ghosts would be in the analytics, counted, blended into the conversion rate, indistinguishable from real demand on any dashboard.
That is the layer this topic exposes. SPA tracking is not just a routing bug. It is a quality bug wearing a routing bug's clothes.
Why the corrupted data does not stay in your dashboard
“If the damage stopped at a wrong bounce rate, this would be a minor annoyance. It does not stop there.
Modern ad platforms run on the conversion signals you send back. You connect GA4 to Google Ads. You wire Meta CAPI. Every SPA-generated conversion event, the ones you just worked so hard to make fire correctly, gets forwarded to those bidding algorithms as a training example.
Now feed those algorithms a dataset that is missing a third of real humans and padded with bots. The algorithm does what it was built to do. It studies your "converters," builds a profile, and goes hunting for more people like them. If a chunk of your converters are bots and automated agents, the algorithm learns to find bots and automated agents. It gets very good at it.
That is the causal chain none of the top-ranking SPA guides will draw for you. SPA tracking fixed, ad campaigns still underperforming, and the two feel unrelated. They are not. Garbage in, garbage optimized, garbage out. Your ROAS degrades not because the campaign is bad but because the data teaching the campaign is bad.
The root cause is not your trigger configuration. It is architectural. You are running a third-party analytics script that collects every session into one undifferentiated bucket, with no isolation, no filtering, no separation between "anonymous human," "identified human," and "obvious bot," and then you ship that bucket straight to ad platforms. The pipeline never had a checkpoint.
DataCops fixes the pipeline, not the snippet. First-party architecture running on your own subdomain, so the collection itself is far more resilient than a third-party script that gets blocked or races on route changes. Bot filtering at the ingestion point, before the data is ever counted, scored against an IP database of more than 361.8 billion addresses that separates residential from datacenter, VPN, proxy, and Tor. And two separate data tiers: anonymous session analytics that flow unconditionally because they are always legal, and identifiable data that is held until you actually have consent. Clean conversions, and only clean conversions, get forwarded to Meta, Google, TikTok, and LinkedIn through CAPI.
To be straight with you: DataCops is a newer brand than the analytics incumbents, and SOC 2 Type II is in progress, not finished. If you are a heavily regulated buyer you may want to wait for that paperwork. I would rather tell you that than pretend otherwise.
Decision guide
Small React or Vue site, no ad spend, just want honest internal numbers. Configure the GTM History Change trigger properly and turn on Enhanced Measurement history events. That is genuinely enough for you.
Next.js app, moderate Google Ads spend, conversions feel inflated. Fix the router-event tracking first, then look hard at what share of your converters could be automated. The fix and the audit are two different jobs.
You already fixed SPA tracking and campaigns still underperform. Stop tuning the campaign. The problem is upstream. Your conversion feed is contaminated and the bidding algorithm is learning from it.
SPA plus real ad budget plus you forward conversions to ad platforms. This is the case for a first-party, filtered pipeline. Fixing collection without filtering the data just means you contaminate the algorithm more accurately.
Enterprise, regulated, compliance signs off on every vendor. Get the SPA tracking correct now, and shortlist a first-party architecture for when SOC 2 Type II lands.
You fixed the symptom and called it the cure
The mistake I see, over and over, is treating SPA tracking as a checkbox. Trigger fires, duplicates gone, bounce rate looks normal, ticket closed. The dashboard went from obviously broken to quietly wrong, and quietly wrong is the more expensive state, because nobody investigates a dashboard that looks fine.
A working History Change trigger tells you that GA4 is now recording route changes. It tells you nothing about whether the sessions behind those route changes are human. Those are two different questions. The whole SPA-analytics genre answers the first one and pretends it answered the second.
So here is the question to take back to your own data. You fixed your SPA tracking last quarter. Has anyone since then actually checked how many of your recorded conversions came from a real person, or did you just confirm the events are firing and move on?