
December 8, 2022
Track Single Page App with Google Analytics 4 and Google Tag Manager
Updated: December 8th, 2022.
Tracking page views on regular websites is fairly easy: you just add a tracking code to every page and done! Whenever a visitor clicks an internal link, a browser window refreshes, and a new page view event 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 pageview is tracked. All the subsequent pages 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 three methods to track single-page applications with Google Analytics 4 and Google Tag Manager).
Why does this happen? Let me show you.
Table of Contents
+ Show table of contents +
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 you the perception of separate pages.
Many 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.
Regarding 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 a bell, maybe names like React, Angular, Vue.js, and 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 on 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:
- Method #1. built-in tracking of Google Analytics 4 (this is the quickest one)
- Method #2. Working with a History Change listener (within GTM)
- Method #3. 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, we first 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 Method 3: 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, the 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 is 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 must ask the developer’s help. Immediately skip to the chapter “Track SPA with GTM and developer’s help” of this blog post and continue reading from there. If the URL indeed changed, 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 the 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 casual, 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 page views 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 to disable all the non-pageview-related tracking (scroll, site search, click tracking, etc.).
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 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).
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 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, look closely 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 simultaneously. That’s why you need to answer one more question.
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 simultaneously 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 the History Change trigger)
After you make sure that the 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 side note. 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 the developer’s help, or you can delay the History Change event.
If URLs of your website contain #
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 the page_location parameter. GA4 will grab the URL properly by itself.
Trigger
If you have thoroughly followed this guide, you have already 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 the 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 this chapter up
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 loadscheckbox.
- 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 the 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) the 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?
When it comes to single-page applications, you shouldn’t. GA4 config evaluates the parameters only once per page load. So if page_title or page_location changes (and on SPAs, that happens a lot), only the initial value will be sent to Google Analytics 4.
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 this chapter up
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 the 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 Admin > 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 situations.
With certain configurations 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, cooperating 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 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.

73 COMMENTS
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
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 :)
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}}"
Yes, looks like you can skip that part.
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.
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.
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?
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.
Thank you very much Julius, that makes perfect sense! I am going to try with your suggestion !
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?
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).
Ok, thanks, i will try it.
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?
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.
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?
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.
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?
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
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
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.
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
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.
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
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.
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?
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.
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
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'
});
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.
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/
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?
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)
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
Just use the "All Pages" trigger too.
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
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.
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?
Hey. By using the destination goals https://support.google.com/analytics/answer/1116091?hl=en
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.
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.
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 !
Glad to help!
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
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.
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?
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.
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?
Click tracking should work. Most likely, your setup is somewhere incorrect.
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?
If some keys in the array/object may change, clearing is an option.
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
Yes, you are correct.
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?
You can add multiple tags to the same trigger.
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.
No, unless you manually send it to those tools.
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?
You should discuss this with your developers.
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
Hi, read the first sentence of the blog post. Highlighted in red :)
If i create a GA4 property, after this i can i connect with my old analytics property?
There is no connection between ga3 and ga4. It does not matter whether you create both properties together or separately
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?
No,that is developer's responsibility to figure out
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?
GA automatically handles subdomains. Just make sure that you use the same GA property/data stream for every subdomain.
Hi Julius, Thank you for going into such detail and I always appreciate your flow charts :)
According to GA4 documentation, the configuration tag should be fired on every page. I assume this includes virtual pages. Is there anything wrong with having both history changes and page views as triggers for the config tag (when enabled to fire a page view event)? Or is it better to create a separate GA4 event for history changes as you described?
It does not include virtual pages
Can you clarify why the config tag should NOT be fired on all virtual pages?
Thank you for the article. My questions if it is possible instead of creating custom event page_view just using configurational tag with option send pageview hit?
Hey thanks for the article! However, I got stuck on option nr 3)
Despite following the steps, I have issues fixing the page_view event properly in our GA4 using GTM for our SPA. I set enhanced Measurement turned off, added GA4 config tag firing on all pages but not ticking in the box to ”send a page view” - as our pageView event is tricked upon re-load) and a separate GA4 event tag to capture the page views with pageURL and pageTitle as parameters.
I can see these pageView events triggered in GTM preview mode, but not as GA4 event on the tag.
Also. since we we have two GA4 accounts and measurement IDs/streams (one for Sweden and one for UK), we set the "Measurement ID" up as a variable which picks up the measurement ID. Do you think that might be causing our issues? (Instead of just writing the Measurement ID out in plain text and creating country-specific tags? (that worked previously but want to avoid having too many tags). Also, debug mode isn't working, despite setting it to true in the parameter section, which I found odd.
Thanks for any help you might have here.
Cheers!
Hi Julius,
First of all: thanks for the helpful article!
I have a question about the following statement:
¨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.¨
Why is this important? I dont do it right now in our setup, and with every event, it sends the right (vpv) page_location and page_title data to GA4.
Curious!
Stefan
Hello Julius, thank you for the valuable information in this article. I am taking my first steps in GA4. In my case, I am trying to push user id to GA4 (following your tutorial on tracking user id in GA4), but due to the website being SPA, I have not succeeded, even though the data is successfully stored in the variable in GTM. No pageview, no container loaded, GA4 configuration tag is not firing All Pages trigger. I see History events on all other navigating actions around the website except, the login event itself. Should I try the "virtual event" approach? And how should I combine this to GA4 configuration tag?