Add Google Analytics To Next.js/React App

We'll be integrating Google Analytics with Next.js. The same can be applied to a React App with React Router.

The Approach

Instead of adding Google Analytics directly to Next.js, we are going to add it via Google Tag Manager (GTM). This way we can quickly easily add/manage all of our tags on this platform instead of adding scripts directly to our application, building it, and deploying.

If you don't want to add GTM and just have Google Analytics, this post will still be relevant.

Adding The GTM Scripts

First let's add the GTM scripts to the <head/>. We want this to exist on every single page in our application. Since I'm using Next.js, I'm going to add this in _document.js.

Here is what a minimal version of what your component might look like:

import Document, {
  Html,
  Head,
  Main,
  NextScript,
  DocumentContext,
} from 'next/document';


const gtagId = process.env.NEXT_PUBLIC_GTAG_ID;

const gtagHead = `(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','${gtagId}')`;
const gtagBody = `<iframe src="https://www.googletagmanager.com/ns.html?id=${gtagId}" height="0" width="0" style="display:none;visibility:hidden"></iframe>`;

class MyDocument extends Document {

  static async getInitialProps(ctx: DocumentContext) {
    const initialProps = await Document.getInitialProps(ctx);
    return { ...initialProps };
  }

  render() {
    return (
      <Html lang="en">
        <Head>
          {process.env.NODE_ENV === 'production' && !!gtagId && (
            <script dangerouslySetInnerHTML={{ __html: gtagHead }} />
          )}
        </Head>
        <body>
          {process.env.NODE_ENV === 'production' && !!gtagId && (
            <noscript dangerouslySetInnerHTML={{ __html: gtagBody }} />
          )}
        </body>
        <Main />
        <NextScript />
      </Html>
    );
  }
}

In the snippit above, you can see we conditionally load the Google script tag.

  1. If the script tag exist as an env variable. Doesn't make sense to load the script if the tag is missing.
  2. If we are on production build. We do this so we don't end up triggering analytic events when we are developing.

Note: The <noscript/> is only required for Google Tag Manager, if you are using Google Analytics, there will be only one script you'll need to add in the header.

Listening To Page Changes

Since this is a Single Page Application (SAP) we need to use our Router to detect for page changes. With Next.js we'll be using the their router, next/router.

We need to listen to the page change event on a component that will load on every page and fire a pageview event. In my case, I'm putting it in _app.js.

function MyApp({ Component, pageProps }) {
  const handleRouteChange = url => {
    if (
      typeof window === 'undefined'
      || !window.dataLayer
    ) {
      return;
    }

    const event = {
      event: 'pageview',
      page: url,
    };
    window.dataLayer.push(event);
  };

  useEffect(() => {
    Router.events.on('routeChangeComplete', handleRouteChange);

    return () => {
      Router.events.off('routeChangeComplete', handleRouteChange);
    };
  }, []);

  return (
    <Component {...pageProps} />
  );
}

Adding Google Analytics To Google Tag Manager

Follow the steps here to pipe the Tag Manager events to Google Analytics.

Debug

If you've coded up everything but don't see the analytic events, here's a list of stuff you can double check:

  • Submit and Publish your newly created GA Tags
  • Hard refresh on your browser
  • On local? Remove the process.env.NODE_ENV production check

Conclusion

And there you have it. I can now safely confirm that you and I are the only ones who've seen this page 👀