• 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

January 1, 2022

Track Single Page App with Google Analytics 4 and Google Tag Manager

Updated: January 1st, 2022. 

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

*****

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), there are more nuances. Even though Google Analytics 4 now has a built-in tracking of such pageviews, it doesn’t always work out of the box. And some websites are just really inconvenient from the web tracking standpoint.

The result? Only the first page view is tracked. All the subsequent page views are not captured (until the visitor completely refreshes the page).

To mitigate this, you will need to do additional configuration to track such websites/web apps. But don’t worry, in this blog post, I will show you 3 methods of how to track single-page applications with Google Analytics 4 (and, in most cases, Google Tag Manager).

Why does this happen? Let me show you.

 

Table of Contents

+ Show table of contents + 

  • What is a Single Page App (SPA) / Website?
  • Install Google Tag Manager
  • Multiple ways of tracking SPA + How to read this guide
  • Do you need this guide at all?
  • Does the page URL change (when you navigate from page A to B)?
  • Method #1. Track pageviews with GA4 Enhanced Measurement
  • Method #2. Try the History Change trigger
    • How many History events do you see?
    • Sending data Google Analytics (thanks to the History Change trigger)
    • If URLs of your website contains #
    • If URLs of your website do not contain #
    • Trigger
    • Should the GA4 configuration tag automatically track the first page_view?
    • To sum up this chapter
    • Test the setup
  • Method #3: Track SPA with GTM and Developer’s help
    • Variables
    • Trigger
    • Create a GA4 event tag
    • Should you override parameters in the GA4 config tag?
    • Should the GA4 config tag auto-track the first page_view?
    • To sum up this chapter
  • Test the setup
  • Check the DebugView
  • There is no rogue referral issue in GA4
  • Conclusion

 

Video tutorial

If you prefer watching videos, here’s the tutorial from my Youtube channel. But if you want to get the most out of this topic, you should do both: watch the video and read the blog post.

 

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/buttons and interacts with the page, subsequent content is loaded dynamically (and the page technically does not reload, even though it might look different).

The default Universal 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 GA3 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.

When it comes to Google Analytics 4, Enhanced Measurement offers built-in pageview tracking in such situations. However, that does not work on all single-page websites, thus some additional GTM configuration might be needed.

If the term “single page application” does not ring the bell, maybe names like React, Angular, Vue.js, Gatsby.js are familiar? Websites built with these (or similar) JavaScript frameworks are single-page applications.

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

There are three techniques explained in this guide:

  • Built-in tracking of Google Analytics 4 (this is the quickest one)
  • working with a History Change listener (within GTM)
  • asking a developer to push pageview data to the Data Layer every time a visitor navigates from one page to another.

But before we jump right into the configuration part, first we need to evaluate the context and 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).

Note: If all 3 tracking options are possible for you, you should go with the developer’s help + dataLayer.push (as it’s proven to be the most robust).

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

 

Do you need this guide at all?

The first diamond in the flow chart above asks a question: When you navigate between pages, do you see new pages in the GTM preview mode?

What does this question mean? By default, Google Analytics tag fires every time a page is loaded (meaning that the page must load completely from scratch).

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, you will see a new page title here. Every time this appears, it means that the page was completely (re)loaded.

So if on every pageview you notice that a page title on the left side of the sidebar, you are not working with a 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 an SPA, every time you navigate from page A to page B, you won’t see the page title. 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, Tag Manager does not track “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 an 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 or maybe the built-in GA4 functionality will take care of this automatically.

URL changes might look different 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.

 

Method #1. Track pageviews with GA4 Enhanced Measurement

Note:  Enhanced measurement provides a bunch of event tracking capabilities, but many of them can work inaccurately (e.g. scroll tracking), thus it will make sense if you disable all the non-pageview related tracking (scroll, site search, click tracking, etc.).

Now, let’s see if the built-in page view tracking in GA4 works for you.

In Google Analytics 4, go to Admin > Data Streams and click on your web data stream.

Then click the gear icon next to Enhanced Measurement (also, make sure that Enhanced Measurement is enabled).

Then click Show advanced settings under Page views and check if the Page changes based on browser history events checkbox is enabled. This will allow GA4 to automatically try every time the URL changes (or at least try to do so).

After that, save all the changes in GA4. In Google Tag Manager enable the Preview and Debug mode and then navigate to your single page application/website. Try navigating from one page to another.

In the preview mode of GTM, you should start seeing History Change events and their technical event name is gtm.historyChange-v2. You will be able to see it while having the GTM container selected at the top of the preview mode.

If you indeed see that, then click on your GA4 measurement ID at the top of the preview mode and check if you see multiple Page View events being sent to Google Analytics 4. If yes, then it’s good news. GA4 will be able to track pageviews of your single page application automatically and you don’t need any additional configuration.

Also, check the DebugView of GA4 to make sure that page_view events are coming with proper page_title and page_location parameters.

If you cannot see the History Change events (or the page_view events are not being sent to GA4), then go to the next chapter of this blog post.

 

Method #2. Try the History Change trigger

The most robust solution of tracking single-page applications is cooperation with a developer. No doubt. However, sometimes the developers are not available at all, so we might have to work with what we have. That’s why this method is also available as an alternative.

In Google Tag Manager, go to Triggers > New > History Change and create a trigger with the following settings:

