How to implement Google Tag Manager using the next/script component in Next.js 11?

Version 11 of Next.js recently introduced a new approach with the Script component offering various strategies.

To avoid duplicate tags, it is advised to implement Google TagManager using the afterInteractive strategy.

In my experimentation:

// _app.js

import Script from 'next/script';

class MyApp extends App {
  public render() {
    const { Component, pageProps } = this.props;

    return (
      <>
        <Script strategy="afterInteractive">
          {`(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>
        <Component {...pageProps} />
      </>
    );
  }
}

export default MyApp;

The current implementation successfully loads Google Tag Manager, however, it inserts the same script on each page navigation leading to duplicate tags being generated.

Is there a more effective way to utilize the new Script component?

Answer №1

It's important to assign an id to your <Script> element when it contains inline content (without a src attribute).

By doing so, you can easily track whether it has already been rendered to avoid duplications.

This guideline is outlined in the eslint rule connected to Next:

next/script components with inline content need to have an id attribute specified for tracking and script optimization purposes.

For more details, refer to: https://nextjs.org/docs/messages/inline-script-id

An example solution would be as follows:

    <Script id="gtm-script" strategy="afterInteractive">{`...`}</Script>

Furthermore, it is recommended to incorporate eslint into your Next project:
You can either execute next lint command or install eslint next config using the following command:

yarn add -D eslint eslint-config-next

Then, create a .eslintrc.json file in your project root directory with at least this configuration:

{
  "extends": ["next/core-web-vitals"]
}

Explore more about Next.js eslint configuration here: Information about next.js eslint configuration.

Answer №2

My solution involved dissecting the GTM script.

I placed the initial dataLayer object on the window in the _document page.

// _document.js

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

export default class MyDocument extends Document {
  render() {
    return (
      <Html lang="en">
        <Head>
          <meta charSet="utf-8" />

          <script
            dangerouslySetInnerHTML={{
              __html:
                `(function(w,l){` +
                `w[l] = w[l] || [];w[l].push({'gtm.start':new Date().getTime(),event:'gtm.js'});` +
                `})(window,'dataLayer');`,
            }}
          />
        </Head>
        <body>
          <Main />
          <NextScript />
        </body>
      </Html>
    );
  }
}

The GTM script is loaded using the Script component (which should not be used on the _document page)

// _app.js

import Script from 'next/script';

class MyApp extends App {
  public render() {
    const { Component, pageProps } = this.props;

    return (
      <>
        <Script src={`https://www.googletagmanager.com/gtm.js?id=GMT-XXXXXXX`} />
        <Component {...pageProps} />
      </>
    );
  }
}

export default MyApp;

Answer №3

Make sure to include an "id" parameter in your inline scripts for Next to efficiently manage script loading.

Although the documentation touched on it, this detail was overlooked in the initial release.

A patch was subsequently introduced to address this issue, so updating your Next setup should resolve it.

Find the Patch here - https://github.com/vercel/next.js/pull/28779/files/11cdc1d28e76c78a140d9abd2e2fb10fc2030b82

Join the Discussion Thread - https://github.com/vercel/next.js/pull/28779

Answer №4

When working with newer versions of NextJS (13-14), there is now a more streamlined approach available through the @next/third-parties library:

  1. To get started, simply run
    npm install @next/third-parties@latest next@latest
    or
    yarn add @next/third-parties@latest next@latest
  2. In your pages/_app.jsx/.tsx or app/layout.tsx/.jsx, include the following line:

import { GoogleTagManager } from '@next/third-parties/google'

