reactjs

Google Tag Manager in Next.js

The app I'm currently working on is bootstrapped using Next.js and needs to implement Google Tag Manager. The task was assigned to me, but Google's "Quick Start Guide" for GTM wanted me to do this:

"Copy the following JavaScript and paste it as close to the opening tag as possible on every page of your website, replacing GTM-XXXX with your container ID:"

<!-- Google Tag Manager -->
<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.jsid='+i+dl;f.parentNode.insertBefore(j,f);})(window,document,'script','dataLayer','GTM-XXXX')</script>
<!-- End Google Tag Manager -->

And also this:

"Copy the following snippet and paste it immediately after the opening tag on every page of your website, replacing GTM-XXXX with your container ID:"

<!-- Google Tag Manager (noscript) -->
<noscript><iframe src="https://www.googletagmanager.com/ns.html?id=GTM-XXXX" height="0" width="0" style="display:none;visibility:hidden"></iframe></noscript>
<!-- End Google Tag Manager (noscript) -->

If you know a little more about React development than I do after four months, you can instantly spot that following these instructions very literally is going to require you to use dangerouslySetInnerHTML to force React to use this inline script in the <head> of the page. The reason this attribute is named so verbosely and alarmingly is that incautious use of it opens a site up to a cross-site scripting attack.

I didn't know this, so I had to spend five minutes searching to find it out. Still, when I did, I immediately started looking around for a more React-friendly alternative. (Google's own docs were strangely silent on the issue.) Further searching produced this module, but its examples weren't very helpful:

import React from 'react'
import ReactDOM from 'react-dom'
import Router from 'react-router'
import routes from './routes'

...
import TagManager from 'react-gtm-module'

const tagManagerArgs = {
  gtmId: 'GTM-000000'
}

TagManager.initialize(tagManagerArgs)
...

const app = document.getElementById('app')
ReactDOM.render(<Router routes={routes} />, app)

When you read documentation for almost anything based on React you see things like this all the time, and when you're an Extremely Junior DeveloperTM it can be very frustrating because you never mount an app the way those last two lines suggest when you're using the common bootstrappers like create-react-app or Next.js. Since you only barely understand how any of this works, you can feel very uncertain about how to adapt this code to your own actual app. And what are those ellipses hiding? I'd like to see that code, please.

So from my experience as that Extremely Junior Developer, this just looked like we were tossing an initialize method into a random location where it would never get called. I had to dig around for quite some time to find this Next.js GitHub issue that provided a helpful example, not for Google Tag Manager, but for Google Analytics. Fortunately, the poster explicitly said that it would work for "other ga tools"

My needs were slightly simpler than the poster's, so here's the code I ended up with ("XXXXXXX" here represents whatever your GTM ID might be), in the _app.js file of my NextJS installation:

import App, { Container } from 'next/app'
import React from 'react'
import './app.css'
import TagManager from 'react-gtm'

const tagManagerArgs = {
  id: 'GTM-XXXXXXX'
}

class MyApp extends App {
  componentDidMount () {
    TagManager.initialize(tagManagerArgs)
  }

  render () {
    const { Component, pageProps } = this.props
    return (
      <Container>
        <Component {...pageProps} />
      </Container>
    )
  }
}

export default MyApp

A couple notes:

  1. If you don't have an _app.js file in your Next app, here's what that's all about.
  2. I didn't actually end up using the module I mentioned above, but this fork of it, which seemed like it was a little more up-to-date.

Hopefully this helps some young developer who felt just as lost as I did when I was trying to solve this problem.