Then we need to test if it works. Enable Google Tag Manager Debug mode. Click Preview button in the top right corner of your GTM interface (near Submit button).

If you ever stumble upon older GTM tutorials (published before the October 16th, 2020), they will tell you that an orange banner must appear in the GTM interface (when the preview mode is enabled). That is no longer true. From October 16th, 2020, the orange banner is gone.

Once you click the Preview button, a new browser tab will open with tagassistant.google.com. If it does not, read this guide.

A popup there will ask you to enter the URL which you want to test and debug. It might be the address of a homepage or it might be a specific page’s URL and then press Start.

A new browser tab (or window) should appear where you will see the URL that you entered in the previous popup. At the bottom of that page/tab, you must see the following badge:

Click (or scroll) through various sections of the single page web app (or website) to make the URL change. 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. 

On some single page applications, you might see several History events occur at the same time. That’s why you need to answer one more quesiton.

 

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

Some websites may be coded in a particular way that will cause multiple history events to appear at the same time in the GTM preview mode. If you face this situation, read this guide where I explain how to configure your trigger to work only with certain History Change events (and thus avoid duplicates).

 

Sending data to Google Analytics (thanks to History Change trigger)

After you made sure that History Change trigger works fine, you will have to send those pageviews to Google Analytics.

Google Analytics 4 is an event-based analytics platform. It means that everything is an event. Pageview, click, purchase, etc.

Since we want to send the pageviews every time a History event occurs, we will do that with a GA4 event tag. But its configuration depends on the URL structure of your website.

Important sidenote. On some websites, you might notice that your single page app’s pageviews are tracked properly BUT in the reports, you might notice that the title of the tracked page always belongs to the previous page. If you notice this, you will need to implement single-page app tracking with developer’s help or you can delay the History Change event.

 

If URLs of your website contains #

This part of the blog post applies to you if your website’s URL contains #, e.g. www.mywebsite.com/#/something.

Go to Tags > New > GA4 event tag and enter the following configuration. I presume that you already know what a GA4 configuration tag is, thus I won’t do an introduction here.

By itself, Google Analytics 4 (and its predecessors) are not capable of automatically tracking the URL fragments (that’s the part of the URL that goes after #). That is why we need to override the page_location. Important: you will also need to do that in all other future GA4 event tags.

What kind of variable did I use here?

One of the ways how you can pass the full URL (including fragment) to GA4 is by creating a JavaScript variable called window.location.href.

Save the tag.

 

If URLs of your website do not contain #

In the previous chapter, we overrode the page_location parameter because the URL contains the hashmark (#). But if your website’s URL has no #, then you DON’T NEED to override page_location parameter. GA4 will grab the URL properly by itself.

 

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 GA4 event tag for page_view.

 

Should the GA4 configuration tag automatically track the first page_view?

If you open your GA4 configuration tag (not the event tag), you will see that there is a checkbox Send a page view event when this configuration loads. Should it be enabled or disabled?

The answer is …. can you guess it? … Correct! It depends.

You should check what happens after you load/reload the page of your single-page application.

Does History event appear in the preview mode every time you reload the page?

If you reload the page (without clicking anywhere else on your website) and you see this:

…then you should disable the checkbox in the GA4 configuration tag.

If History events don’t appear immediately after you refresh the page and they become visible only when you start navigating your website/web app, then you should keep the checkbox enabled.

 

To sum up this chapter

After you follow all the steps in this section of the blog post, you should have:

  • 1 GA4 configuration tag (that you should have created before even reading this blog post). This tag should fire on All Pages.
    • If every time a page is loaded, the History event occurs in the preview mode, you should disable the Send a page view event when this configuration loads checkbox.
  • GA4 event tag that sends the page_view event. Link this tag to the aforementioned GA4 configuration tag. This tag should use the History Change trigger.
    • If URL of your single page website/app contains the #, you should overwrite the page_location parameter.

 

Test the setup

After you configure and save everything, it’s time to check the setup. I briefly explain how to test your page_view tracking here.

 

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

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': 'virtualPageview',
 'pageUrl': 'https://www.mywebsite.com/something/?page#contact-us',
 'pageTitle': 'Contact us' //some arbitrary name for the page/state
 });
</script>

Note: Values of ‘pageUrl’ and ‘pageTitle’ parameters (in that code snippet) should be dynamically replaced with the URL (and title) of the page a visitor is currently viewing. A developer should take care of that. If the URL contains the # or question marks and some parameters, they should be included there too.

If your developer says that it’s too complicated to show the full URL, then ask for at least the page path. The rest of the URL you will need to build in GTM with variables like protocol, page hostname, etc.

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 “virtualPageview” has occurred and the new page URL is https://www.mywebsite.com/something/?page#contact-us. 

Then you will use this dataLayer.push as a triggering condition in GTM (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 GA4 event tag)
  • Create a trigger (and assign it to the GA4 Event tag).

 

Variables

  • Title: dlv – pageUrl (dlv stands for “Data Layer Variable”)
  • Variable type: Data Layer Variable
  • Data Layer Variable Name: pageUrl
    This variable will read the value of the pageUrl 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 – virtualPageview
  • Type: Custom Event
  • Event name: virtualPageview
  • This Trigger fires on: All Custom Events

 

Create a GA4 event tag

Go to Tags > New > GA4 event tag and enter the following configuration.

