Tracking page views on regular websites is pretty 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 caught by the 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. Does this situation sound familiar?

Why does this happen?

In this blog post, I’ll explain how to track Single Page Web App with Google Tag Manager (Single Page Websites are also included!).

What is A Single Page App / website?

According to Google, a 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.

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

In 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 logical 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.

Actually, this is not a big deal when you employ Google Tag Manager. Continue reading, I’ll show you how deep the rabbit hole goes. Relax, it’s not that deep 🙂


Install Google Tag Manager

If you haven’t yet, install Google Tag Manager to a single page web app or website. I have published a blog post with several options for your consideration.

Done? OK, let’s proceed.


History Change Trigger

When you navigate the single page website or app, take a closer look at the browser address bar. Does the URL have a hash mark? For example, If no, don’t panic. I’ve seen websites which no hash mark, but they can be tracked with History Change trigger.

The string of characters that follows a hash mark is called URL Fragment. Luckily, Google Tag Manager can track when the hash changes and provides the perfect way to do it with the History Change Trigger. It activates when it captures an event dispatched by the URL fragment change.

So if your single page application or website use hash marks in the URL, you’ll find this blog post pretty useful.


Enable History Change Trigger

First, you need to enable History Change Trigger. This way you can check whether this type of trigger is suitable for you (and what data can you get). Go to Triggers > New and enter the following options.

History change trigger Google Tag Manager

Next, head over to Variables. Google Tag Manager offers a bunch of built-in variables related to the History Change Trigger. In Variables section, click Configure (under the Built-in Variables) and enable New History Fragment variable. I’ll explain this choice later.
Enable History Variables in Google Tag Manager



Now, let’s enable Google Tag Manager Preview and Debug mode. 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) various sections of the single page web app (or website) to make the hash mark change in the URL (in other words, just browse your single page app/website).

After the hash mark changes, take a closer look at the event stream in the Preview and Debug console.

Did the gtm.historyChange event appear? If yes, congratulations!

gtm.historyChange in Google Tag Manager

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

Let’s Check Our New Variable

Remember when I asked you to enable New History Fragment variable a few paragraphs ago? Let’s check what happens with it when a gtm.historyChange event occurs.

In Preview and Debug mode Event Stream, choose gtm.historyChange event and head over to Variables Tab. You should see something like this:

New History Fragment Variable in GTM Preview and Debug Mode

In the screenshot above, New History Fragment variable’s value is contact-us. This means that the address (visible in the browser address bar) is

New History Fragment stores the new URL fragment when it changes. If you need the know what was the previous URL Fragment, you can enable a built-in Old History Fragment variable as well.

P.S. If your single page website does not use a hash mark in the URL, try enabling all History Variables and see what data is available in the Data Layer (after the History Change trigger fires). I’ve seen some websites which were pushing valuable information accessible with New History State Change variable.



OK, let’s do a quick summary what we have so far:

  • We have created a History Change Trigger. Every time a URL Fragment changes, a gtm.historyChange trigger will fire, thus our Universal Analytics Tag can be dispatched.
  • Furthermore, we have created a New History Fragment variable. Every time a URL Fragment changes and a gtm.historyChange trigger fires, we know the exact value of the Fragment. This data can be passed to Google Analytics as Page URL.

Is there something we’re missing? Ah, yes… the Universal Analytics Tag itself.


Universal Analytics Tag

The last but not least, Universal Analytics tag. We’ll use it in order to pass the data to Google Analytics.

Normally, Universal tag fetches the value of the Full Page URL automatically and transfers it to Google Analytics servers. But unfortunately, URL Fragment is not a part of Full URL, thus Google Analytics does not catch it by default.

no hash mark in google analytics reports

We’ll need to do some additional configuration in the Universal Analytics tag in order to send URL Fragment value over to GA.

First, let’s go to Tags section in Google Tag Manager account. Click New and choose the following settings:

  • Tag Type: Universal Analytics
  • Track Type: Pageview
  • Choose Google Analytics Settings Variable. This time, I will not go into details, how it works. You can read more about it in my other blog post. This time, I’ll choose to override the GA settings variable.
  • Enter Tracking ID. The best practice here is to add it as a Constant Variable.
  • More Settings > Fields to Set > page >>> {{New History Fragment}}

GA Pageview Tag in Google Tag Manager

The difference between this type of configuration and the regular Universal Analytics Pageview Tag is the additional field we set, page. This way we tell Google Analytics to ignore the Full Page URL value it fetches by default and use {{New History Fragment}} instead.

So if website’s or web app’s address is, the Universal Analytics GTM tag will send contact-us to GA as page value.

new history fragment sent to google analytics


And finally, the History change trigger we have created in the beginning of this blog post. Assign it to this Universal Analytics Tag.

Save the 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.

That’s it! The technique we used in this blog post is called Virtual Pageview. Although the page was never reloaded, we imitated a Virtual Pageview with help of History Change Trigger.

Google Tag Manager Course - Learn More

Things to keep in mind

If website’s/app’s address contains not only URL fragment but Page Path as well (e.g., 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:

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

Page path and New History Fragment


Plan B

If (for some reason) History Change trigger isn’t working for you, there’s a Plan B how to track single page web app with Google Tag Manager. Ask a developer to fire a dataLayer.push event whenever a user navigates to any part (or section) of a website/web application.

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

Note: The  ‘url’ attribute (of that code snippet) should be dynamically changed to the address 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 fire that little piece of JavaScript code. It indicates that a “Virtual Pageview” occurred and the current page address is You should use it as a trigger to dispatch a Universal Analytics tag.

To achieve this, complete the following steps:

Create a variable:

  • Title: dlv – url (dlv stands for “Data Layer Variable”)
  • Variable type: Data Layer Variable
  • Data Layer Variable Name: url
    datalayer variable - url

Create a Trigger;

  • Title: Custom – Pageview
  • Type: Custom Event
  • Event name: Pageview
  • This Trigger Fires On: All events
    Custom Pageview Trigger

Update Universal Analytics Pageview tag (that I have previously described):

  • Fields to set: page = {{dlv – url}}
    fields to set - dlv url
  • Trigger: Custom – Pageview


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.

Fortunately, that’s a pretty easy issue to overcome. In order to track single page web app with Google Tag Manager, enable History Change Trigger and built-in History Fragment variables. Use that trigger to create a virtual page view by dispatching a Universal Analytics tag.

If History Change trigger isn’t working, there’s also another way to track a single page web app with Google Tag Manager. Ask a developer to fire dataLayer.push event every time a user interacts with the page. Use that trigger as a condition to fire a Universal Analytics tag.

By the way, if you found this post about tracking 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.

Julius Fedorovicius

Head of Developer Operations at Omnisend. I am highly interested in Google Tag Manager, Google Analytics, Adwords, Email marketing, Email Deliverability, Digital Marketing in general. You can follow me on Twitter or Linkedin.