How to Track Single Page Applications with GA4 & GTM

Many websites have adopted the Single Page Application structure to provide a responsive and smooth user experience and more will join their ranks in the future.

But this can make tracking a bit more complex compared to traditional websites. Now that the date of Universal Analytics’s retirement draws closer, it’s even more important to figure out how to track single page applications (SPAs).

We will also cover some common challenges that arise when tracking SPAs. Additionally, we will provide some best practices for optimizing your SPA tracking setup.

Here’s a quick overview of what you’ll learn:

What are Single Page Applications?

How to Track SPAs?

Common Challenges with Tracking SPAs

Best Practices to Track SPAs

Let’s get started!

What are Single Page Applications?

A single page application or website is one where each page of the content is not required to be loaded every time you navigate to different pages because it loads everything in the first instance.

Now, you might see that the URLs change when you click on different links, buttons, and pages, but they are just to give you an idea that a new page has been loaded when technically it does not as the content is served dynamically.

So if there’s no refresh then it means the tracking code only loads and fires once regardless of what other content you see. 

Logically, that also means you would see only one session for that user because the page never reloads, which has been the case with UA.

Another important thing to note is that Single Page Applications can be confused with Single Page Websites, but they are different.

Single page websites have all the content on one page where you scroll through different sections to get the information you want. There’s often a sticky navigation bar that follows you around.

Clicking on the links/buttons often leads you to that section on the same page, as they are anchor links without reloading the page.

GA4 came with Enhanced Measurement which can be helpful to track SPAs in general. However, for some SPAs, you might need to do additional work with GTM to correctly track them.

But if it is such a problem, why do SPA websites exist? Here are some reasons:

  1. They load very fast as all the resources like HTML, CSS, and Scripts in the backend load simultaneously. This also makes it easier for SPAs to provide smooth transitions while providing all the information quickly.
  2. SPAs can cache data effectively as they send a single request to the server and save all the data which can also be used if the users go offline or have a bad internet connection.
  3. They are easy to debug with the browser’s developer tools because it’s easier for the devs to go through the rendered JS vs so many lines of code.
  4. Development of SPAs can be much faster compared to traditional websites because the front end and back end can be separated. As a result, more developers can work at the same time, and making changes to one end doesn’t affect the other.
  5. They are easy to convert to iOS and/or Android apps as the same backend web app code can be used to develop the apps.

Some major examples of SPAs are Netflix, Facebook, Twitter, and Gmail, so they are not as problematic as you might think.

How to Track SPAs?

There are generally three widely used methods to track SPAs. These are:

  1. GA4’s Enhanced measurement to track pageviews
  2. GTM’s History Change trigger
  3. Data layer Push and GTM

So let’s see how you can use each.

GA4’s Enhanced Measurement to Track Pageviews

To do this, we need to go to the Stream settings. Click on the Admin cog in the bottom left → Data Streams under the property column → Select the stream.

You will now see the Web stream details, so click on the settings cog under Enhanced measurement.

Here click on the Show advanced settings. 

Check the Page changes based on browser history events.

Other Enhanced measurement events like Scrolls, Outbound clicks, and Site search might not work as expected as they will understate or overstate their numbers. Therefore it’s better to turn them off or set them up with GTM to ensure they work properly.

Once all is done, you can click on the Save button in the top right corner.

To verify whether it’s working properly or not, we can go to GTM’s preview mode. Log into GTM and click on the Preview button in the top right corner.

This should open another window where you can enter the URL of your website and click on the Connect button. Once connected, this page will refresh and show tracking information about your website.

Now you should be seeing the history change event, aka, gtm.historyChange-v2, when you’ve selected the GTM container on the top.

Next, you can click on GA4’s measurement ID on the top to see if the pageview event is being sent or not.

What if you don’t see the events being sent to GA4? Then, it’s time to try the second method.

GTM’s History Change Trigger

As you may have noticed, this method also tries to listen to changes in the website’s history as GA4’s built-in enhanced measurement does.

This history change event can then be used to send page view events to GA4. How? 

Let’s start by logging into GTM. Next, go into Triggers and click on the New button.

Here, scroll down and choose History Change as the trigger type.

Next, set it to fire on All History Changes and Save it.


It should work now, but let’s test it. Let’s go into GTM’s preview mode as we did in the first method.

As you can see, we have the GA4 config tag firing, but we also have multiple History events, which can happen with some SPA websites.

