Cardinal Path’s response to COVID-19 Cardinal Path is sharing all we know to help marketers during COVID-19.  Learn more.

Developing your site in a single-page application architecture – using frameworks like VUE, Angular, React, or Aurelia – makes your users’ experience easier and faster, but it is a little bit challenging from a tagging perspective.

What is a Single Page Application?

A Single Page Application (SPA) is essentially a Web page that initially or dynamically loads all of the HTML, CSS, and JavaScript resources required to provide an end-user experience that emulates a complete desktop application rather than a single static Web page. As the user clicks links and interacts with the page, subsequent content is loaded dynamically via an AJAX call. The application will often update the URL in the address bar to mirror traditional page navigation, but another full page request is never made. The Angular site itself is actually an example of an SPA.

How Does the Single Page Application Work?

New content from the Web server may delivered in JSON (JavaScript Object Notation) format, which the initial Web page processes dynamically. The application will often update the URL in the address bar to emulate traditional page navigation, but another full page request is never made.

From a Web analytics standpoint, this means that you have to track subsequent pageviews manually, since new pages do not load and the tracking for “physical” pageviews is not reactivated.

In an SPA architecture, the page is updated without a full reload of a new page.

The Challenge

Let’s reiterate the challenge: in traditional websites tracked into Google Analytics through Google Tag Manager, each page of content requires a round trip, so every time a page loads, a pageview gets sent to Google Analytics via GTM.

This does not happen in an SPA. In an SPA, a basic Google Analytics pageview tracker would record only the first pageview but not the subsequent screen refreshes.

Does The URL Change?

In SPAs, the following URL modifications may take place:

  • URL without hash
  • URL with a hash
  • no URL change

URLs without hash:

Some of other SPAs update the URL in the address bar without using the hash. For example:


URLs with hash:

Some SPAs only update the hash portion of the URL when loading content dynamically. This practice can lead to situations where many different page paths point to the same resource. In such cases, it’s usually best to choose the best URL represent your page and only ever send that page value as a virtual pageview to Google Analytics. For example, consider a SPA has a “Contact Us” page portion that can be reached via any of the following URLS:


Since the same portion of the SPA is accessible through multiple URLs, the challenge here is somewhat different from tracking URLs without hash. When tracking URLs without hash as above, we just need to make sure that we’re tracking each screen refresh.

When we’re tracking URLs with hash, we certainly still want to make sure to track each screen refresh, but we certainly should avoid sending different Page dimensions in virtual pageviews to represent the same screen. To avoid fragmenting the virtual pageviews in your GA reports for this example, it’s best to track all of these pages as /contact-us.html.

Storing the Screen Refreshes in the Browser History

In all three URL scenarios outlined above – hash, no hash, or no change – you can opt to write entries to the browser history that correspond to the screen changes.

To write to the browser history, you can take advantage of the window.history.pushState() method alongside the JavaScript code that you and your developers are customizing within the SPA framework to make the actual page updates.

For the no-hash examples above, we could push each of the pages to the browser history of follows:

var serializableObj = {slug: “slug”};
window.history.pushState(serializableObj, “User Input”, “/guide/user-input”);

The method requires three arguments: a serializable object, a page title, and – most importantly for our tracking discussion – a page path, as highlighted above.

We could write to the browser history for all of the contact screens with hash above as:

var serializableObj = { slug: “slug” };
window.history.pushState(serializableObj, “Contact Us”, “/contact-us.html”);

Note that changing the browser history in an SPA will have an even more critical benefit than tracking: because the different screens of your SPA are recorded in the browser history, your end users will be able to use the browser back and forward arrows to navigate through their own viewing history.

Apart from this fundamental usability enhancement, the updates that we make to the browser history will also provide an additional option for tracking the SPA screen updates as virtual pageviews.

Generating Virtual Pageviews for the Screen Refreshes

In the previous section, we reviewed how to update the browser history to correspond to the SPA screen refreshes. As we explained earlier, this in itself will not generate a regular, physical pageview because the page does not reload.