This time, I override two parameters (page_location and page_title). Their values are the Data Layer variables that I created in one of the previous chapters. Important: you will also need to do that in all other future GA4 event tags.

Set this tag to fire on a Custom Event trigger that you have also recently created.

 

Should you override page_location and page_title in the GA4 config tag?

Personally, I don’t see a point in that because on most single-page applications, GA4 config tag will fire sooner than the first virtualPageview dataLayer.push occurs, thus it will be undefined in most cases.

I mean, if you want, you can override them. I think you should be fine. But if you notice some negative impact because of this, let me know in the comments.

Honestly, we are all learning GA4 as we go, and maybe in the future, I will learn of a solid reason why it is necessary to override these parameters in the config tag as well.

 

Should the GA4 configuration tag automatically track the first page_view?

If you open your GA4 configuration tag (not the event tag), you will see that there is a checkbox Send a page view event when this configuration loads. Should it be enabled or disabled? It depends (just like it was in the History Change trigger’s chapter of this blog post).

You should check what happens after you load/reload the page of your single-page application. Does the virtualPageview event appear in the preview mode every time you reload the page?

If yes, then you should disable the checkbox in the GA4 configuration tag.

If virtualPageview events don’t appear immediately after you refresh the page and they become visible only when you start navigating your website/web app, then you should keep the checkbox enabled.

 

To sum up this chapter

After you follow all the steps in this section of the blog post, you should have:

  • 1 GA4 configuration tag (that you should have created before even reading this blog post). This tag should fire on All Pages.
    • If every time a page is loaded, the virtualPageview event occurs in the preview mode, you should disable the Send a page view event when this configuration loads checkbox.
  • GA4 event tag that sends the page_view event. Link this tag to the aforementioned GA4 configuration tag. This tag should use the virtualPageview custom event trigger.
    • If URL of your single page website/app contains the #, you should override the page_location parameter.

 

Test the setup

After you configure and save everything, it’s time to check the setup. I briefly explain how to test your page_view in the next chapter.

 

Check the DebugView

Save all the changes in your container and enable/refresh the preview mode by clicking the Preview button in your GTM container. Then go to the GA4 DebugView (you will find it at Configure > DebugView).

When you load the first page of your single-page app, you should see one page_view event in the DebugView.

Check whether page_location and page_title parameters are correct and belong to the current page you are looking at.

Then start navigating your website/web app and check if the number of page_views that you see in the debug_view is correct and whether their parameters are correct.

Within the next 24 hours, you should start seeing your pageview data in Reports > Engagement > Pages and Screens reports of GA4.

 

There is no rogue referral issue in GA4

If you have some experience with Universal Analytics and how to track Single Page applications with it, you probably have heard of a phenomenon called rogue referral.

This is the result of how Universal Analytics (GA3) treats sessions.

However, the definition of sessions in GA4 is different, therefore you DO NOT need to implement any additional fixes to mitigate rogue referral (because there is none in GA4).

 

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. GA4 might be able to track pageviews of single-page applications in some cases but it definitely does not cover all the situations.

With certain configuration in Google Tag Manager (and possibly some input from your developer), you can still track them. In this blog post, I’ve explained the built-in GA4 solution and two other 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, cooperate 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

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.

Did I miss something or do you have any questions? Let me know in the comments.

Julius Fedorovicius
In Google Tag Manager Tips
92 COMMENTS
pratama
  • Feb 19 2018
  • Reply

Hi, nice tips. thank you.

But i just wondering i i use the Plan B with declaring datalayer 'url' as page url do i also get page path, hostname, automatically or i should also declare them id datalayer?

have you tried that?
thanks

    Julius Fed
    • Feb 19 2018
    • Reply

    It depends you on how you declare that URL in the data layer. The best way to find out is to try. Give it a try and see what happens :)

Johan
  • Mar 5 2018
  • Reply

Hello Julius,

Great article!

If we only want to track the page paths and not work with fragments. Can we skip creating the new history fragment and skip this part?:

"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}}"

    Julius Fed
    • Mar 5 2018
    • Reply

    Yes, looks like you can skip that part.

harbo
  • Mar 20 2018
  • Reply

Hi Julius,

I have using this method for a year. and i have my ultimate problem.

my case is an SPA using react,

when "histroyChange" is triggered, variable on "Page Title" or "isJWPlayerFoundOnPage" is still have value from earlier page. basically, any variable which would have a correct value on window loaded on conventional website, will not updated yet on "historyChange".

so, i end up with correct page value but often have inconsistent on page title on google analytic.

do you have solution for that? thanks in advance.

    Julius Fed
    • Mar 21 2018
    • Reply

    Yes, talk with developers. Ask them to fire a dataLayer.push every time a state (page) changes. That .push should contain Page title and other useful information, e.g.:
    window.dataLayer.push({
    'event': 'Pageview',
    'pageTitle': 'This is a page title',
    'someUsefulInformation': '123abc'
    })

    After that, create Custom Trigger, Data Layer Variables and use them. History change trigger and variables will not be useful in this case. Using dataLayer.push is a very robust solution which increases the quality of your data.

yanfei
  • May 25 2018
  • Reply

Thank you for the great article.

