• Courses
    • Courses
    • Course bundles
  • Blog
  • Services
  • Resources
    • Youtube channel
    • E-books and Guides
    • GTM Recipes
    • View All Resources
    • GTM Community
    • GA4 community
  • About
    • About
    • Contact
  • Login
  • Courses
    • Courses
    • Course bundles
  • Blog
  • Services
  • Resources
    • Youtube channel
    • E-books and Guides
    • GTM Recipes
    • View All Resources
    • GTM Community
    • GA4 community
  • About
    • About
    • Contact
  • Login

track single page applications with universal analytics

Track Single-Page Applications with Universal Analytics and GTM

Note: In July 2023, Universal Analytics stops working. It is no longer recommended to do new implementations with Universal Analytics (GA3). Use Google Analytics 4 instead.

Important: This blog post contains Universal Analytics (GA3) examples. If you are looking for GA4 examples, click here

***

Updated: May 30th, 2020. Tracking page views on regular websites is fairly easy: you just add a tracking code to every page and done! Every time a visitor clicks an internal link, a browser window refreshes, and a new page view hit is sent to Google Analytics.

But when it comes to single-page websites (or web applications), the default tracking stops working. Regardless of what does the user/visitor does on your website, only one page view is tracked (the first one). Does this situation sound familiar?

Why does this happen? Let me show you.

In this blog post, I’ll explain how to track Single Page Web App with Google Tag Manager (including SP websites). The blog post is extensive so get ready.

 

Table of Contents

