How to Track Single Page Applications with Google Tag Manager and Google Analytics

🚨 Note: All standard Universal Analytics properties will stop processing new hits on July 1, 2023. 360 Universal Analytics properties will stop processing new hits on October 1, 2023. That’s why it’s recommended to start using Google Analytics 4. We’ve also created a post on how to track single page applications with GA4 and GTM

Single Page Applications (SPAs) are built differently than regular websites, which causes some difficulties in tracking them correctly with Google Analytics.

Fortunately, it is possible to solve these issues with the help of Google Tag Manager.

GTM For Beginners

Sign up to the FREE GTM for Beginners Course...

In this article, you will learn how to track Single Page Applications with the help of GTM.

💡 Top Tip: The best practice is to try out new strategies on your demo website before implementing them live on your website.

Links:

The Rogue Referral Problem (Simo Ahava)

Track Single Page Application (E-Nor)

What are Single Page Applications (SPAs)?

Single Page Applications (SPAs) are web applications that load a single HTML page and dynamically update the content as the user interacts with the application, without requiring a full page refresh.

This allows for a more seamless and responsive user experience.

Single Page Applications often look like traditional websites, but they work very differently. In order to achieve a super-fast performance, SPAs load all of the HTML, CSS, and JavaScript resources required to navigate the site with the initial browser request.

After the initial load is completed and the user starts navigating on the site, SPAs only update the required content within the established page, instead of loading complete new pages every time. 

This makes them fast and responsive. Almost like a native App you’d see on iOS or Android phones.

To achieve the desired effect SPAs utilize modern JavaScript Frameworks such as Angular, React or Vue.

They are still fairly new, so you wouldn’t find them in combination with popular CMS such as Wordpress, Magento, Shopify or Joomla (although there are exceptions).

If you encounter an SPA it was most likely built from scratch by a developer or a team of developers.

This means SPAs are often not as “standard” as you might be used to and therefore Tracking also needs to be customized to work with these new types of websites. 

Why is it hard to Track Single Page Applications?

On traditional websites, Google Analytics and other tracking tools work by executing their code snippets every time a new page is loaded by the browser. Single Page Applications, however, don’t load new pages via a full server roundtrip, they swap out the content of the initially established page and only load the new content parts. You can observe this when navigating on an SPA in the browser tab: there is no loading animation when you click from one page of an SPA to the next.

Screen recording showing the behavior of a web Single Page Application in a browser.

SPAs lack the browser events that analytics software like Google Analytics rely on – if you track an SPA, you will likely only see one single page view per session, as only one roundtrip to the server is done. Google Tag Manager also relies on entire pages being loaded, so there are similar issues when using GTM on an SPA.    

Two Methods to Track Single Page Applications

Despite these challenges, there are several ways to properly track Single Page Applications from the web. This article will show you two ways how to do this: With the use of the history trigger, which is the easier option, and by adding a custom data layer.

History Change Trigger

When you click through an SPA with the GTM preview mode opened, you will see there’s no reload happening and no new events are shown. By the way, this is also a great way to test whether you are really dealing with a web SPA.

The first way to solve the tracking challenge is via the history listener API. As you may know, GTM allows us to access a vast number of browser APIs, like detecting a click or hovering over an element. The history API is one of the data sources we can listen to via GTM. 

Provided that the developer of the SPA has implemented the browser history functionality, we can hook into the browser history with GTM and see different entries for each SPA page that was visited by the user.  

To make use of this browser history information, we need to employ the corresponding trigger in GTM. It is called the History Change listener. You find it by clicking on Triggers → New and under Other you will find the History Change trigger.

Simply give this trigger a name and save it, then go check in the GTM preview pane whether this tracking works. Under summary, you should see a new History event whenever you click on a link of your SPA. 

Screenshot of GTM preview pane showing a History event in the Summary tab.

It’s worth noting that the history trigger doesn’t actually listen to a click. It only checks if anything was added or removed from the browser history API. Note that your SPA needs to make use of the history functionality of the browser. If that isn’t the case, this solution won’t work and you can move on to the Custom Data Layer section.

Let’s assume we’re dealing with a web site that enables us to use the history trigger in the desired way: What’s the next step we need to take in order to track our SPA correctly? 

First, check one of the history events in the summary tab and click on the Variables tab. You should be able to see that the Page Path URL is changing with every single history event.

animated .gif of GTM preview pane showing two history events with changing URL page path values.