I have a question, we have multiple subdomains on single domain, using 1 GAID. Only one subdomain is a single-page app. Is that a safer approach to create a new page view trigger to only fire on this SPA (subdomain) with your solution, and create a blocking trigger for current pageview tag not fire on this SPA (subdomain), or it really does not matter?

    Julius Fed
    • Jun 1 2018
    • Reply

    Hi, it depends on the structure of the single-page website + the structure of other sites, their settings, etc. Both approaches can work fine. I'd prefer using one pageview tag and set history change triggers to fire only on that landing page.

      Yanfei
      • Jun 4 2018
      • Reply

      Thank you very much Julius, that makes perfect sense! I am going to try with your suggestion !

Max
  • Jun 22 2018
  • Reply

Hi, and thanks for the great article!

But we have an issue: before login or registration we have SEF URL, like /category/page. After registration or login user goes in "SPA part of site" with #! urls. The problem is that on first page after login, analytics tracking fire twice:
1 - like pageview (because page reloaded after click on "register" button)
2 - like history change (because we have #!thankyou url fragment after successful registration)

So - i don't know how to get rid of this f*cking doubled data. Maybe you know something decision?

    Julius
    • Jun 22 2018
    • Reply

    Hi, One of the ways would be update your history change trigger to not fire when url fragment equals to "login" (or whatever the value is at that moment).

      Max
      • Jun 22 2018
      • Reply

      Ok, thanks, i will try it.

Dipen Patel
  • Jul 17 2018
  • Reply

Hello, I've gotten almost all of our SPA working with GTM, only problem I'm having is before when our site used to be a regular site, we were able to be deliberate about certain pages not even having any tracking scripts (For example, the login page) for obvious reasons. But in a SPA, once a GTM trigger has been fired and a JS script is already on the page, if a user goes to the login page, is there any way to remove any tracking script on that page without a full reload of the SPA?

    Cody Swartz
    • Jul 17 2018
    • Reply

    If you search the comments for "login" there's a similar situation above from Max with an answer to not fire when the url fragment = "login".

    There's also similar logic to fire off a custom HTML tag which you can then filter out whether or not you want to fire off the next event which will actually fire off the analytics or modify the data layer you're passing over. For example we test that the last URL isn't the same as the new one, you could also test that it's not the login page. If the conditions are met though then you can push an event into the data layer with whatever information you want (we push the lastUrl into the data layer) then we fire the analytics from that event.

      Dipen Patel
      • Aug 6 2018
      • Reply

      I understand when scripts won't get fired on certain SPA pages, but if a script is already on the DOM, any script can hook on to let's say a window event. Now if I go to that login page, that event would still be there even if the script isn't executed again, any solution?

    Julius Fedorovicius
    • Jul 19 2018
    • Reply

    GTM should not be considered as a tracking script if the container does not fire anything on that moment. Therefore, if a visitor goes to the login page, just set your tags not to fire there by adding a blocking trigger. Blocking trigger's rules:

    Event name: .* (enable Regex checkbox)
    Condition: New History Fragment contains /login/ (or something like that).

    Add this trigger as an exception to all the tags which you do not wish to fire on a login page.

Hector
  • Sep 14 2018
  • Reply

Hi Julius!

Thank's for the post. It was really useful. But i need your help. My website has the # between // like this www.page.com/#/page/us ... I've already did everything you said in the post in the tag fires everytime but i can't see the result in Analytics

Can you help me please?

Best Regards!

    Julius Fedorovicius
    • Sep 15 2018
    • Reply

    Hey, if the tag fires but you still cannot see pageviews in the GA Real-time reports, there are several general ways how to troubleshoot this and fix it https://www.analyticsmania.com/post/google-analytics-real-time-reports-not-working/.

Adam
  • Sep 18 2018
  • Reply

I have an SPA that's being used on a touchscreen at a congress and multiple users will be using it throughout any given day. I can easily track pageviews as stated above however how can I tell GA that a new session has started when another user comes to use the app? I don't refresh the browser at any point between users sessions however i can do this if needs be. My main question is: will GA/Tag Manager know intuitively that it's a single page app based on the pushing of custom pageview events (i'm using plan B) and register a new session if the page is refreshed?

    Julius Fedorovicius
    • Sep 19 2018
    • Reply

    Hey, in your case, you could use "sessionControl" field in the Google Analytics Pageview tag. Every time a user comes in, you'll need to refresh the page. On that refresh, you could pass the additional field "sessionControl":"start" (in the "Fields to set" field of your GA Pageview tag). The Pageview tag must fire only on All Pages, DOM ready or Window Loaded trigger (definitely not on history change or custom dataLayer.push).

    Here's more about the session control parameter https://developers.google.com/analytics/devguides/collection/analyticsjs/field-reference#sessionControl

Jean
  • Sep 29 2018
  • Reply

Hi Julius,
Thank you for your useful guide. In your answer to Liam on JANUARY 3, 2018 AT 6:38 PM, You asked him to see if the UA tag fired. (as he saw the history change fire but does not see the variable in Analytics)
In the Variables tab of google tag manager I see the New history fragment Data Layer Variable string: 'pricing'
but in the tags tab, it tells me that the UA tag does not fire. Is this normal or could this be the issue why I don't see the label "pricing" in analytics -> behaviour -> site content?
Thank you
Jean

    Julius Fedorovicius
    • Oct 6 2018
    • Reply

    If the tag does not fire although it's supposed to, then it's definitely not an expected behavior. If the tag did not fire, it looks like your trigger is misconfigured. In Preview and Debug Mode, click the history change event, then click the tag that was supposed to fire (but did not). You'll see the conditions of your trigger and which ones were met. This should give you more clues of what's wrong.