+ Show table of contents +

  • What is a Single Page App / Website?
  • Install Google Tag Manager
  • Multiple ways of tracking SPA + How to read this guide
  • Do you need this guide at all?
  • Option No.1 – History change trigger:
    • Does the page URL change (when you navigate from page A to B)?
    • Let’s find out whether History Change listener will help
    • Do you see History events in the GTM Preview and Debug mode?
    • When do you navigate from Page A to B, how many History events do you see?
    • Do any of History variables in GTM return useful data?
    • Track Single Page Application with Google Analytics tag
      • If URL change involved URL Fragment (#)
      • If URL change involved URL Fragment and the link might contain Query Parameters
      • If New History Fragment variable is empty and you’re using New History State
      • Trigger
  • Option No.2 – Track Single Page Web Apps with Google Tag Manager and developer’s help
    • Create variables
    • Create a trigger
    • Update the GA Settings Variable
  • Check the reports
  • Rogue referral issue
  • Final words

 

What is a Single Page App (SPA) / Website?

Single Page Application (SPA) is a web application or website that loads all of the resources required to navigate throughout the site on the first page load. As the user clicks links and interacts with the page, subsequent content is loaded dynamically (and the page technically does not reload, even though it might look differently).

The default Google Analytics tracking works well with traditional websites because the snippet code is run every single time users load a new page.

However, in a single-page website/app, all of the necessary code is loaded once and the page does not reload during the entire user session. That is why you see only one page view in your GA reports. The URLs might change but that is just to give the perception of separate pages.

A lot of web tracking tools won’t work as expected with SPAs because they’re written for traditional websites where the tracking codes can detect when a new page loads.

But everything is possible and you can track such pageviews with Tag Manager as well. Continue reading and I’ll show you how deep the rabbit hole goes.

 

Install Google Tag Manager

If you haven’t yet, install the Google Tag Manager to a single-page web app or website. I have published a blog post with several options for your consideration. Also, check whether your Google Tag Manager is working properly.

Done? Great, let’s proceed.

 

Multiple ways of tracking SPA + How to read this guide

Spoiler: there are two techniques explained in this guide, working with a History Change listener (within GTM) and asking a developer to push pageview data to the Data Layer every time a visitor navigates from one page to another. The first half of the blog post is dedicated to History Change (and finding out whether it will work for you) while the 2nd half will explain how to work with a developer.

But before we jump right into the configuration part, first we need to evaluate the context and see find out which tracking option to choose. Just like in my other popular blog post about form tracking, to make complex things a bit more understandable, I’ve created a flow chart that can help you decide which tracking method to choose (or, in many cases, there will be only one method possible for you).

If both options are possible for you, you should go with the developer’s help + dataLayer.push (as it was proven to be more robust). Continue reading.

First, let’s take a quick look at it, and then we’ll dive deeper into each step.

IMPORTANT: In October 2020, the preview mode in GTM has changed. Hence the very first step in the workflow (below) has changed a bit as well. Preview mode no longer disappears. Instead, start the flow (below) with the following question: “When you navigate from page A to page B, do you see the new page loaded in the GTM preview mode?

google tag manager single page app

 

Do you need this guide at all?

The first diamond in the flow chart above asks a question: When you navigate from one page to another (within the same website), does the GTM Preview and Debug mode quickly disappear and then reappear?

I know, this question might sound stupid, but just to be 100% sure I want you to think whether this guide is exactly what you need.

What does this question mean? By default, the Google Analytics pageview tag fires every time a page is loaded (meaning that the page must load completely from scratch). If you navigate from one page to another, page B is loaded from zero while page A is gone.

Together with the page’s code, the Tag Manager JavaScript snippet is also loaded from scratch. So if you navigate from page A to B, the GTM Preview and Debug console (if it’s enabled) disappears together with Page A (because the page is now abandoned), and then it is loaded from scratch on page B.

So if on every pageview you notice that the GTM Preview and Debug console reloads as well, you’re not working with the Single Page Application. Read my GTM guide for beginners, instead. I’ve explained there how to track your first GA pageview with Tag Manager.

On the other hand, if you’re really working with SPA, every time you navigate from page A to page B, the Preview and Debug console stays as it is and continues tracking your interactions (e.g. clicks), but not pageviews. Why?

That’s because in web tracking, “pageview” usually is when the page document is loaded and JavaScript tracking codes are loaded together with it (including Pageview tags). However, in SPA, all the subsequent “pageviews” happen dynamically, without loading/reloading the page document. That’s why by default, neither Tag Manager nor GA track such “dynamic” pageviews.

Because of this, you need to do some additional configuration (and maybe even with the help of a developer).

Alright, so the first question is answered. If you are indeed working with SPA, let’s move to the next question in our flow chart.

Does the page URL change (when you navigate from page A to B)?

If the URL does not change, you will have to ask the developer’s help. Immediately skip to chapter “Track SPA with GTM and developer’s help” of this blog post and continue reading from there. If the URL indeed changed, this means that you still have chances to use the History Change listener on your own.

URL changes might look differently based on a website you’re on. On some websites, the URL fragment changes (that’s the part of the URL after the hashmark #). On others, the URL change might look casually, like on a regular website (e.g. from /home/ to /home/contact-us), etc.

If you confirm that the URL changes, let’s move to another step.

 

Find out whether History Change listener will help

First, you need to have enabled at least one History Change Trigger on a page. Theoretically, it should be able to catch such URL changes. When the trigger is enabled on a page, it activates the History Change listener, a function (more like a set of functions) that is lurking somewhere in the background and waiting for a moment when the URL dynamically changes.

Once the listener spots the change, it pushes the History Event to the Data Layer (and you will see it in the Preview and Debug mode).

To enable a trigger, go to Triggers > New and enter the following options.

google tag manager single page app

Next, head over to Variables. Google Tag Manager offers a bunch of built-in variables related to the History Change Trigger. In the Variables section, click Configure (under the Built-in Variables) and enable all the History-related variables.

google tag manager single page app

Proceed to another question from the flow chart.

 

Do you see History events in the GTM Preview and Debug mode?

Now, let’s enable Google Tag Manager Preview and Debug mode and see whether the listener works. An orange notification bar should appear in your account.

Preview and Debug mode

Once the preview mode is enabled, navigate to the site where the container is implemented and you will see a debug console window at the bottom of your browser showing detailed information about your tags, including their firing status and what data is being processed.

Click (or scroll) through various sections of the single-page web app (or website) to make the Page URL (in other words, just browse your single-page app/website). After the change happens, take a closer look at the event stream in the Preview and Debug console.

Did the History event appear? If yes, that’s good news! If not, skip to the chapter where I explain how to cooperate with developers. You will have to cooperate with developers.

Every time a visitor navigates to a certain section of your app/website, History event will fire. This is a perfect trigger to fire the Universal Analytics Pageview Tag.

 

When you navigate from Page A to B, how many History events do you see?

The reason why I asked this question is that the answer isn’t always 1. You should expect 1, however, sometimes Single Pages Applications are coded in a way where the History listener catches two or more events. For example, I’ve seen some cases where a visitor navigates from /home/ to /pages/contact/.

As a result, two History Events happened:

  • One tracked the change from /home/ to /pages/ 
  • And the other one captured the change from /pages/ to /pages/contact/.

This means that with Tag Manager, you would have tracked two pageviews instead of one. Not accurate.

Another example was in a project where I saw the same History events appearing multiple times. Every “pageview” resulted in 2 or 3 History Events. Not accurate either.

In that case, if you use the History Change listener without any modifications to your History trigger, your data will be bloated and unrealistic.

You have three options here and two of them involve a developer.

  • Option A: Consult with a developer and ask whether it’s possible to fix the double or triple-history event issue by reducing it always to 1.
  • Option B: Once again, skip to the chapter where you have to implement this tracking together with a developer.
  • Option C: Try to filter out some of the History Events.

 

Do any of History Variables return useful data?

Since there are several ways how a URL can be dynamically changed (Simo has mentioned that) there are different ways how you can retrieve the history information about the page that was just loaded. Let’s take a look at two examples.

When the History event appears in the GTM Preview and Debug console, click it and go to the Variables tab. Scroll down until you see History-related variables (if you don’t, you need to enable them in the Tag Manager Container by going to Variables > Built-in Variables > Configure).

What do you see?

Does the New History Fragment contain some data? If the URL change involves the hashmark #, then this variable indeed should return some value. That’s great! We’ll later need to use that variable in the Google Analytics Settings Variable.

google tag manager single page app

If the URL (with #) changed but the New History Fragment variable returns undefined, try this solution.

If the URL change does not involve URL Fragment (#), then check another variable which is New History State. Does it contain some information? An example could look like this:

google tag manager single page app

While this is a good sign, we don’t need all of its value. We’ll just need one key, path. However, the built-in GTM variables don’t allow you to access that particular parameter, therefore, we’ll need to create a Data Layer Variable with the following settings:

We’ll need this variable in a minute.

However, if no History Variables return useful data about the page to which a visitor has navigated, consult with a developer or skip to this chapter.

Honestly, I haven’t seen a situation where no History Variable contained something useful, maybe it’s even impossible. I am not sure 🙂

 

#7. Use History Listener to Track Single Page Application with Google Analytics

Alright, so now we have arrived at the step where we will combine everything together and will send those pageviews to Google Analytics.

 

If URL change involved URL Fragment (#)

Normally, Universal tag fetches the value of the Full Page URL automatically and transfers it to Google Analytics servers. But if your URL changes involve the hashmark (#), Google Analytics will not catch it by default.

google tag manager single page app

We’ll need to do some additional configuration in the GA Settings Variable in order to send URL Fragment value over to Google Analytics (that’s why it was important to check whether History variables contained some useful data).

Open your GA Settings Variable (which is used by your Universal Analytics tags). Go to More Settings > Fields to Set > page and enter {{New History Fragment}}.

google tag manager single page app

 

 

If website’s/app’s address contains not only URL fragment but Page Path as well (e.g. https://www.example.com/category1/product2/#contact-us), the solution I’ve described above will only send contact-us to Google Analytics and category1/product2 will be ignored.

So if your single-page website/app address contains not only URL Fragment but also Page Path, you should update the Universal Analytics settings to this (either in all Google Analytics tags or in the GA Settings Variable):

  • Instead of page >>> {{New History Fragment}}
  • Enter page >>> {{Page Path}}{{New History Fragment}}

google tag manager single page app

In both ways, we tell Google Analytics to ignore the Full Page URL value it fetches by default and use the new value of page that we are sending.

 

If URL change involves URL Fragment (#) and also might contain Query Parameters

In some single-page applications, it’s possible that URLs might also include some query parameters (e.g. example.com/?q=product#search). If that’s the case for you, you could pass an additional variable with the page field to Google Analytics. That variable will return all the query parameters that are present in the URL.

Create a JavaScript Variable with the following settings window.location.search.

Then go to the Google Analytics Settings Variable and insert this variable before the Fragment Variable. Final result: {{Page Hostname}}{{js – window.location.search}}{{New History Fragment}}.

P.S. if the only possible query parameters in your SPA are UTM parameters, then don’t worry. There is no need to additionally send it in a page field. GA will take care of your traffic attribution in another way.

 

If New History Fragment variable is empty and you’re using New History State

As I have mentioned in one of the chapters before, it’s completely possible that the New History Fragment variable will have no value. If that’s your case, you need to dig deeper into what is present in the Data Layer. Here is an example of one History Event in the Preview and Debug mode. After I clicked the History event, I switched to the Data Layer tab.

google tag manager single page app

As you can see, gtm.oldUrlFragment and gtm.newUrlFragment are empty. But there is plenty of other useful information, for example, one of these two:

That’s why we should create a Data Layer Variable that accesses one of those values in the Data Layer. I have no preference here, so it’s up to you which one to choose. If you go with the gtm.newUrl, create the following Data Layer Variable.

If you decide to go with the submitUrl, you need to enter the entire path from the top level to the bottom. In this case, it would be gtm.newHistoryState.props.submitUrl (remember, it’s case-sensitive).

Use it in the page field (of your Google Analytics Settings Variable), instead of {{Page Path}} and {{New History Fragment}} (and query parameters if you entered them).

If you gave your DLV a different name, then that other name will be visible in the Value field of the screenshot above.

 

Trigger

If you have thoroughly followed this guide, you already have created a History Change trigger that listens to all events. If yes then keep it as it is. If you haven’t created it, do it now.

Assign the History trigger to your GA Pageview tag.

You might also need to assign the default All Pages trigger to the Google Analytics Pageview tag if the History event does NOT appear in the Preview and Debug mode when the initial page load happens.

What does this mean? In some projects, I’ve seen a History event occurring in the GTM debug console together with the initial page load.

First, you see Page view, then DOM Ready event, and then History. If that’s your case, you should be using only the History Change trigger in your page view tracking (and avoid the All Pages). Otherwise, the first page view would be tracked twice, 1 time because of the All Pages trigger and the 2nd time because of the History event.

Now, save the Universal Analytics tag, refresh Preview and Debug mode, and try interacting with various parts of a single page app/website. Every time you navigate somewhere, a Universal Analytics Pageview Tag should fire.

Don’t forget to check the data in GA Real-time reports.

 

The Alternative Option: Track Single Page Applications with Google Tag Manager and Developer’s help

Welcome to the 2nd large chapter of this guide. Most likely, you’ve reached this point because your History Change tracking attempts failed or you are simply curious to learn more.

If (for some reason) History Change trigger isn’t working for you (or there was another reason which brought you to this section), there’s another option on how to track single page web app with Google Tag Manager. Ask a developer to activate a dataLayer.push code whenever a user navigates between pages/states of a website/web application.

Here’s a sample code snippet that the developer could use:

<script>
 window.dataLayer = window.dataLayer || [];
 window.dataLayer.push({
 'event': 'Pageview',
 'pagePath': '/something/contact-us',
 'pageTitle': 'Contact us' //some arbitrary name for the page/state
 });
</script>

Note: The  ‘pagePath’ and ‘pageTitle’ parameters (in that code snippet) should be dynamically changed to the address (and title) of the page a visitor is currently viewing. A developer should take care of that.

Anyway, what does that code mean?

Every time a user goes to a particular section of your page, a developer should activate that little piece of JavaScript code. It indicates that a “Pageview” has occurred and the new page URL is /something/contact-us. If the page address contains some query parameters that are important to your reporting (excluding UTM parameters), ask a developer to include those parameters in the pagePath key as well.

Anyway, you should use this dataLayer.push as a triggering condition (that activates the GA Pageview tag) and then send the title and page path (page over to GA.

To achieve this, complete the following steps:

  • Create two variables (and include them in the GA Settings Variable)
  • Create a trigger (and assign it to the Universal Analytics Pageview tag).

 

Variables

  • Title: dlv – pagePath (dlv stands for “Data Layer Variable”)
  • Variable type: Data Layer Variable
  • Data Layer Variable Name: pagePath
    This variable will read the value of the pagePath that was pushed to the Data Layer by a developer.

Create a 2nd variable:

  • Title: dlv – pageTitle 
  • Variable type: Data Layer Variable
  • Data Layer Variable Name: pageTitle

 

Trigger

  • Title: Custom – Pageview
  • Type: Custom Event
  • Event name: Pageview
  • This Trigger fires on: All events

 

Update the GA Settings Variable and Universal Analytics tag

Go to the Google Analytics settings variable that you’re using in your SPA’s GTM container and include the pagePath and pageTitle variables.

  • Fields to set: page = {{dlv – pagePath}} and title = {{dlv – pageTitle}}

After that, go to the Universal Analytics Pageview tag and add the recently created trigger, Pageview.

 

Check the reports

Once you implement one of these options of single-page app/website tracking, it is crucial to check the GA reports. First of all, start with real-time reports.

On the left side of the GA interface, click Real-time > Overview. You should be seeing your own pageviews there. If you can’t read this troubleshooting guide.

Once you start seeing your own pageviews, you’ll need to wait for a while (from 10-20 minutes up to 24 hours) until you start seeing those pageviews in standard reports as well. By saying “Standard”, I mean Behavior > Site Content > All pages.

 

Rogue referral issue

Working with a single-page application and sending virtual page views to Google Analytics might mess your referral data up. Simo Ahava has posted a solution on how to fix the rogue referral issue. So after you follow my tips on how to track a single-page web app with Google Tag Manager, head over to Simo’s blog and implement his solution too. That solution is not an alternative to what you’ve learned here. It is a supplement.

This is especially important if you have paid traffic (e.g. from Google Ads) coming directly to the SPA.

 

Track Single Page Web App with Google Tag Manager: Conclusion

The problem with single-page web apps or websites is that regular pageview tracking does not work. All of the necessary code is loaded once and the page does not reload during the entire user session. This is why you see only one page view in your GA reports.

However, this is not a dead-end. With certain configurations in Google Tag Manager (and possibly some input from your developer), it is fully possible to track even them. In this blog post, I’ve explained two options on how to track single-page websites: use GTM’s built-in History Change trigger or cooperate with a developer.

If you’re not sure which method to choose, here’s a rule of thumb:

  • If you have development resources, choosing the path with a developer is a more robust option (in terms of tracking quality). But if those resources are unavailable right now, check the flow chart I’ve included at the beginning of this guide. However, there’s still a chance that, eventually, you’ll end up asking for the developer’s input. (if the flow chart says so).

By the way, if you found this post about tracking a single-page web app with Google Tag Manager useful, consider subscribing to my newsletter.  You’ll get various bonuses and useful stuff related to GTM.

Hi, I'm Julius Fedorovicius and I'm here to help you learn Google Tag Manager and Google Analytics. Join thousands of other digital marketers and digital analysts in this exciting journey.Read more
Analytics Mania
  • Google Tag Manager Courses
  • Google Tag Manager Recipes
  • Google Tag Manager Resources
  • Google Tag Manager Community
  • Login to courses
Follow Analytics Mania
  • Subscribe to newsletter
Recent Posts
  • Introduction to Google Tag Manager Server-side Tagging
  • 3 Ways to Pull Data from Data Layer with Google Tag Manager
  • Cannot See the Google Tag Manager Option in Google Ads Conversion?
Analytics Mania - Google Tag Manager and Google Analytics Blog | Privacy Policy
Manage Cookie Settings