Google Analytics

In October 2020 Google launched GA4 - a major change to Google Analytics, one which is more event driven to reflect that there has been a fundamental shift away from traditional web sites to web applications and mobile apps.

What follows is a guide for implementing gtag.js into the codebase, this is different from Google Tag Manager.

Terminology

Tag Manager ( a tool for processing events in a website and sending to a GA account ) can have difficulty with the complexity of site described in this guide as it's more of a full on javascript application than a website.

GA4 - it's wildly different than GA3 and Universal Analytics, and is the recommended path take by Google who is committed to it (as it works better with Javascript applications (like pathways), mobile apps etc).

gtag.js - Is a developer friendly library that exposes only a few methods for logging events later in this document each of these will be explored.

Prerequisites

GA4 Measurement ID

Setup

Install NPM Package

Install the ga-4-react npm package, it helps to ensure the that the google scripts are initialized correctly before calling functions.

This small but mighty package will do a few things:

  • Reference the google script file from google's CDN

  • Add a script tag to the head element to execute javascript to wire up GA

  • Expose GA4 in react in an easy to use way.

Terminal / Command Prompt ( /azure-quickstart/ui )
npm install ga-4-react

Add Environment Variable

Update .env file to include the GA4 Measurement ID.

  • Create React App

/azure-quickstart/ui/.env
...
REACT_APP_GA_MEASUREMENT_ID=<<your GA4 measurement ID>>
...
  • Vite

/azure-quickstart/ui/.env
...
VITE_GA_MEASUREMENT_ID=<<your GA4 measurement ID>>
...

Initialize React Component

Update index.tsx to:

  • Read the GA4 Measurement ID environment variable

  • Create an instance of the Ga4react class

    • Automatically enter into debug mode when running locally

  • Initialize the ga4react object prior to rendering the app to ensure GA4 is available in the app.

/azure-quickstart/ui/index.tsx
...
import GA4React from 'ga-4-react';

const gaMeasurementId = process.env.REACT_APP_GA_MEASUREMENT_ID as string;
const ga4react = new GA4React(gaMeasurementId,{ debug_mode: process.env.NODE_ENV !== 'production'});

(async() =>  {
  
  try{
    await ga4react.initialize();
  }catch(error){
    console.error(error);
  }

  ReactDOM.render(
    ...
  );
  
})()

...

Configure CSP Header

Once deployed, the website will not work as the webpage is referencing a script from a different domain and also using some inline code, thanks to CSP.

To get things working the HTTP headers will need to be updated:

  • Allow script and images to be loaded from a different domain

  • Allow a very specific inline script to be run (adding a hash of the script)

    • As the inline script includes your GA Measurement ID, you have to generate the hash yourself for each environment (assuming you use a different measurement ID for QA/UAT/Prod)

This will not work with proper security headersUpdate CSP

To generate the CSP hash use the following as an example:

The script block is what gets injected on your page from the react component, to get the specific specific block for your code, pause your app in chrome debugger and get your exact string. ( /src/lib/ga4tagmanager.tsx ~line 130) (be sure to change the debug_mode to false if you are debugging locally, as it wont be false when you deploy to your production environment)

window.dataLayer = window.dataLayer || [];
        function gtag(){dataLayer.push(arguments);};
        gtag('js', new Date());
        gtag('config', 'G-XXXXXXXXXX', {"debug_mode":false});

(change out the code with your GA Measurement ID and use an online hash generator to get the proper hash value, in the example above, the hash would be: sha256-IBkv3CC/Fwz7jbHfcCIlP19U0yv+7XjdPbTS4DBRBeg=)

Read more on CSP with these resources

Example CSP from a Static-Web-App config section for reference.

/azure-quickstart/ui/staticwebapp.config.json
{
  ...
  "globalHeaders": {
    "content-security-policy": "default-src https: 'unsafe-eval' 'unsafe-inline' https://www.googletagmanager.com; object-src 'none'; script-src 'self' https://cdn.jsdelivr.net https://www.googletagmanager.com 'sha256-IBkv3CC/Fwz7jbHfcCIlP19U0yv+7XjdPbTS4DBRBeg='; img-src 'self' www.googletagmanager.com data:;"
  },
  ...
}

Integrate with ReactRouter

Update App.tsx to register every page change by subscribing to react routers' location changes

/azure-quickstart/ui/src/App.tsx
...
import React, { useEffect } from 'react';
import { Switch, Route, useLocation } from "react-router-dom";
import  { useGA4React } from "ga-4-react";

..
 
function App() {
  const ga = useGA4React();
  const location = useLocation();
  
  useEffect(()=> {
    if(ga){
      ga.pageview(location.pathname)
    }
  }, [location, ga])

  return (
    <>
      ...
    </>
  );
}

export default App;

Integrate With React Components

Note that "select_content" and "click" are Google defined events each with their own valid parameters. Google has relatively low limits on the number non-standard event types and parameters therefore it is recommended to stick to the standard types and parameters.

/azure-quickstart/ui/src/components/<<some component>>.tsx
import{ useGA4React } from "ga-4-react";


const MyComponent: FC< RouteComponentProps| any> = (props) => {
  const ga = useGA4React();

  var someAction = (eventDetail:string) => {
    if(ga && eventDetail ){
      ga.gtag(
        'event',
        'select_content',
        {
          'content_type':'user-took-some-action',
          'item_id': eventDetail
        }
      );
    }
  }
  
  const trackOutboundLink = (event:any) => {
    var targetUrl = event.target.href;
    if(ga && targetUrl ){
      ga.gtag(
        'event',
        'click',
        {
          'page_location': targetUrl,
          'page_referrer': document.location.pathname + document.location.search
        });
    }
  }
  
  ...
  
  return(
    <>
        ...
          <a href='https://www.google.com' target='_blank' rel='noreferrer'  onClick={trackOutboundLink}>Google</a>
        ...
    </>
  )
}

export default withRouter(MyComponent);

Adding Helmet to update Page Name

It is always good to update the page title for usability, it also benefits in GA

Terminal / Command Prompt ( /azure-quickstart/ui )
npm install react-helmet

Integrate Helmet with your React Pages

/azure-quickstart/ui/src/components/<<some component>>.tsx
import React, { FC } from "react";
import { Helmet } from 'react-helmet';
...


const MyComponent: FC<any> = (props) => {
  ...

  return(
    <>
        <Helmet>
          <title>A PAGE TITLE FOR THIS COMPONENT</title>
        </Helmet>
        ... YOUR NORMAL REACT CODE ...
    </>
  )
}

Verifying with Debug Mode

Testing everything out:

Debug the codebase and navigate to the webpage, a new floating box will be shown the lower right of the screen, click "connect" to connect to the Tag Assistant, a new browser window will open and from there it is easy to see google is receiving from the website.

Additional Resources

Last updated