Keshan D
  • Dec 11 2018
  • Reply

Hi Julius,
Quick question. I have done set up as you suggested. And its working fine. But the problem I have is, in all pages of the SPA the history change fires twice. For example from the landing page, when I navigate to inbox it will send two page views. This happen across all pages. Any idea how I can overcome that.?

Thanks a million

Regards,
Keshan

    Julius Fedorovicius
    • Dec 11 2018
    • Reply

    Hey, in this case, I'd consult with a developer:
    - Either ask to take a look at why there are two URL changes at once and ask to fix it and make it only one.
    - Ask a developer to push every URL change to the data layer (just make sure that a developer does not send duplicate events). This guide might be useful https://www.bounteous.com/insights/2018/03/30/single-page-applications-google-analytics/

    Also, check if there aren't multiple history change triggers or multiple containers on a page with enabled history change triggers.

Paweł
  • Dec 31 2018
  • Reply

Hi,

If you have a website where the url does not change (only the content dynamically reloads) is it better to push virtual pageviews or track navigating through the app as events? The best practice?

Best Regards

    Julius Fedorovicius
    • Dec 31 2018
    • Reply

    Hey, the best practice then is to ask a developer to push the data layer events when the state of your app changes and also with that event he/she should push the "fake URL", for example "/dashboard/". Then you could fetch that fake URL with the Data Layer Variable and send it to GA together with the virtual pageview.

Klaudia
  • Jan 11 2019
  • Reply

Hey! Great article but I've a problem with configuration. I'm trying to add chat on every page but without cart page. I added variables with "path page does not contain cart" and it's still not working. It's good when I navigate by clicking on url path and loading again all app. How to configure GTM in dynamically routing path?

    Julius Fedorovicius
    • Jan 11 2019
    • Reply

    The issue with the SPA is that if a script fires it will be active as long as the page is not fully reloaded (with F5 or Refresh). So if a chat has already loaded in, say, product page, it will also be visible in the cart as well (because the page does not reload).

    You should consult with the developers/creators of your chat solution and ask if there is some sort of JavaScript API that allows showing/hiding a chat by firing a little snippet of code.

Catalina
  • Jan 17 2019
  • Reply

Hi Julius,

I actually have a website which is done on Wix and the GTM code is place according to their instructions, the GTM account ID was just introduced in the CMS.

Problem is that pageviews do not fire when you change the page from the menu. If I reload the page it works fine but if I press on anything from the menu, like if I press on blog, or about page, no pageview is triggered. The URL of the page changes as it should (no hash, the URL acually changes) and below the menu the content changes. But my guess is that when the content changes the page is not reloaded. None of the history change variable are showing anything and I just can't figure out what's going on.

Could you please help?

Thanks,
Catalina

    Catalina
    • Jan 17 2019
    • Reply

    Hi,

    I just found out a solution. My URL is changed without hash and even though the history variables are not showing anything the Page Path does change, which is great because that can be caught with a History Change trigger -> Fire on Some History Changes -> Page Path matches RegEx :* and this trigger can be added to the Pageview Tag. Just in case anyone needs this as well :).

      Julius Fedorovicius
      • Jan 17 2019
      • Reply

      There is a better way to track Wix pageviews. Read this

Bastien
  • Jan 28 2019
  • Reply

Hi Julius,

Thanks a lot for sharing such great content.

One of my client has a ReactJS app and want to track events and pageviews in it.

I read your post and it seems that I can do it without any specific library. But I saw on some other websites that with REactJS, it seems mandatory to use specific library like :
https://github.com/faouzioudouh/react-tracker
https://www.npmjs.com/package/react-google-tag-manager

Did you already implement some Google TAg MAnager and Google Analytics tracking on a ReactJS app ?

Do you think the implementation you provide can work on this environment ?

Thanks a lot

    Julius Fedorovicius
    • Jan 28 2019
    • Reply

    For React apps, it's better not to use my provided solution and go with either some custom libraries or just as a developer to code a solution. Every time a state changes in the React app, a developer should push that change to the Data Layer, e.g.
    window.dataLayer = window.dataLayer || [];
    window.dataLayer.push({
    'event': 'pageview',
    'pageUrl': '/something-something/',
    'pageTitle': 'Page Title'
    });

Franc
  • Apr 25 2019
  • Reply

HI Julius,

I have a situation as follows.

SPA url lesco.com/myproductrange/#/range;

If I reload the URL, it is being captured as just "/myproductrange/" (as there is no history change, just "Window Loaded"). Could you advise as to how it would be possible to solve it.

    Julius Fedorovicius
    • May 2 2019
    • Reply

    Hey. Yes, I have. One of the possible solutions could be to try create your own URL variable (where you set it to return only URL fragment). That could be used in your tracking.

    More on URL variable -> https://www.analyticsmania.com/post/url-variable-google-tag-manager/

Jeremy
  • May 31 2019
  • Reply

Julius - thanks you so much for the helpful post! I have a 3 step registration process (2 sign ups + 1 thank you page) and I was able to finally get virtual pageviews and goals to fire in my SPA!!