Next, we want to add some auto variables that are provided by the history change trigger. To do this, go to VariablesConfigure and activate all checkboxes under History. Once enabled, you will see those history variables in the Variables tab in the GTM preview pane.  

Screenshot of the Built-in History Variables in GTM.

It depends on the architecture of your web SPA whether or not these history variables are useful for tracking. Any of the variables need to have been addressed by the developer of your web SPA.  

In order to use the variables for our page view tracking, we need to head over to the Tags section in GTM. Create a new tag and and name it accordingly, i.e. “GA – Pageview – all pages”.  Choose Google Analytics as the tag type and Page View as the track type. Make sure you’ve entered the Google Analytics tracking code in the Google Analytics Settings.

Just copy the tracking code over from your Google Analytics account and enter it by clicking Select Settings VariableNew Variable… and then pasting it into the Tracking ID field. Hit Save and then choose the History Change trigger as the trigger for your tag. To make sure that your new tag fires on all pages, you should also add the All Pages trigger to your tag. 

Screenshot showing the settings of the new History Listener tag.

Save your tag and refresh the preview console and click a few links on your web SPA. You should now see a new tag being fired every time there is a new page view or browser history event. This means that each time a tag is fired, the corresponding information will be sent over to Google Analytics. 

You can check this by looking at the Google Analytics Debugger. Click again on a few links of your web site and see if you can find hit Type page view and a URL under location when looking at the browser console.

Screenshot showing hitType pageview and a URL location in the browser console.

Alternatively, you can also check the GA Real-time reporting to see whether the information is passed on from GTM to Google Analytics. And presto, we’re now properly tracking all page views on our SPA website. Of course, don’t forget to submit the GTM changes you’ve made, so they get published to your website. 

Custom Data Layer 

As you saw, the history trigger makes it relatively easy to track Single Page Applications – but your web SPA needs to make use of the browser history API for this to work. What can you do if this isn’t the case? Don’t worry, there’s a solution for those cases as well – using the custom data layer push.

What is the custom data layer push? As you may know, GTM has a data layer, which is GTMs central repository of structured data. You can push data into this data layer with the help of some JavaScript. And this is what we will do for this second way of how to track Single Page Applications website traffic. 

For the custom data layer push, you will need the help of a developer, unless you have coding skills yourself. Let’s assume you have access to a developer who can help you with this task.    

What you want to add is a JavaScript code that looks like this

CODE:

dataLayer.push(❴’event’: “pageview”❵)

This snippet should be deployed with each new view visible to the user (what we would consider a pageview normally). Ideally, the developer should add more data, such as the hostname, the path, the URL (highly recommended), the query path and the page title (optional). 

We’ll assume that the developer has now correctly implemented this JavaScript on your web SPA and move on with our tracking in GTM. In the preview pane, when you click on one of the pageview events in the Summary tab and then go to Data Layer,  you should be able to see all the information that was implemented via the newJavaScript snippet that the developer inserted for you. 

 Screenshot of the GTM preview window showing the implemented Custom Data Layer.

If this all works as intended, you can now use this Custom Data Layer to fire your tags. To do this, head over to GTM and Triggers New, and give it a name, such as “Custom – pageview”. The Trigger Type we choose is Custom Event, and the Event name to fire the tag is “pageview”. That’s it, you can save your new custom trigger and connect it to a tag.

Screenshot of GTM showing the settings for the custom pageview trigger.

Go to Tags New, name it “GA – Pageview – pageview”, choose Google Analytics as the Tag Type and Page View as the Track Type, select your GA tracking ID and as a trigger select the new custom pageview trigger we just created. 

Screenshot of GTM showing the settings for the GA - Pageview - pageview tag.

Do you need the All Pages Trigger here as well, as in our history listener tracking? Well, this depends on how your web SPA is built. If you notice that there’s an explicit page view event whenever you click through your web site, that means it’s not needed to add All Pages as an additional trigger.     

As before, save your new tag and refresh your page. Click on some of the links and make sure the new tag is firing correctly each time you go to another page of your SPA website. If this all works as intended and you’ve verified the tracking in GA, go back to GTM and submit your changes.   

Common Issues with Single Page Applications Tracking 

While this process of implementing the tracking for web SPAs was fairly straightforward, there are some issues you can run into when you are trying to track your traffic. 

URL issues

