How to measure SPA web traffic with Google Tag Manager and Google Analytics

Ramón Saquete

Written by Ramón Saquete

It is becoming more and more common to find websites programmed with full stack frameworks such as Angular, React or Vue that facilitate the implementation of websites Single Page Application or SPA where, by default, tracking scripts only work when users land on the site from outside, but not during navigation. Here we are going to see different solutions to this problem.

On a website SPAUnlike a classic Multiple Page Application (MPA) website, the JavaScript code included in the pages is not executed each time the user visits a new URLThe user loads the web site for the first time in the browser.

Once the user is already inside the website, JavaScript is not executed again during navigation, unless there is something in the code that forces it. This is a problem for the JavaScript used by the tracking tools, since they are only executed on initial load.

Let’s see how to solve it in two possible ways:

  1. Configuring Google Tag Manager.
  2. Without using Google Tag Manager and adding additional JavaScript.

Whichever form is chosen, we will need to detect, directly or indirectly, the calls to the history.pushstate method , responsible for the URL change by JavaScript in a SPA.

Let’s see how to apply each solution to Google Analytics. In theory, what is explained below can be applied to other tracking scripts . However, depending on the specific case, it may be necessary to add something else.

Tracking a SPA using Google Tag Manager (GTM)

In the following example, we start from an already created Google Universal Analytics account and a GTM container that we will have installed on the website, according to Google’s instructions.

First, we need to create a trigger that is fired when a page change occurs in the SPA . This can occur in three situations:

  1. When the user clicks on a link that causes the content and URL to change with JavaScript.
  2. When the user moves in the history with the browser buttons.
  3. When the user clicks on a button that causes a change of position in the history by programming.

The trigger responsible for this is“Change in history” which we configure as follows:

Google Tag Manager SPA Activator
GTM SPA Activator

We have given the trigger the name “SPA tracker” and marked it to be triggered by all events in the history. We save it and then, if we have not already done so, we should include the Google Analytics identifier in a constant, to have it available when configuring any tag:

Google Tag Manager constant declaration
Declaration of constant in GTM

With each activation of a history event, an integrated variable of Google Tag Manager will be updated, which will tell us the path of the URL where we are and that we will find already configured as “Page Path”:

GTM integrated variables
GTM integrated variables

Now we create a variable of the type “Google Analytics Configuration” where we assign to the “page” field of Google Analytics, the integrated variable “Page Path”, and to the tracking identifier, the previously declared constant:

GTM configuration variable
GTM configuration variable by assigning the pre-existing “Page Path” variable to the “page” field.

We save the variable as “SPA Analytics Configuration” and we are ready to create the “Google Analytics” type tag, to which we will assign the configuration variable created in the previous step and the “SPA tracker” trigger:

Google Analytics SPA tracking tag in GTM
Google Analytics SPA tracking tag in GTM

We can see that we have also added the “All Pages” trigger, so that it is also triggered when entering a page. If we already had another Google Analytics tag configured with the same tracking ID and the “All Pages” trigger, we can either remove it or leave it and not add the “All Pages” trigger to the new tracking tag. In the latter case, we could have a different configuration for the MPA and SPA pages of the web, while if we do it as in the screenshot, the configuration will be unified.

Finally, we give the label a name. For example: “Tracking analytics SPA”, and clicking on the “Preview” button (top right in GTM), we will check inside the site if it works correctly from the GTM console. Here we will see that the tag is triggered with every “Page View” event due to the “All Pages” trigger and every time we click on a SPA link thanks to the “SPA tracker” trigger that we have created of the “Change in history” type:

Events triggered in the GTM console
In the GTM console we see that one “Page View” and two “History” events have been fired because we have entered the SPA and viewed two pages, so our tag has been executed three times.

If we wanted to pass more information to Google Analytics, we would have to add additional variables on data created in the “Data Layer”, where the programmer can add the necessary information. Interesting information may already exist within the “Data Layer” if we examine the object gtm.newHistoryState, where with each change of the history the object passed by the programmer to the history.pushstate method to know the state of the application and the visited page is saved.

Most of the time, in the gtm.newHistoryState we will only find an identifier of the visited page, but in others it may have interesting information.

Once the configuration is finished and tested, we submit the changes and publish the new version.

Tracking a SPA by adding additional JavaScript