While these pageviews and goals are now firing, one thing unexpected was my GA account started showing pageviews from a subdomain it didn't before.

Did this get picked up because of the history change? If so, how would I exclude history change from firing on specific URL's or make sure history change is only firing on the 3 pages I want it to?

    Julius Fedorovicius
    • May 31 2019
    • Reply

    Yes, change the History Change trigger from "Fire on all History Change events" to "Some History Change events" and then enter the condition you need (e.g. Page URL contains xxx)

adrian
  • Jun 25 2019
  • Reply

Hi Julius,

Trought you guide i have been able to track a pretty complicated website. However, i cant measure the first page with interaction (imagine a new user jumps in the home, due there are not history changes, i cant get data until the visit other url). If i use other options, pageviews are duplicated.

¿There is any thing that could be done?

Thank you

    Julius Fedorovicius
    • Jun 25 2019
    • Reply

    Just use the "All Pages" trigger too.

GTM Enthusiast
  • Aug 15 2019
  • Reply

Hi Julius,
I have a question regarding single page web app. When it is necessary to contact developer regarding custom events, do we ask them to insert also other information such as name or text or anything else so I can catch those virtual pageviews in GTM.Or maybe you have some texts regarding single page web app beside your article.

All the best

    Julius Fedorovicius
    • Aug 15 2019
    • Reply

    Hey, it's up to you what you ask. You should definitely ask for a page URL and page title. If you think something else is also useful, go for it and ask.

Charana
  • Sep 18 2019
  • Reply

Hey. Great article. I was able to set this up and I can see the URL fragments in the real time view. How do I setup a conversion goal for each?

    Julius Fedorovicius
    • Sep 19 2019
    • Reply

    Hey. By using the destination goals https://support.google.com/analytics/answer/1116091?hl=en

Serena
  • Sep 19 2019
  • Reply

HI! Thanks for your article.
I have traced my SPA with the History Change trigger method, on GA I see my pageviews correctly but I have a problem with the sessions.
Let me explain.
My website is made up of:
- pages where there is an actual page loading
- the SPA part.
On GA the sessions of the pages with loading are correctly counted, but the page count of the SPA is zero.
Thank you.

    Julius Fedorovicius
    • Sep 20 2019
    • Reply

    Hey, you haven't provided a lot of information in this comment so it's hard to guess. There are many reasons for that, for example:
    - You are using the wrong GA ID for the SPA
    - You implemented the History Change wrong and your GA tag is still not firing.

    Start with the GA Real-time reports and see whether you see the SPA pageviews there.

Ethan
  • Sep 23 2019
  • Reply

Alright, so I never leave messages on websites, but damn, this article is so good and it also helped me set up GTM for my React website.
Thank you so much the high quality article !

    Julius Fedorovicius
    • Sep 23 2019
    • Reply

    Glad to help!

Sean G
  • Oct 24 2019
  • Reply

This walkthrough was so helpful! Thanks so much for providing this valuable resource.

I implemented this on our website where we're running infinite scroll in our blog. What I've noticed is that in our pages report, URLs are being display like so:

www.example.com/www.example.com/blog-post-name.

We are using a GA filter to put the hostname in the pages report. We haven't had the issue described above until modifying GA for infinite scroll.

Any advice you can share would be much appreciated!

Thank you,
Sean

    Julius Fedorovicius
    • Oct 24 2019
    • Reply

    If you already have a "show full url" filter, then don't pass the hostname with the "page" field in the Google Analytics Settings Variable.

Robert B
  • Jun 3 2020
  • Reply

Hi Julius, I'm struggling to get this working correctly for my site.
I enabled the history listener and they worked correctly in that step, and only fired once when url changes.

During the step "Do any History Variables return useful data?"
All of my history variables do not have useful data.

The page path variable is updating though..

Since this is the case -
Under my google analytics variable:
Can I use {{Page Path}} for the page field value? or do I have to use data from one of the history variables as described?

Thanks in advance for the amazing content.

    Julius Fedorovicius
    • Jun 3 2020
    • Reply

    Since page path is updated properly in your case, then you don't need to do any configurations on the GA settings variable level. Just fire that pageview tag on every history change and GA tag will do the rest. You can check that in your realtime reports.

    Robert B
    • Jun 3 2020
    • Reply

    Note - I set up my GA pageview trigger to take place on history changes and pageviews as described.

    When history changes I am getting a page view to trigger and under my google analytics settings on the tag I am seeing

    fields to set:[{fieldname: 'page', value: '/signup-preferences'}]

    So it seems to be working properly, but I want to make sure I'm not missing something! Thanks again

      Dan
      • Oct 12 2020
      • Reply

      Got a similar experience but my pageview trigger fires only on the first page on Container Load. Is it supposed to fire that early? If not how do you prevent that?

Robert B
  • Jun 3 2020
  • Reply

Thanks for the response Julius!
I was hoping to use these new history listeners to help create exceptions.

I have a popup that triggers when the dom loads on my homepage. It already has rules set so that it will not appear on pages other than /.
The issue - If a user loads the dom on the homepage and then navigates to another page - the popup will still trigger.

Any advice on how to not have the popup show?

    Julius Fedorovicius
    • Jun 4 2020
    • Reply

    Update your triggers. If a visitor navigates to another page (that is a virtual pageview), and you have ONLY DOM ready trigger, then your popup should not appear. So this means that you have more triggers added to that popup. Remove them.