  1. Insert
    <GoogleTagManager gtmId={process.env.NEXT_PUBLIC_GTM_ID} />
    into your return statement (within the _app/layout file)
  2. Remember to add NEXT_PUBLIC_GTM_ID to your environment variables or replace {process.env.NEXT_PUBLIC_GTM_ID} with your actual GTM id

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

The Express.js feature "app.use() mandates the use of middleware functions"

Currently, I am delving into learning Express.js 4 and Node, but I seem to have hit a roadblock with an error that has me stumped. I'm attempting to utilize the node-sass package to compile my sass code; however, I've encountered obstacles in ge ...

The use of dangerouslySetInnerHTML causes the page layout to stretch, expand, or grow in size

I am currently working on my NextJs app, where I'm utilizing CosmicJs as a headless CMS to showcase content on the webpage. Within the layout of my page, I have structured it with 3 columns, and the content pulled from the CMS is meant to be displaye ...

Issue with passing boolean parameter in Javascript not functioning properly

I have a function that contains multiple if statements, designed to execute when a parameter is true. However, I've noticed that passing false as a parameter does not seem to have any effect. Can you help me figure out what I'm doing wrong? fu ...

working with JSON array information

I have a JSON array retrieved from a database that I need to manipulate. Currently, it consists of 8 separate elements and I would like to condense it down to just 2 elements while nesting the rest. The current structure of my JSON looks like this: { "i ...

Create a function within jQuery that triggers when an option in a select field is chosen

As I edit a podcast, I have encountered a situation where an option is being selected through PHP code. Now, I am looking to implement jQuery functionality for when the option is selected from the select field. I have searched through various questions an ...

Expand the video comparison slider to occupy the entire width and height

I am striving to develop a video comparison slider that fills the height and width of the viewport, inspired by the techniques discussed in this informative article: Article Despite my efforts, I have not been successful in achieving this effect so far a ...

NodeJS module loading issue

Currently, I am attempting to utilize the resemblejs library () in order to compare two images. Despite creating a directory and adding resemblejs as a dependency, when running nodejs test.js, an error occurs: var api = resemble(fileData).onComplete(funct ...

Execute an asynchronous function in Javascript, then output the returned data to the console

Is there a way to effectively handle the data returned from an async function? example: JS FILE: async function getData(){ try { $.getJSON('./data.json', (data) => { return data; }); } catch(error ...

Filter a nested array in AngularJS and retrieve the parent object as the result

I've recently started experimenting with AngularJS to create checkbox filters for my wine store. Here is an example of an item in my store: { "id": 17, "name": "Ermelinda Freitas Reserva", "tag_ids": [40, 12, 56, 6, 60], " ...

Sorting through a collection by using a nested array

I've been working on optimizing my code to prevent making new http requests to my API every time I need to filter results. Currently, I have an array called pageContent that is populated with data from an API fetch when the page loads. Each object in ...

What is the best way to receive a response from the foo.onload = function() function?

My current code involves a function that is triggered by .onload, and I'm looking to return a specific value based on certain conditions: newImg.onload = function() { var height = newImg.height; var width = newImg.width; if(height > wi ...

AngularJS Multi-select Dropdown Filter Logic

Thank you for taking the time to review my query. Currently, I am working on an AngularJS UI-Grid project where I have incorporated a Multi-select Drop-down Menu for the "First Name" column. However, I am facing challenges in implementing the logic similar ...

A beginner's guide to integrating AWS Textract with React/NextJS

Seeking assistance to clean up my code or get a nudge in the right direction. I believe I'm on the right track, but some help would be great. Currently utilizing this. Here's the snippet of my code. UPDATE: Error: err TypeError: input.filter i ...

Display the bash script results on an HTML webpage

My bash script fetches device status and packet loss information, slightly adjusted for privacy: #!/bin/bash TSTAMP=$(date +'%Y-%m-%d %H:%M') device1=`ping -c 1 100.1.0.2 | grep packet | awk '{ print $6 " " $7 " " $8 }'` device2=`pin ...

Sending data from PHP to JavaScript using JSON encoding through POST requests

I am dealing with a multi-dimensional array that needs to be passed to a PHP script using JavaScript to parse JSON data and display it on Google Maps. I am experimenting with using forms to achieve this: <?php $jsontest = array( 0 => array( ...

What is the method for incorporating parameters into an array filter?

Imagine having an array containing multiple duplicate objects. How can we create a condition to specifically identify objects with certain criteria, such as matching IDs, duplicate status, and duplicate dates? The goal is to only display the object with th ...

The instance of the Javascript Object Prototype losing its reference

I'm currently developing a small Javascript Object that will attach click listeners to specific elements, triggering an AJAX call to a PHP function. Everything is functioning as expected, but I want to execute a function once the AJAX response is rece ...

Communication breakdown between components in Angular is causing data to not be successfully transmitted

I've been attempting to transfer data between components using the @Input method. Strangely, there are no errors in the console, but the component I'm trying to pass the data from content to header, which is the Header component, isn't displ ...

Is there a way to remove the "next" button from the last page in a table?

I'm currently working on implementing pagination for my table. So far, I have successfully added both the "next" and "previous" buttons, with the "previous" button only appearing after the first page - which is correct. However, I am facing an issue w ...

Utilizing multiple Next.js parameters in combination with Supabase for creating dynamic routes

Recently, I embarked on a small project to enhance my knowledge of Nextjs using supabase. However, I have encountered a slight obstacle along the way. Essentially, I have created a table for stores (including fields such as name, email, address, and slug) ...