One of these issues is URL problems. URL problems can occur when the URL structure of your website is using anchor links (the hash symbol which is used to jump to specific parts of a page on traditional websites). Google Analytics will not track the part of the URL that follows the hash symbol – which, depending on your URL structure could mean that all tracked URLs look the same, even though users are effectively visiting different pages. Unfortunately, there are quite a few SPA websites that are using anchor links for their page URLs, so this is a common issue.

Assuming we have such a website and GA by default ignores any part of a URL that is following a hash sign, how can we still properly track the web SPA?

This is where the history auto event variables come in, which we added in the history trigger chapter, but didn’t use. The auto variables Old History Fragment and New History Fragment will help us here (of course, the developer needs to have chosen to populate the browser history API with this information). Old History Fragment will show us the previous page that was visited and the New History Fragment will show the one we are visiting now. 

To add the missing URL information, we edit our existing “GA – Pageview – pageview” tag. First, activate the “Enable overriding settings in this tag” checkbox, click on More SettingsFields to Set, + Add Field with the field name “page” and as a value we choose the “New History Fragment” variable. This variable value will be added to the data that gets sent by your tag over to GA and will then be recognized as part of the URL that is being visited. 

Screenshot of GTM showing the settings for adding the URL page path information to the existing page view tag.

Save your changes and go back to Google Analytics, which should now show the full URL path of your SPA website and not only the part until the anchor link.      

If the developer of your site chose not to use the history trigger, you can revert to the Custom data layer solution. That’s why it was important to not just add the pageview event but some more information, such as the hostname, path and URL of the pages on your website.

If the developer has added the path as part of the Custom Data layer to your website, you can simply create a new Variable in GTM by going to Variables New, naming the new variable (we choose “div – page”) , selecting “Data Layer Variable” as the Variable Type, “path” as the DataLayer Variable Name and select this newly created Variable in your “GA – Pageview – pageview” tag in the “Fields to Set” section as the new value instead of New History Fragment.

Screenshot of GTM, showing the settings for the new div - page variable.
Screenshot of GTM, showing how to replace the New History Fragment value with the div - page value.

As you can see, when you are facing URL issues with your SPA, there are ways to transfer the missing parts of a URL over to Google Analytics, but the options you have depend on the setup of your web SPA. In case you are working with Custom Data Layers, make sure that your developer implements information such as URL path right away – it could save yourself valuable time afterwards 

Rogue Referral Problem

Another issue that can arise when you track Single Page Applications is the so-called Rogue Referral Problem. This issue, and how to fix it, was first reported by Simo Ahava. It describes a wrong attribution of Google Ads traffic as organic traffic in Google Analytics. Detecting this issue isn’t easy. To do this, you need to go to the User Explorer of GA and look for users that have Google CPC as the original referral channel on the first page of their visit, but were attributed as organic traffic with their next and all following actions, resulting in their entire session being rewritten as organic.

What happens is that Google Analytics strips some of the original information once the user hits your web SPA. During the following page views, GA is looking at the user’s previous pages and “forgets” that they originally came from one of your ads, wrongly attributing the user session as organic.

How can you tackle this problem? The recommended fix by Simo Ahava is to insert the following code in the page HTML before the GTM container:  

CODE:

  window.dataLayer = window.dataLayer || [];
  window.dataLayer.push({
  originalLocation: document.location.protocol + '//' +
                    document.location.hostname +
                    document.location.pathname +
                    document.location.search
});

However, in case you don’t want or you can’t edit your HTML directly, you can also add this code via a custom HTML tag. Copy the code above, head over to GTM and go to Tags →  New, name the new tag and choose custom HTML as Tag Type and paste the code into the HTML field, enclosed by <script> tags. 

Screenshot of GTM, showing how to set up the Custom HTML tag.

This will push the URL with all parameters into the data layer. It will persist there as long as the page or GTM don’t reload. Therefore the information about the original user source cannot get lost. 

This tag then needs to be fired once per page and on all pages of our site, but it has to be fired before any other tag gets fired. We can establish this by going back to our “GA – Pageview – pageview” tag, scrolling to the Advanced Settings and choosing Tag SequencingFire a tag before GA – Pageview – pageview fires, selecting our custom HTML tag and hit save. 

Screenshot of GTM, showing Tag Sequencing setup for the GA pageview tag.

To make sure this worked, open the GTM preview pane and select the Data Layer tab. You should see a new Layer called {originalLocation:} that delivers the URL of the page as its value. This new Data Layer can now be pulled into a new variable. 