Bruno Santos
  • Jun 9 2020
  • Reply

Brazilian greetings Julius! Your article helped a lot and I managed to solve it using history only. However, I'm trying to create a click event, but GA doesn't recognize the interactions.

I tried to set it up to take the history too, but without success. Do you have any tips on how I can collect my click events at my SPA?

    Julius
    • Jun 9 2020
    • Reply

    Click tracking should work. Most likely, your setup is somewhere incorrect.

Adrzej
  • Jul 1 2020
  • Reply

Hi, one question. When I have implemented dL for Enhanced Ecommerce, I must clean array every time once user change page? For example, Offer page >> lising - some dimensions may be present in the layer (from offer page on listing page) and be sent with event on listing.

What's best practise for dL and SPA?

    Julius
    • Jul 1 2020
    • Reply

    If some keys in the array/object may change, clearing is an option.

Alex
  • Jul 2 2020
  • Reply

Hello Julius,

Thank you for this useful article 🙏

You said: "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."

It's still unclear for me if I should do that if I need to pass values of custom query parameters to GA.

For example, a visitor comes to our website at the following URL: https://www.acme.com/?ref=E6GTJQLXT&utm_campaign=summer+sale&utm_medium=email&utm_source=newsletter, all query parameters are preserved in the URL while browsing the site, should I have something extra send to dataLayer if I want to pass 'ref' parameter value to Google Analytics custom dimension? And if so, why shouldn't I do the same for UTM parameters?

    Alex
    • Jul 2 2020
    • Reply

    I have to correct my words - query parameters are NOT preserved in the URL while browsing the site, this only happens with redirects.

    Julius Fedorovicius
    • Jul 3 2020
    • Reply

    If developer is pushing to the data layer all the virtual pageviews, you could ask him/her to send all query parameters as well.

    Instead of

    dataLayer.push({
    'event' : 'custom-pageview',
    'pageUrl' : '/page/',
    'pageTitle' : 'sometitle'
    });

    you should ask for this:

    dataLayer.push({
    'event' : 'custom-pageview',
    'pageUrl' : '/page/?ref=E6GTJQLXT&utm_campaign=summer+sale&utm_medium=email&utm_source=newsletter',
    'pageTitle' : 'sometitle'
    });

      Alex
      • Jul 3 2020
      • Reply

      Julius,

      Should the developer send a full URI or just an URN in pageUrl variable? In the article you indicated a full URI, but in the comment above it's just the URN. All I know is that when GA script sends a hit, the hit contains a full URI, for example, it may be &dl=https%3A%2F%2Fwww.acme.com%2F.

      What exactly should we use to pass to GA?

        Julius Fedorovicius
        • Jul 3 2020
        • Reply

        Actually, both should work. dl (document location) will be caught by GA automatically in order to take, for example, UTMs. But if you want to see some other query parameters in your reports, they could be included in the pageUrl data layer variable.

        alternatively, a developer can push the pageUrl without query parameters and you can pass them with the window.location.search javascript variable. Check the "If URL change involves URL Fragment (#) and also might contain Query Parameters" chapter for inspiration.

        To sum up: there are several ways to achieve the same things here. The result will be the same. Choose the most convenient for you.

        Julius Fedorovicius
        • Jul 3 2020
        • Reply

        And sorry if the previous comment was misleading/confusing.

Bram
  • Jul 8 2020
  • Reply

Thank you very much for the useful article.

The only thing i dont seem to get is, how to setup conversion tracking in GA.
People land on /app#/thankyou , so this means i can setup just hitting URL /app/thankyou in GA as a goal.
As GA sees landing on /app/thankyou as a pageview.

Or am i thinking to simple? Always seem the case with GTM haha.

Thanks again.

Best regards

    Julius
    • Jul 8 2020
    • Reply

    Yes, you are correct.

Raqi
  • Aug 12 2020
  • Reply

Thank you for the useful article.

I have one question about my issue in my website.

Since the pageview uses a custom event trigger, can I use the same trigger to fire my Google Ads conversion tracking tag? or is it require another trigger to fire my tag?

    Julius Fedorovicius
    • Aug 12 2020
    • Reply

    You can add multiple tags to the same trigger.

Charu Mishra
  • Feb 1 2021
  • Reply

If I'll fire custom pageview event with PageTitle parameter.
window.dataLayer.push({
'event': 'Pageview',
'pagePath': '/abc/contact-us',
'pageTitle': 'Contact us'
});

will it also update the value of page title in any other tool except GA.
Kindly confirm.

    Julius
    • Feb 1 2021
    • Reply

    No, unless you manually send it to those tools.

Joe Privett
  • Feb 14 2021
  • Reply

As part of new PCI compliance laws coming in, it will be necessary to exclude scripts on 'payment' pages. How can we exclude GTM specifically on payments pages within an SPA?

    Julius
    • Feb 14 2021
    • Reply

    You should discuss this with your developers.

Igor Stina
  • May 17 2021
  • Reply

Hi Julius, thanks for the information that you share on this website. It's really helpful and clear to understand.
I have a quick question for you. Is it even possible to track SPA websites traffic with GTM and Universal Analytics. Or I should switch to GA4 instead?
Thanks in advance,
Igor

    Julius
    • May 17 2021
    • Reply

    Hi, read the first sentence of the blog post. Highlighted in red :)

