GTM randomly skips initial pageview in single page app
C

3

18

I have Pageview tag in Google Tag Manager that tracks SPA pageviews, identical to the one described in this guide. Basically it is Universal Analytics with linked Google Analytics ID that is triggered on History Change (at some point All Pages trigger was also added with no success).

In my current app GTM skips Pageview tag on initial pageviews on all routes that don't have async resolvers. Usually the routes fire the tag sometimes (1 of 5 times), this may vary a bit depending on conditions (cached vs uncached, localhost vs production).

On the routes that have resolvers with long durations (> 1s) Pageview tag is always fired on initial pageviews (5 of 5 times).

Pageview tag is fired normally on all routes after app initialization (pushState).

This behaviour was confirmed with GTM debug console and GA realtime monitoring.

The setup seems to be the recommended one, GTM snippet is loaded in <head>, Angular 2 app is loaded at the end of <body>.

<html>
  <head>
    <script>/* Google Tag Manager snippet */</script>
    ...
  </head>

  <body my-app>
    ...
    <script src="my-app-bundle.js"></script>
  </body>
</html>

And Angular 2 is bootstrapped like usual:

platformBrowserDynamic().bootstrapModule(MyAppModule);

I've tried to move GTM snippet all around, before and after my-app-bundle.js, even replace it with synchronous:

<script>
  window.dataLayer = ...
</script>
<script src="https://www.googletagmanager.com/gtm.js?id=..."></script>

There was no difference with default snippet.

I've found by trial and error that Pageviews start to work normally on initial pageviews if the app is bootstrapped with considerable delay, 200-1000ms (it seemed at first that DOMContentLoaded does the trick but the delay wasn't enough):

setTimeout(() => {
  platformBrowserDynamic().bootstrapModule(MyAppModule);
}, 1000);

I hope that this problem is familiar to the experts who've done GTM with SPA/Angular 2 applications. Unfortunately, I cannot provide MCVE for this case but I believe it can be replicated with any Angular 2 (2.3.1) app with routing and Google Tag Manager account.

Usually Angular 2 apps can be safely bootstrapped at the end of <body>.

What is going on there and how pageview tracking should be properly handled with GTM without race conditions?


UPDATE: When switching from GTM to using GA directly with

router.events.subscribe(e => {
  if (e instanceof NavigationEnd)
    ga('send', 'pageview', location.pathname);
})

everything works fine on initial and subsequent pageviews with no race conditions.


UPDATE 2:

Here's a timeline of how it looks in the case of success with long-running route resolver, gtm.js and main.bundle.js are loaded in the beginning (it doesn't matter in which order), analytics.js (analytics_debug.js when GA Debugger is on) is loaded when route resolver completes and Pageview tag is fired, i.e. after ~10s:

Convalescent answered 14/1, 2017 at 21:21 Comment(2)
I'm not sure if this would help in this instance; but the google-sign-in button must be manually rendered in NgAfterViewInit due to a very similar issue. Is there a manual function you could call in this library to trigger the behavior you need?Ries
@Ries Unfortunately, no. It looks like A2 app should be adapted to fit GTM lifecycle, not in the opposite way (currently it looks like the app should be bootstrapped on window load to fit it).Convalescent
C
1

As @kemsky suggested, GTM lifecycle is tied to internal gtm.load event, which happens on window onload. So DOMContentLoaded may be too early to bootstrap.

Considering that GTM script was loaded prior to SPA script,

window.addEventListener('load', () => {
  platformBrowserDynamic().bootstrapModule(MyAppModule);
});

callback will be triggered when GTM is ready to receive history change events, and there should be no race conditions.

Convalescent answered 29/1, 2017 at 6:55 Comment(2)
But this will delay app bootstrap. Any way to fix this without performance penalty ?Damiano
@Damiano I guess it's one way or another. The other way would be hacking GTM source code, which is not an option. From my experience delay was negligible (had to switch to GA to avoid GTM overhead any way). Feel free to update with your measurements if you will have performance issues due to window.addEventListener('load' ...).Convalescent
G
0

It looks like analytics scripts are loaded with <script async=true>:

j.async = true; or a.async = 1;

Try to remove async and see if it helps.

Gaikwar answered 22/1, 2017 at 22:8 Comment(7)
It was already mentioned in the question, even replace it with synchronous:.Convalescent
You have tried with gtm.js, but in fact gtm.js loads analytics.js and it is also loaded async. it looks like you should either not use gtm or use gtm.load event to start application.Gaikwar
I don't think that this is the case. It is hard to believe that GTM is that stupid. I've updated the question with a screenshot that confirms that it's not. GTM loads analytics.js on demand. The problem is not that GA is not loaded but the tag is not fired (it is seen in GTM debug bar).Convalescent
But you've mentioned interesting thing. Indeed, it looks like GTM differs gtm.dom (document load?) and gtm.load (window load?) events. This could possibly explain why I had no success with delaying the bootstrap with DOMContentLoaded. What do you mean by use gtm.load event to start application?Convalescent
previously i had experience with google charts, it works using the same pattern when one script is loading other and so on. You can delay start using gtm.load event instead of domconententloaded. Also google had issues with zone.js, so you may try to update zone, to load zone.js before or after gtm is loaded.Gaikwar
Do you have specific information about gtm.load DOM event? window.addEventListener('gtm.load'... and document.addEventListener('gtm.load'... listeners aren't triggered. I guess I've already excluded zone.js from the equation, I've tried to load GTM as the first and the last script on the page.Convalescent
It looks like gtm.load fires on window load event (not sure what happens there, but it looks like it is a crucial moment). So considering that gtm.js is executed before JS file where SPA is initialized, bootstrapping on window load should be a solution. It was your idea in the first place, and I guess it can be accepted as an answer.Convalescent
H
0

You may change the trigger of your UA pageview tag from plain 'pageview' and stick to custom event fired at NavigationEnd

router.events.subscribe(e => {
  if (e instanceof NavigationEnd) {
    dataLayer = window.dataL
    dataLayer.push({'event':'custom pageview event'});
  }
})
Honeysucker answered 19/2, 2018 at 9:5 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.