In many cases, the most suitable way to track SPA screen refreshes is through Google Analytics virtual pageviews. Virtual pageviews:

  • offer an alternative to event tracking for user actions that don’t generate a physical pageview but are still conceptually closer to pageviews than events
  • are generated with a regular GA pageview tag, but with the Page field overwritten as needed
  • are treated exactly the same as physical pageviews in terms of processing and reporting in GA

We can set up the virtual pageview tag in GTM. Apart from this, the main consideration will be:

Should I use browser history or the data layer for the trigger and variable(s) that the virtual pageview tag will need?

We outline both approaches below.

Virtual Pageviews from History Change

If you are writing to the browser history as outlined above, you’re generating the gtm.historyChange event in the GTM data layer, which can serve as the basis of your trigger as you take the following approach to generate a virtual pageview for each screen refresh.

URLs without hash:

These steps apply when the URL path is being rewritten:

  1. Configure a History Change trigger as below.
  2. Apply the trigger to your existing Google Analytics pageview tag within GTM. (Since the page path is updating in the browser with the Page dimension that we want, we don’t have to overwrite the Page dimension in the Google Analytics pageview tag: we can allow the default pageview behavior of reading the page path plus any querystring parameters.)

URLs with hash:

In the case of URLs with hash, we can take advantage of the New History Fragment variable in GTM for both triggering the virtual pageview and populating the page dimension:

  1. Enable the New History Fragment built-in GTM variable:
  2. Configure a JavaScript variable to read the title that you have written to the browser history:
    Custom JavaScript Variable to get Page Title
  3. Configure a History Change trigger as below:
  4. Apply the trigger to the following tag, configured as a GA virtual pageview with both page and title fields overwritten:

Note that we’re explicitly adding the page fragment – i.e., the portion of the URL after a #, which GA would not record by default.

If you have multiple URLs with hashes pointing to the same screen, the data layer approach described below would probably be a more straightforward approach for avoiding multiple Page dimensions for the same SPA screen.

Virtual pageviews from the data layer

You can use this approach whether or not you’re also updating the browser history. Whenever the user navigates from screen to screen in the SPA, you can push a custom event to the GTM Data Layer and use two of GTM Data Layer variables to add the value of the page path and page title.

‘pagePath’:’/Contact Us’,
‘pageTitle’ : ‘Contact Us’

  1. In the GTM interface, create a trigger based on the “pageviewCustomEvent” custom event and create two Data Layer variables (pagePath, pageTitle). Use the trigger that you created to fire the pageview tag and the Data Layer variables to get the value of the page path and page title.
    Google Analytics Pageview Tag Setup
  2. Create a data layer variable to read pagePath.
    Page Path Data Layer Variable Setup
  3. Create a data layer variable to read Page Path.
    Page Title Data Layer Variable Setup
  4. Configure the Google Analytics virtual pageview with the variables and trigger outlined in the previous steps.

History Change Trigger:

SPAs use the HTML5 History API to modify a website’s URL without a full page reload. As the SPA uses the HTML5 History API to modify the website’s URL without, this change will trigger a GTM event called “gtm.historyChange”. You can listen to this event using the GTM history change trigger to send pageviews to GA. In the GTM interface, create a history change trigger and create a custom JS variable to get the page title. Use the trigger that you created to fire the pageview tag and the built-in Page Path variable to get the value of the page path and the custom JS variable you created to get the page title. Check the following screenshots.

History Change Trigger
Built-in Page Path Variable
Custom JavaScript Variable to get Page Title
Pageview Tag using Custom Event Trigger

Virtual Pageviews as Goal Funnel Steps

From reporting perspective, tracking pageviews properly will help you to track your conversions funnel easier. For example, you can create a goal to know the competed purchase rate for a specific funnel. The funnel might have the following URLs:
Cart: /mybag
Billing and Shipping: /myinfo
Payment: /payment
Review: /revieworder
Order Completed: /ordercompleted
And the Funnel Visualization might be as following:
Purchase Completed Funnel Visualization


Every SPA framework has its own way to navigate and to fire events. Use the method that you see it suitable for your case. Tell us about your Front-end Framework and we will be happy to help 🙂

Happy Tagging!