dijital pazarlama
  • May 27 2021
  • Reply

How is forr e-commerce event add to cart and purshase setup?

Kamil
  • May 31 2021
  • Reply

Huge dose of knowledge about Google Tag Manager!

Brad
  • Jun 22 2021
  • Reply

This page from google says not to update the page location, but rather send a set command and set page to a whatever url path you want. At least that’s how I read it. So in my interpretation, we should not send the url fragment but rather the meaningful part after it as “set”, then follow with a page view. Thoughts?

https://developers.google.com/analytics/devguides/collection/analyticsjs/single-page-applications

Ditte
  • Aug 18 2021
  • Reply

Hi Julius,

Thank you for this!

We did the custom implementation and observed that pageviews were suddenly sent twice (as demonstrated in the GA tab of the Tag Assistant window and in the GA4 debugger).

The fix was to update the GA4 tag configuration to only fire once per page. Maybe this is something specific to our site, but if it's not, it could be useful to include as a step in your guide :)

Also, when I preview our setup, I don't get a pageview on the first pageload. Instead I get a user engagement event, but with the correct page location and referrer. Would that be an issue?

Pawel
  • Oct 29 2021
  • Reply

Hi Julius,

#GA4
Do you know if it is possible to overide (to)long Page URL (I have one long parameter here) in order sent lighter hit? Something like with custom task in UA.

I created custom JS varaible without this long parameter and it looks good in GTM but in collect=v2 hit I still see this long url, although I replace page_location with this variable (fields to set in GA4 config).
And I see page view hit with this long url :(

Juan Rendon
  • Dec 1 2021
  • Reply

If i create a GA4 property, after this i can i connect with my old analytics property?

    Julius
    • Dec 1 2021
    • Reply

    There is no connection between ga3 and ga4. It does not matter whether you create both properties together or separately

Gus
  • Dec 23 2021
  • Reply

Hi, great help this article. I was wondering if it is possible to send custom parameters (and user properties) along the automatic page_view events using the enhanced measurement included in GA4? Or if I need to send custom parameters and user-properties with page_view, do I need to either use the history-change trigger or ask my dev?

Omar Dahroug
  • Feb 1 2022
  • Reply

Hi Julius, thanks for your blog. I'm currently building a next.js application and would like to load GTM only AFTER the entire page is loaded. The goal behind this is to improve the website performance. Have you figured out a way how I can run third-party JS code only after my application has loaded?

    Julius Fedorovicius
    • Feb 1 2022
    • Reply

    No,that is developer's responsibility to figure out

kapil
  • Feb 4 2022
  • Reply

Thanks for this. Can you please tell us how to track events in a single-page application using click text/class or Id? Because I am not able to see values for these parameters in GTM debug mode.

Sergey
  • Feb 4 2022
  • Reply

Great article, thanks.

Quick question. I have a website (test.com), which is a standard website where pages refresh every time a user clicks on a link, no problem tracking it. Then I have a single-page web app, (app.test.com).

Should I use different GTM containers for them if I wanna use one GA4 property to track both?

more info
  • Mar 28 2022
  • Reply

Even though I had some old school HTML and CSS expertise dating back to the
late 90s , I was way out of my depth.

Andria
  • Apr 6 2022
  • Reply

Hello and thank you for the great article.

We have a SaaS product which is also a SPA and each time a user requests a free trial of the product, we create an instance of the following format:
username.domain.com

So apart from our website that we are tracking as domain.com we want to also be able to track activity on each one of the SPA accounts created.. so *.domain.com

Is this possible and how can we do it?

    Julius Fedorovicius
    • Apr 13 2022
    • Reply

    GA automatically handles subdomains. Just make sure that you use the same GA property/data stream for every subdomain.

Matej
  • Apr 12 2022
  • Reply

Hi Julius, your blog is a game changer. I am would like to donate, please guide me.

I have also issue of my own regarding SPA tracking, help please :).
Web is stock car listing. Filter is Vue 3, and also financing calculator is Vue 3. User selects car and that he might enter calculator that has several steps.

I have implemented dataLayer variables as recommended in you guide, i have done the GMT part as well and debugging works just fine.

I have added GA4 event tag - virtual also as guide said to do. There is the important notice to do the same for other GA4 event tags. There i realized that if i do so, i wouldn't be able to measure web outside Vue 3. Vice versa.

Please, what do you recommend? Would adding Javascript to all other non-Vue pages work?

Thank U, Matej

Joris
  • Apr 21 2022
  • Reply

Thanks for the great article Julius!

When I simply add the GA4 configuration (via GTM, on all page views, with or without the first page view tracked) to our React app, it refreshes the app each time an internal link is clicked. This is not the behavior we want, as we want some elements to update dynamically, iso reloading everything.

Do you have any ideas what could be causing this, or how we could fix/debug it? We don't have any issues with the old GA or so.

Leave a comment Cancel reply

Your email address will not be published. Required fields are marked *

 

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
  • RSS feed
Recent Posts
  • Exclude URL Query Parameters in Google Analytics 4
  • Exit Pages in Google Analytics 4
  • How to Track Custom Events with Google Analytics 4
Analytics Mania - Google Tag Manager and Google Analytics Blog | Privacy Policy
Manage Cookie Settings