Although GTM is a fantastic tool to make the installation of tracking scripts independent of the web code, sometimes it is better not to use it. For example, if we only plan to use Google Analytics, by directly installing the footprint of this application we are saving the load of a tag manager that is only going to be used to load a tag.

In addition, the analytics expert would not need to configure the tag manager. If the website also uses a CMS, it probably has a plugin to easily install Google Analytics without the help of a developer, so sometimes not using GTM is much easier.

Another problem is that to do some things, GTM requires basic programming knowledge, in addition to knowing how the tool itself works. Typically, those interested in learning about the tool are analytics experts who do not have programming skills and developers are usually not aware of the analytics experts’ tools.The analytics expert asks the programmer to install the code, sending the programmer a link to the documentation and perhaps copying some of the code provided by a configuration tool.

In the case where a plugin is used , if it is already prepared to track SPA pages, the problem will be solved. Otherwise, we should dispense with the plugin and ask a developer for help.

Next, we will see how a Google Analytics developer should install Google Analytics to work in a SPA, so this part requires knowledge of JavaScript.

Implementation without additional libraries

Basically, what should be done, is that with each call to the history.pushstate method, the Google Analytics code that generates a new page view should be executed . According to the Google Analytics documentation for SPAs, the following function would be correct:

   function registrarVisitaAPaginaActual(){
       if (typeof(ga)!=="undefined"){
          ga('set', 'page', window.location.pathname);
          ga('send', 'pageview');

Within the function“registerVisitAPaginaActual()” we check that the object “ga” that allows us to make the calls to Google Analytics has been loaded and, if it has been loaded, we tell it that the current page has been visited. We could have here the code to record the visit of any kind of tracking script.

The difficult thing now is to get this function called every time history.pushstate is called. Currently there is no native event to detect when a history.pushstate call occurs. There is the event
event, which is also interesting to take into account, but it is not triggered by calls to history.pushstate, but only by changes in the history caused by the user or by programming, with calls to history.back(), history.forward() or history.go(-1).

A possible solution would be to add the call to the function “registerVisitAPaginaActual()” in each place in our code where a history.pushstate appears. This can be tedious, time-consuming or even impossible if these calls are obfuscated in the core of a framework’s router.

What is recommended in these cases to make the implementation cleaner and more viable, is to apply a hack that overwrites the native method history.pushstate, as follows:

window.onpopstate = history.onpushstate = function(e) { 
           //aquí podríamos pasarle e.state a nuestra función 
           //si queremos registrar algún dato más del estado actual, 
           //como el tipo de página
            //nos guardamos el método nativo original
            var pushState = history.pushState;  
            //sobreescribimos el método nativo
            history.pushState = function(state) {  
                //llamamos al método nativo original que nos habíamos guardado
                pushState.apply(history, arguments);
                //el evento inventado onpushstate se ejecuta 
                //después de llamar al  método pushsate
                if (typeof history.onpushstate == "function") {  
                   history.onpushstate({state: state});  

Obviously we have to load the Google Analytics library with the correct identifier beforehand, so that the code registers the visits.

Implementation with Google’s autotrack library

Another way to implement this is to use the Google library
library, which implements the same hack with a bit more code, to include Google Analytics and track SPA visits.

When including this library, we only need to use the following code with the correct Google Analytics identifier:||function(){(ga.q=ga.q||[]).push(arguments)};ga.l=+new Date;
  ga('create', 'UA-XXXXX-Y', 'auto');
  ga('require', 'urlChangeTracker');


Due to the non-existence of the onpushstate event, the implementation of tracking codes in SPA’s becomes more complicated than necessary. The WhatWG should include this method in its specification and browsers should implement it.

In the meantime, all implementations of libraries and Google Tag Manager itself, are based on the hack explained in this post. This is not good because an update in some browser can make this hack obsolete at any time, although at the moment it has been working for many years and it does not seem that it will happen in the short term.

  •  | 
  • Published on
Ramón Saquete
Ramón Saquete
Web developer and technical SEO consultant at Human Level. Graduated in Computer Engineering and Technical Engineering in Computer Systems. He is also a Technician in Computer Applications Development and later obtained the Pedagogical Aptitude Certification. Expert in WPO and indexability.

What do you think? Leave a comment

Just in case, your email will not be shown ;)

Related Posts