Google tag manager, out of the box, is unable to track React js page navigations using react-router
.
The reason is how the react-router
uses react virtual dom to manipulate the view. Unlike traditional anchor tags, React's router Link doesn't do a full page reload.
Instead, the router Link only updates the altered dom elements and leaves the rest of the dom untouched.
That is the expected behavior and much more efficient than manipulating the entire DOM.
This article explains three ways to integrate Google Tag Manager with React Js.
- Using Google-provided tag manager scripts with history change trigger.
- Using the npm package
react-gtm-module
with history change trigger. - Directly triggering a modified Google Tag Manager script.
1. Integrate GTM with Google-provided tag manager scripts
In this method, I place the GTM scripts in the index.html file located in the public folder. Use the
Here is my index.html
after the changes.
<!DOCTYPE html>
<html lang="en">
<head>
<!-- Google Tag Manager - as high in the <head> of the page -->
<script>(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':
new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],
j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);
})(window,document,'script','dataLayer','GTM-XXXXXXX');</script>
<!-- End Google Tag Manager -->
<title>React App</title>
</head>
<body>
<!--immediately after the opening <body> tag-->
<noscript><frame src="https://www.googletagmanager.com/ns.html?id=GTM-XXXXXXX"
height="0" width="0" style="display:none;visibility:hidden"></iframe></noscript>
<!-- End Google Tag Manager (noscript) -->
<div id="root"></div>
</body>
</html>
The next step is to create a history change trigger. Proceed to this step to create a history change trigger in the GTM console. Now my app is all set to track page views.
2. Integrate GTM with the npm package react-gtm-module
There is an npm package to integrate GTM with React Js. It is called react-gtm-module
.
I can install the package by running
npm install react-gtm-module --save
or
by editing the package.json
file and adding "react-gtm-module": "^2.0.11"
as a dependency.
Now I must place the tracking code in the application. The same rule applies to the tag manager scripts, but I cannot select index.html
this time.
I use App.js
, but your choices may vary depending on your app architecture.
import React from 'react';
import { BrowserRouter, Route } from 'react-router-dom';
import TagManager from 'react-gtm-module';
import Users from './Users'
import Posts from './Posts'
import Login from './Login';
const App = () => {
useEffect(() => {
TagManager.initialize({ gtmId: 'GTM-XXXXX' });
}, []);
return (
<BrowserRouter>
<React.Fragment>
<Route path="/" component={Users} exact />
<Route path="/posts" component={Posts} exact />
<Route path="/login" component={Login} exact />
</React.Fragment>
</BrowserRouter>
);
}
export default App;
The next step is to create a history change trigger. Proceed to this step to create a history change trigger in the GTM console. Now my app is all set to track page views.
3. Integrate GTM by directly triggering a modified Google Tag Manager script
I modified the GTM script so I could call it directly. Then I created a separate file to place my modified analytics script. My modified script is in Typescript, which is helpful for most users.
export const analytics = ((w: Window, d: Document, s: string, l: string, i: string) => {
(w as any).dataLayer = (window as any).dataLayer || [];
(w as any).dataLayer.push({'gtm.start':new Date().getTime(),event:'gtm.js'});
var dl = l != 'dataLayer' ? '&l='+l : '';
var scr = 'https://www.googletagmanager.com/gtm.js?id='+i+dl;
/*
To avoid Multiple installations of google tag manager detected warning
*/
if(!scriptExists(scr)){
var f = d.getElementsByTagName(s)[0],
j: HTMLScriptElement = d.createElement("script")
j.async = true;
j.src = scr;
f?.parentNode?.insertBefore(j,f);
}
})
const scriptExists = (url: string) => {
var scripts = document.getElementsByTagName('script');
for (var i = scripts.length; i--;) {
if (scripts[i].src == url) return true;
}
return false;
}
Now I must call this function whenever the user clicks a link. Therefore, I need a common page shared by all web application pages.
useEffect(() => {
analytics(window, document, 'script', 'dataLayer', 'GTM-AA12345');
},[])
That's it. I don't need to implement a history change trigger with this method.
Creating a history change trigger in the Google tag manager console
Now it's time to create a history change trigger in the Google tag manager console. Log into google tag manager and follow the below steps.
-
Select workspace and click Triggers
-
To create a new trigger, click
New -> Trigger Configuration
and selectHistory Change trigger
from the list.
-
Make sure to check
All History Changes
.
-
Name the trigger and click
save
.
Don't forget to publish your workspace container. Now you would see a history change trigger for navigations within your application.
Debug google tag manager with tag assistant
We can use Google tag assistant to ensure all the intended triggers are triggered on your page navigations. To start a debug session, click the Preview button on the top right. Once you click start, the tag manager will open a debug window connected to your website. If it fails to connect the debugger to your website, close the tab and restart the debug session. I have experienced this issue several times. Once the session is successful, use the window to navigate your website links. You will notice the history trigger fires on each navigation.
Verifying your tags with Google Tag Assistant
The easiest way to ensure your website tags are triggered is to install the Google tag assistant chrome extension. Tag assistant will also tell you if you have any duplicate tag issues. Install it from https://get.google.com/tagassistant.
Why does Google Analytics not track internal page navigations?
React js uses the virtual dom concept to update the view efficiently compared to traditional dom-manipulation technics. React updates only the necessary parts of the dom, leaving the rest of the dom unchanged. Therefore, clicking on a react <Link/> and a traditional <a href/> behave differently.
React js Link vs traditional anchor tag
React js Link is derived from the react-router-dom
. Therefore, react Link is used to routing through virtual dom instead of real dom.
Any changes detected during the transition are updated to the real dom. For example, the illustration below represents a page
transition from page 1 to page 2 in react js. Note that the only change between the two pages is the content image. The rest of
the page remains the same. React detects this change by comparing the virtual dom and re-renders only the changed portion of the dom.
In this case, the content component.
Using the traditional anchor tag in place of the react Link would result in the entire page load regardless most of the page content remains the same.
Pros and cons of React Router Link
The main advantage of the react Link is performance due to efficient dom manipulation. As I mentioned earlier, the changes are compared in the virtual dom and update only necessary elements in the real dom, avoiding a total dom re-render.
One potential drawback is external javascript execution. The javascript files you import in your application and javascript code reside inside the page header or body is executed only once. That's when you enter the page by typing the URL or any way that causes a full page load. And this is also the reason why Google analytics does not track your internal page navigations.