In GTM, go to Variables New, name it “div – originalLocation” and select DataLayer Variable as Variable Type and “originalLocation” as the Data Layer Variable Name. Save and check it in the GTM preview. You should now see the new “div – original Location” tag being fired in the Variables tab, with the URL of the original location of the user as the value. 

Screenshot of the GTM preview window, showing the div - originalLocation tag.

However, there is one caveat to the solution of inserting the custom HTML via GTM and not directly into the HTML: It is possible that the originalLocation variable is pushed into the dataLayer at the same time as the tags are trying to retrieve it. This results in a missing Document location fied!  To prevent this from happening, copy the following custom JavaScript code.

CODE:

function() {
  return {{Data Layer Variable - originalLocation}} || window.location.protocol + '//' + window.location.hostname + window.location.pathname + window.location.search;
}

Go to GTM  Variables New, name it, choose Custom JavaScript as the VariableType and paste the above code into the Custom JavaScript field. Make sure that you change the reference from “Data Layer Variable – originalLocation” to the name of your Custom HTML Variable (“div – originalLocation” in our case). 

Changing the reference in the custom JavaScript snippet.

Double check in the GTM preview pane under Variables that the customJavaScript is showing up. Next, we need to add this location to our “GA – Pageview – pageviews” tag, so edit this tag by adding a Field to Set with the Field Name “location” and the value of our newly created “cjs – originalLocation” variable. Save your edits and try if it works on your SPA. 

Screenshot of GTM, showing how to add a location field to the GA pageview tag.

You can simulate a visit from Google ads to your site by adding “?gclid=” to the browser address, followed by a random number. Confirm that both the custom HTML and  the GA – Pageview – pageview tags get fired. If everything is set correctly, the session attribution of a user won’t change, even when the user is navigating on the page, since the location variable won’t drop the “gclid=” suffix.

Keep in mind: this is only an issue when you are running Google Ads. If you don’t, you can safely ignore this rather complex fix for the rogue referral issue.      

Summary

When you start to track Single Page Applications correctly, it may seem difficult. However, there are several ways to accomplish this with the help of GTM, either via the History Trigger, which is fairly straightforward, or, if this isn’t possible, by adding Custom Layer Variables to the site with the help of a developer. Users should take a closer look at their analytics to make sure they aren’t experiencing any URL issues, which can be caused by anchor links. Anchor links are a common way to mimic the standard page navigation in the browser address field and can lead to page path information missing in Google Analytics. Fortunately, the stripped URL path information can be recovered by applying the New History Fragment Variables or by making use of the custom data layer.

Another potential issue when you track Single Page Applications is the Rogue Referrer problem, which can lead to wrongly attributed Google Ad traffic. Fixing this issue requires a bit of tweaking in GTM by adding additional custom HTML and custom JavaScript variables, but with the instructions in this blog, you should be able to master this as well. 

🚨 Note: Another useful thing to track is the conversion value which helps you measure the financial impact of your marketing campaigns.

Keep in mind that the rouge Referrer problem can only occur with Google Ads traffic, so you can ignore this issue if you aren’t running any Google Ad campaigns. In case you are already using Google Ads, you may wonder how to properly connect Google Ads to Google analytics.  Or perhaps you’re already using Google Ads and Google Analytics, but you don’t know which of the programs you should use to track your conversions? Then our video about both conversion tracking methods will help you. 

JOIN US!

Master Data & Analytics with Measuremasters

Exclusive Courses & Workshops | Ongoing Troubleshooting | Support Resources, Tools & much more
Subscribe
Notify of
guest
24 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Amit
Amit
3 years ago

Hi Julian,
I have a question, how we can use GTM “timer” functionality using “history” event.
Thank you

LA
LA
2 years ago

Hi, Julian! I have the same situation, but there is a some problem. When i set custom timer 10 seconds on “history” event, it fires even when user is on the page less than 10 seconds and goes further, to next steps / pages. Here I need to have a normal story with default GTM timer but i can’t do it. The page doesn’t reload. Is there a decision how can I resolve that?

neha sharma
neha sharma
2 years ago

hi Julian, thanks for GTM knowledge regarding SPA. I am trying to track downloads in my SPA application with google tag. Any pointers how to do it?

Travis
Travis
2 years ago

Hey, I’m having a similar issue I believe. When AJAX-loaded content is added to the page (like a download link) clicking on that link does not trigger a linkClick event in GTM. It looks like GTM doesn’t know about the newly added download link element since it wasn’t there on page load. Should it trigger a an event or am I doing something else wrong? Thank you!

