To track pageviews in a single page application, you listen for route changes instead of full page loads. Each time the URL changes through the History API, you send a virtual pageview event with the new page location and title to your analytics tool. Google Analytics 4 can do this automatically, or you can control it manually through Google Tag Manager or code.
That is the short version. The long version matters, because single page applications are the number one reason people open their analytics reports and see a flat line, a single landing page, or a bounce rate that makes no sense.
If you run a website built with React, Vue, Angular, Svelte or Next.js, this guide walks you through every reliable way to set up pageview tracking, how to test it, and how to fix the mistakes that quietly ruin your data.
Table of Contents
- Why pageviews break in a single page application
- How virtual pageviews work
- Track pageviews with Google Analytics 4 enhanced measurement
- Track pageviews in a single page application with Google Tag Manager
- Track pageviews with gtag.js and custom code
- Framework specific setup for React, Vue, Angular and Next.js
- Pageview tracking in Matomo, Plausible and other tools
- Common pageview tracking mistakes and fixes
- How to test and debug SPA pageview tracking
- Consent, privacy and data quality
- SEO considerations for single page applications
- Reporting on SPA pageviews
- Best practices checklist
- Final thoughts
- Frequently asked questions
Why Pageviews Break in a Single Page Application
Picture a visitor landing on your product page, clicking to pricing, then clicking to contact. On a normal website, the browser asks the server for a brand new HTML document each time. Your analytics script loads again, fires, and records three pageviews. Nobody has to think about it.
Now picture the same journey on a single page application. The browser loads one HTML shell once. Everything after that happens through JavaScript, which swaps content on screen and rewrites the address bar. The analytics script loaded once, fired once, and sees nothing else. Three screens, one pageview.
How a traditional website records a pageview
A classic analytics tag runs when the document loads. The page load event is the trigger, so every navigation that reloads the document produces a hit. This is simple, predictable and the reason pageview tracking never needed much explanation for older websites.
What changes in a single page application
In an SPA, navigation does not reload the document. Frameworks use the browser History API, mainly the pushState and replaceState methods, to change the URL without a request to the server. The back and forward buttons trigger a popstate event instead of a reload.
Your analytics code has no built-in reason to notice any of this. Unless you tell it, it keeps believing the visitor is still on the first page. The result is undercounted pageviews, inflated landing page numbers, inaccurate session paths and conversion funnels that look broken.
How Virtual Pageviews Work
A virtual pageview is a pageview you send manually, or semi-automatically, when the content changes but the document does not. Nothing is virtual about the visitor's experience. They saw a new screen. The word only describes how the hit reaches your analytics tool.
Every virtual pageview should carry the same details a real one would:
- The page location, meaning the full URL including the path and any query string
- The page title, which should describe the new screen and not the one before it
- The page referrer, which should be the previous screen in the app, not the original external source
Get those three right and your reports behave as if the site had never been an SPA at all.
There are three main ways to send virtual pageviews. You can rely on Google Analytics 4 enhanced measurement, use Google Tag Manager with a history trigger, or write the event yourself inside your router. Which one is best depends on how much control you want and how clean your app's titles and routes are.
Track Pageviews with Google Analytics 4 Enhanced Measurement
Google Analytics 4 includes an enhanced measurement setting called page changes based on browser history events. When it is on, GA4 watches for History API changes and sends a page_view event whenever the URL changes. For many simple SPAs, this works with no extra code.
How to turn it on
Open your GA4 property, go to the admin area, open data streams, choose your web stream and open enhanced measurement. Make sure the page views option is enabled, then open its advanced settings and confirm the history events setting is on.
When enhanced measurement is enough
It works well when each route has a clear URL, your document title updates at the right moment, and you are not also sending pageviews manually from Tag Manager or code. That last point is the big one. If both run, you get duplicates.
When enhanced measurement is not enough
It struggles when titles update a moment after the URL changes, when your app changes content without changing the URL, when you use hash-based routing, or when you need extra parameters on every pageview such as content group, logged-in status or product category. In those cases, take manual control.
Track Pageviews in a Single Page Application with Google Tag Manager
Google Tag Manager is the most popular route for marketing teams, because developers only need to install one container snippet and everything else happens in the interface. If you want a deeper walkthrough of how tracking fits into a broader marketing plan, our digital marketing services cover analytics setup from the start.
Disable the automatic pageview first
Before you add anything, decide who owns pageviews. If you plan to fire them through Tag Manager, turn off the history events option in enhanced measurement. Otherwise GA4 and GTM both fire and every navigation counts twice.
Set up the base tag
Create a Google tag with your measurement ID and let it fire on initial page load. This covers the first screen a visitor sees, which is a real page load.
Create the history change trigger
In Tag Manager, create a new trigger and pick the History Change type. This trigger fires whenever the URL fragment or the history state changes, which is exactly what an SPA does on navigation. Set it to fire on all history changes unless you need to exclude certain routes.
Create the GA4 event tag
Add a GA4 event tag, name the event page_view, and attach the history change trigger. Then add event parameters:
- page_location set to the Page URL variable
- page_title set to the Page Title variable, or better, a data layer variable if your title lags behind
- page_referrer set to the History Old URL Fragment, or a custom variable that holds the previous full URL
Handle the title timing problem
This is the bug that catches almost everyone. The history change fires the instant the URL changes, but many frameworks update the document title a few milliseconds later. Tag Manager reads the old title and your reports fill up with the wrong page names.
The reliable fix is to push a custom event to the data layer from your router after the new route and title are ready, then trigger the pageview from that custom event instead of the raw history change. A short example:
window.dataLayer = window.dataLayer || [];
window.dataLayer.push({
event: 'virtual_pageview',
page_path: window.location.pathname + window.location.search,
page_title: document.title,
page_location: window.location.href
});
Use a Custom Event trigger named virtual_pageview, read the values from data layer variables, and your titles will be correct every time.
Preview before publishing
Use Tag Manager preview mode, click through several routes, and confirm one page_view appears per navigation, in order, with the correct title. Only then publish.
Track Pageviews with gtag.js and Custom Code
If you do not use Tag Manager, you can send virtual pageviews directly. Start by loading gtag.js and stopping the default automatic pageview so you control timing:
gtag('config', 'G-XXXXXXXXXX', {
send_page_view: false
});
Then, each time your router finishes a navigation, send the event yourself:
function trackPageview(url, title) {
gtag('event', 'page_view', {
page_location: url,
page_title: title,
page_path: new URL(url).pathname
});
}
Call that function after the new view has rendered, never before. This approach gives you the cleanest data and the most control, and it is the one most development teams prefer when they own the codebase. If you are building or rebuilding an application and want tracking planned in from the first sprint, our web development team can set the data layer up with the product.
Why send_page_view false matters
Without it, the config call fires an initial pageview, and your first navigation handler may fire another for the same screen. Set it to false and fire every pageview, including the first, from one place. One owner, no duplicates.
Framework Specific Setup for React, Vue, Angular and Next.js
The principle never changes: wait for the route to settle, then send one pageview. Only the hook point differs.
React and React Router
In a React app using React Router, listen to the location object inside a small component that sits within your router. Whenever the pathname or search changes, send the event.
import { useEffect } from 'react';
import { useLocation } from 'react-router-dom';
export function PageviewTracker() {
const location = useLocation();
useEffect(() => {
gtag('event', 'page_view', {
page_location: window.location.href,
page_title: document.title,
page_path: location.pathname + location.search
});
}, [location]);
return null;
}
If your titles come from a library such as React Helmet, remember that the title updates inside the same render cycle, so effect timing is usually fine. If you still see stale titles, wrap the call in a short timeout or push to the data layer after the title effect runs.
Vue and Vue Router
In Vue, the router exposes an after-navigation hook. It runs once the navigation is confirmed, which is the right moment.
router.afterEach((to) => {
gtag('event', 'page_view', {
page_location: window.location.origin + to.fullPath,
page_title: to.meta.title || document.title,
page_path: to.fullPath
});
});
Keeping a title in each route's meta field is a clean habit, because it removes any dependence on when the document title changes.
Angular
Angular's router emits a stream of events. Subscribe to the navigation end event and send your pageview there.
this.router.events
.pipe(filter(event => event instanceof NavigationEnd))
.subscribe((event: NavigationEnd) => {
gtag('event', 'page_view', {
page_location: window.location.origin + event.urlAfterRedirects,
page_title: this.titleService.getTitle(),
page_path: event.urlAfterRedirects
});
});
Using the URL after redirects prevents you from logging a route that the visitor never actually saw.
Next.js
Next.js is a hybrid. The first load is server rendered, and later navigation happens on the client, so you still need route-change tracking. In the app router, use the pathname and search parameter hooks inside a client component.
'use client';
import { usePathname, useSearchParams } from 'next/navigation';
import { useEffect } from 'react';
export default function Analytics() {
const pathname = usePathname();
const searchParams = useSearchParams();
useEffect(() => {
const url = pathname + (searchParams.toString() ? '?' + searchParams.toString() : '');
gtag('event', 'page_view', {
page_location: window.location.origin + url,
page_title: document.title,
page_path: url
});
}, [pathname, searchParams]);
return null;
}
Wrap the component in a suspense boundary where your framework version requires it, and keep the automatic initial pageview disabled so the first screen is not counted twice.
Hash based routing
Some older apps use fragments, such as a hash followed by a route name. GA4 treats fragments differently from paths, and standard reports can lump every screen under one URL. If you can, move to history-based routing. If you cannot, send the route through a custom page_path parameter so your reports separate the screens.
Pageview Tracking in Matomo, Plausible and Other Tools
Not every team uses Google Analytics. The pattern is the same everywhere, and only the call changes.
Matomo exposes a tracker queue. You push the new URL and title, then call the pageview method on the router change. Matomo also offers a history-change trigger in its own tag manager.
Plausible detects history changes automatically in its standard script, which makes SPA tracking nearly effortless. If you use hash routing, it has a separate script variant for that.
Product analytics tools such as Mixpanel and Amplitude usually need you to send a page viewed event yourself, with properties for path and title. Treat it as the same job: one event per settled route, with consistent property names.
Whichever tool you use, the rule holds. Decide who sends the pageview, send it once, and send it after the content is ready.
Common Pageview Tracking Mistakes and How to Fix Them
Most broken SPA tracking falls into a handful of patterns. Check these before you blame the tool.
Duplicate pageviews
The most common issue. It happens when enhanced measurement history events and a Tag Manager history trigger are both active, or when a router component re-renders and fires the effect twice. Fix it by choosing a single owner and, in React, making sure your effect depends only on the pathname and search values.
Missing pageviews on route changes
If you only see the landing page, your app navigates through the History API and nothing is listening. Turn on history events in enhanced measurement or add a trigger or router hook.
Wrong or stale page titles
Covered earlier, but worth repeating because it spoils content reports. Send the title from route metadata or after the title update, not at the instant the URL changes.
Broken referrers and self-referrals
If the referrer for each virtual pageview is your own domain, your traffic sources report starts crediting your site to itself. Pass the previous screen as page_referrer for internal navigation and make sure your own domain is excluded where needed.
Lost campaign data
Query strings carrying campaign parameters can disappear when a router cleans the URL on load. Capture the campaign parameters before the router rewrites the address, or your paid and email traffic will fall into direct. Our SEO company audits often find this one hiding behind a puzzling spike in direct visits.
Tracking interactions that never change the URL
A modal, a tab or a multi-step form can feel like a new page and still keep the same URL. If the step matters to your funnel, send a custom event or a virtual pageview with a meaningful path. Do not invent a pageview for every tiny interface change, though, or your numbers lose meaning.
Infinite scroll and filters
Endless lists and filter panels change content without navigation. Decide which states deserve their own URL. Those that do should be real routes. The rest should be events.
Inflated or deflated bounce and engagement numbers
GA4 measures engagement rather than the old bounce definition, and a session counts as engaged based on time, multiple page views or a conversion event. If your SPA sends no virtual pageviews, visitors who browse many screens look like single-page sessions and engagement looks worse than it is. Fixing pageviews usually fixes these numbers as a side effect.
How to Test and Debug SPA Pageview Tracking
Never trust tracking you have not watched fire. These tools make it quick.
Google Analytics DebugView
Enable debug mode through the Tag Assistant extension or the debug_mode parameter, then open DebugView in GA4. You will see each event as it arrives, with every parameter. Click through your app and confirm that each route produces one page_view with the right location and title.
Tag Assistant and Tag Manager preview
Preview mode shows which tags fired on which events. Look for the same tag firing twice on one navigation. That is a duplicate in the making.
Browser network tab
Open developer tools, filter network requests by the collect endpoint, and navigate. Each pageview should appear as one request. This is the ground truth when the interface tools disagree.
Realtime report
The realtime view in GA4 is a quick sanity check. If you click five screens and see one, something is missing. If you click one and see several, something is duplicating.
Test with direct landing
Do not only test by starting at the home page. Open a deep link in a fresh tab and confirm the first pageview fires once with the correct path. Many bugs only appear on direct entry.
Consent, Privacy and Data Quality
Pageview tracking sits inside privacy rules. If your audience includes visitors from regions with consent requirements, your tags should respect the visitor's choice. Google's consent mode lets tags adjust their behavior according to whether analytics storage is granted, and a consent banner should load before analytics tags fire.
For single page applications there is one extra detail. A visitor can accept or change consent mid-session without reloading the page, so make sure your consent update reaches the tags immediately. Otherwise the pageviews they generate after accepting go unrecorded until their next full load.
Also keep personal data out of your URLs. Email addresses, names and order identifiers in a query string will be sent as part of the page location. Strip or mask them before the pageview is sent.
SEO Considerations for Single Page Applications
Analytics tells you what visitors do. Search engines decide whether visitors arrive at all, and single page applications have their own challenges there.
Search crawlers can render JavaScript, but rendering is slower and less certain than reading plain HTML. Content that depends entirely on client-side rendering can be indexed late or incompletely. The common solutions are server-side rendering, static generation and prerendering, all of which deliver real HTML for each route.
Each route also needs its own unique title, meta description, canonical tag and crawlable link. Links built from click handlers instead of real anchor elements cannot be followed by crawlers.
Pairing clean analytics with technical optimization gives you the full picture: which routes earn impressions in search, which ones actually get visits, and which ones lose people. If you are planning a rebuild or a migration to a framework like React or Next.js, involve search and analytics early. Our web design team works with developers so that new interfaces look sharp without damaging crawlability or measurement, and our content writing service makes sure every route has copy worth ranking.
Reporting on SPA Pageviews
Once pageviews are flowing correctly, the standard reports start telling the truth. Here is where to look.
- The pages and screens report shows views by page path and title, so you can see your most visited routes.
- The landing page report shows where sessions begin, which should now reflect real entry points and not one lonely shell URL.
- Path exploration in the explorations area lets you follow how visitors move between routes.
- Funnel exploration turns a series of routes into a conversion funnel, and it only works well once pageviews are accurate.
- Linking Google Search Console to your property lets you compare search impressions with on-site behavior for each landing page.
Build a dashboard that shows views, engaged sessions and conversions per route. When a new release changes your routing, check it the next day. Tracking breaks quietly after deployments more often than anywhere else.
Best Practices Checklist
- Choose one owner for pageviews and disable every other source
- Disable the automatic initial pageview if you send pageviews manually
- Send pageviews only after the route and title have settled
- Keep a clear, unique title and path for every meaningful screen
- Pass the previous internal screen as the referrer
- Preserve campaign parameters before the router rewrites the URL
- Remove personal data from URLs before sending them
- Treat modals and steps as events unless they truly act as pages
- Test with deep links as well as the home page
- Recheck tracking after every release that touches routing
How AMICI Global Solutions Can Help
Tracking is the foundation of every marketing decision you will make. Without trustworthy pageview data, you cannot judge which content works, which campaigns pay back, or where visitors leave. At AMICI Global Solutions, we help businesses across Dwarka and Delhi NCR set up analytics that hold up, from tag planning and data layer design to reporting that a business owner can read without a glossary.
If you want to learn this skill yourself, our digital marketing course covers analytics, tag management and measurement. If you would rather hand it over, talk to our team and we will audit your current setup. You can also read more guides on our blog, learn about who we are, or explore how social media optimization feeds traffic into the pages you are now tracking properly.
Final Thoughts
A single page application does not break analytics. It just stops doing the one thing analytics quietly depended on, which was reloading the page. Once you replace the reload with a deliberate virtual pageview, sent once and sent at the right moment, your reports come back to life.
Start with the simplest option that fits your site. Use enhanced measurement if your routes and titles are clean. Move to Tag Manager with a custom data layer event when you need accurate titles and extra parameters. Write the event in your router when your team owns the code and wants full control. Then test every route, watch for duplicates, and recheck after each release.
Do that, and the numbers you see will finally match the journeys your visitors actually take.
Frequently Asked Questions
Q. How do I track pageviews in a single page application?
Listen for route changes and send a virtual pageview each time the visible screen changes. You can do this with the Google Analytics 4 enhanced measurement history setting, a Google Tag Manager history change trigger, or a custom event fired from your framework's router after the new content and title have loaded.
Q. Does Google Analytics 4 track single page applications automatically?
Partly. When the enhanced measurement option for page changes based on browser history events is enabled, GA4 records a page_view whenever the URL changes through the History API. It does not handle hash routing well, can capture stale titles, and cannot detect screens that change without a URL change, so many sites still need a manual setup.
Q. What is a virtual pageview?
A virtual pageview is a pageview event sent without a full page load. In a single page application, the visitor sees a new screen but the browser never reloads the document, so you send the event yourself, with the new page location, title and referrer, to make the visit appear in your reports as a normal page view.
Q. Why am I seeing duplicate pageviews in GA4?
Duplicates almost always mean two things are sending the same event. The usual causes are enhanced measurement history events running alongside a Tag Manager history trigger, a config call that fires an automatic pageview while your router also fires one, or a component that re-renders and triggers its effect twice. Pick a single source and disable the rest.
Q. What does send_page_view false do?
It tells the Google tag not to send the automatic pageview when it loads. You use it when you plan to send every pageview manually, including the first one, so that your router or Tag Manager is the only thing sending them. It prevents the initial screen from being counted twice.
Q. How do I use the Google Tag Manager history change trigger?
Create a trigger of the history change type, attach it to a GA4 event tag named page_view, and pass the page location, title and referrer as parameters. For accurate titles, many teams push a custom data layer event from the router after the route is ready and use that as the trigger instead.
Q. How do I track pageviews in React?
Add a small component inside your router that reads the location from React Router. Whenever the pathname or search value changes, send a page_view event with the current location and title. Disable the automatic initial pageview so the first screen is not recorded twice.
Q. How do I track pageviews in Next.js?
Create a client component that reads the pathname and search parameters, and send a page_view event inside an effect that runs when either value changes. The first load is server rendered but later navigation is client side, so you still need this tracking for every route change after the initial visit.
Q. Why are my page titles wrong in my single page application reports?
The history change usually fires before your framework finishes updating the document title, so the tag reads the previous screen's title. Send the title from route metadata, or fire the pageview from a custom event pushed after the title update, and the names in your content reports will be correct.
Q. Can I track hash based URLs in Google Analytics 4?
You can, but the fragment is not treated as part of the path in standard reports, so different screens may be grouped under one URL. The best fix is to move to history-based routing. If that is not possible, send the route as a custom page_path parameter so each screen is reported separately.
Q. How do I test whether my pageview tracking works?
Use DebugView in GA4, Tag Assistant or Tag Manager preview, and the network tab of your browser. Click through several routes and confirm that each navigation produces exactly one page_view with the right path and title. Also test by opening a deep link in a fresh tab to check the first load.
Q. Do single page applications hurt SEO?
Not by themselves, but pure client-side rendering can make indexing slower or less reliable. Server-side rendering, static generation or prerendering gives crawlers real HTML for each route. Unique titles, meta descriptions, canonical tags and real anchor links for navigation also help search engines understand and index each screen.
Q. Should every modal or tab count as a pageview?
Only when it behaves like a real page in the visitor's journey and matters to your funnel. Most modals, tabs and interface changes are better recorded as events. Over-counting them with pageviews inflates your numbers and makes path and funnel reports harder to read.
Q. Can I track single page application pageviews without Google Analytics?
Yes. Tools such as Matomo, Plausible, Mixpanel and Amplitude all support single page applications. Some detect history changes automatically, while others expect you to send a page view event from your router. The core rule is the same everywhere: one pageview per settled route, sent after the content is ready.
Q. How can AMICI Global Solutions help with analytics and tracking setup?
AMICI Global Solutions helps businesses in Dwarka, Delhi and across Delhi NCR plan, implement and audit analytics, from Google Tag Manager and GA4 configuration to reporting dashboards. You can reach the team through the contact page to book a review of your current tracking.