If this happens, then you should investigate and set triggers based on some changes vs all changes, so that the data is not being polluted.

Data layer Push and GTM

The third method works when the first two methods fail and involve using a developer to push a data layer into the code which you can use to store values and trigger pageviews. 

Here’s an example of how the code could look:


 window.dataLayer = window.dataLayer || [];


 'event': 'virtualPageview',

 'pageUrl': ',

 'pageTitle': 'Learn GA4' //Some name for the page/content to differentiate



Values for pageUrl and pageTitle should be populated dynamically as they will change for every page and/or content type.

Ideally, this will be sorted by the developer along with any hashtags, question marks, and other query parameters in the URLs.

Once the data layer has been pushed, we can complete the setup in GTM in the following three steps:

  1. Setting up two data layer variables
  2. A custom event trigger
  3. GA4 page_view event tag to send the data to GA4

Let’s begin by setting up the data layer variables, one for the pageUrl and one for the pageTitle. 

  1. Go into Variables → Click on New → Select the Data Layer Variable type and set up the variables like this:

Now we can store the values of the page URL and page title in data layer variables.

  1. Next, we want to set up a trigger based on the custom event that is mentioned in the data layer above, i.e., virtualPageview. Go to Triggers → New → Select Custom Event as trigger type and set up as follows:

  1. Go to Tags → Click on New → Select GA4 Event as the tag type and set up as shown below:
    1. Event Name: page_view
    2. Event Parameters and Value
      1. page_location value of DLV – pageUrl variable
      2. page_title value of DLV – pageTitle variable
    3. Trigger Type: whenever there’s a virtualPageview custom event detected

Don’t forget to test the third method in GTM’s preview mode as well as GA4’s DebugView, to ensure that you’re receiving the correct data, especially for the page_location (URL) and page_title parameters.

Why didn’t we add these parameters to the GA4’s Configuration Tag? This is because GA4 looks at the parameters only when the page loads, which means only once for an SPA website.

So, you don’t get to see the later values that keep changing, you only get the values sent the first time when the page loads.

Some of these methods can be somewhat tricky, but they are doable for the most part.

Common Challenges with Tracking SPAs

Knowing the challenges with SPAs isn’t necessary but it will help you to deal with them better. Let’s learn about some of the common ones:

  1. You won’t be able to track anything if Javascript is not enabled, as SPAs don’t work without JS. That’s something that cannot be bypassed easily. However, there are solutions like doing server-side rendering, even though it can still get complicated as other functions might not work properly.
  2. If the URL has different parameters and fragments like hashtags or question marks, then it won’t be tracked by default. So, you will have to store the full URL in the Javascript variable using the window.location.href value and then override the page_location parameter’s value with this variable for every GA4 event tag.

  1. If several history change events show up in GTM preview mode, they should be looked into to eradicate duplicate data, by adjusting the trigger to fire on some specific history change events.
  2. When using the data layer method you see the virtualPageview event every time you load the website, then you should disable the Send a page view event when this configuration loads option. 

  1. When there is a rogue referral issue, i.e., when Google attributes paid traffic as organic traffic, this should be handled. You can read more about the rogue referral issue in Simo Ahava’s article. Unfortunately, this fix doesn’t work with GA4 at the moment. 

This is not an exhaustive list and there could be other challenges based on your website’s setup, but these should help you to know what to expect.

Best Practices to Track SPAs

Here are some best practices to keep in mind when you’re tracking SPAs:

  1. Use the developer’s help if it’s available and go through the data layer method.
  2. Test and verify your setup with GTM’s preview mode and GA4’s DebugView, to ensure there are no duplicate or wrong data captured.
  3. If your website has URLs with fragments, i.e., #, then ensure you’re storing the full URL in the Javascript variable and overriding the page_location parameter with the value of that variable for all the GA4 event tags.
  4. Don’t add page_location and page_title parameters in the GA4 configuration tag. Create a separate GA4 event page_view event tag to override these parameters.
  5. Check if you need to use the Send a page view event when this configuration loads option with the GA4 configuration tag or not.

These best practices should be helpful enough to avoid common data collection issues.

[("push_subscription_ids", "!=", False)] Webuzz - Ultimate Text Copy Disabler

Webuzz - Ultimate Text Copy Disabler

This page has disabled text copying, right-clicking, keyboard shortcuts, and more.