Marlie
Marlie
2 years ago

Hi Julian! I needed to create a few JavaScript Variables to collect data from the site code. The JavaScript Variables are not “refreshing” as I continue through the SAP – any ideas?

Anna Lee
Anna Lee
2 years ago

Hi Julian, Thank you so much for your awesome guide. I was looking for how to solve the Rogue Referral Problem for couple of weeks but couldn’t find anywhere give me step by step instructions like this. The implementation is working so far but I got stuck at this step “Make sure that you change the reference from “Data Layer Variable – originalLocation” to the name of your Custom HTML Variable (“div – originalLocation” in our case). “ I change the “Data Layer Variable – originalLocation” to “div – originalLocation”However, GTM kept saying that div – originalLocation is found in anotehr variable.… Read more »

Daniela
Daniela
2 years ago

Hi, I have a question about tracking SPA with the new GTM Preview mode. It doesn’t collect information across pages. Do you know if there is a workaround? It’s much more difficult to track things now. Thank you!

Harshad
Harshad
2 years ago

Julian, do you think rough url problem can affect google ads conversions too ( not the ones imported from analyttics) ? According to me it should not, if we are using “first lick attribution model”.As even though gclid is not passed so second click, ideally it should consider 1st and second click as same user and attribute the conversion to first click”

J
J
2 years ago

Hi Julian, first of all, thank you for the information. It really helps! I am running into an issue after I follow the workaround and trying to get the Rogue Referral Problem solved. Somehow the setup only works on the first page jump. When I monitor the traffic source in real time, the medium switch back to Organic after the second page visit. Landing – CPC First Page – CPC Second Page – Organic I checked the setup multiple time and can’t spot anything different than yours. I wonder if you can think something that’s related in your past experience.… Read more »

Riki
Riki
1 year ago

Hi Julian,
Thanks for your awesome guide, When setup GTM according to guideline I have 2 questions

  1. The cHTML – Original Location tag doesn’t trigger, right?
  2. And the dl (location) value should match the URL in the link, right? Why am I asking this question, my friend has set up GTM to solve the problem of rogue referrer problem when user goes from abc.com/ad_list to abc.com/ad_view/dien-phone-iphone but dl on console still shows abc.com/ad_list instead of abc. com/ad_view/dien-phone-iphone. Can you tell me which value is correct?

Thank you

Armen
Armen
1 year ago

Hi. I have a question about this part: “If everything is set correctly, the session attribution of a user won’t change, even when the user is navigating on the page, since the location variable won’t drop the “gclid=” suffix.

I did everything as described. But I didn’t understand if attribution works correctly. When I test with “gclid =, the datalayer shows everything correctly, but when I go to another page, gclid = is lost. Is this normal or not?

Beata
Beata
1 year ago

Hi Julian,

Very helpful guide, thank you! The issue we are experiencing is that when we navigate from one page to the next within a single page application the URL is captured correctly by Universal Analytics but the page title is actually the page I came from and not the page I landed on.

Can you please suggest how we might solve this problem?

Thank you,
Beata

nicolas
nicolas
1 year ago

Hi! i have a problem with UTM hat does not appear in the analitycs. lo hice asi:  store.domain.com/?utm_source=XXXk&utm_medium=XXX&utm_id=XXX#products the origial url is store.domain.com/#/products

could you help me?

Leonidas Lainis
Leonidas Lainis
1 year ago

Hi Julian,

Really nice article.

I noticed that the single-page-application tag doesn’t fire when I click on an internal link that has parameters (?utm_source=…). The links are internal, meaning that they have our webshop’s stucture before the parameters. Do you have any idea why this happens?

Evan Jose
Evan Jose
1 year ago

Can I use Google anylytics tracking code for a single page of any website?

Sofiia Bychkovska
Admin
1 year ago
Reply to  Evan Jose

Hey Evan, yes, it is possible, but it is recommended to have Analytics implemented on each page of a website.

Marius
Marius
9 months ago

Hi,
I would like to track some simple links on my SPA but when clicking then GTM does not recognize them as a click (click variable is enabled). Is there any best practice available?

Blog Categories

Want to find the Best Sources for Learning GTM? Subscribe & Get our FREE Resource Guide

Google Tag Manager FREE Resource Guide

now it's time to

Start measuring like a master

Itching to jump into the world of MeasureMasters? This is what you have to look forward to.