How to use google analytics with next.js app?

56,828

Solution 1

To correctly initialize gtag, do the following in _document.js or wherever you defined Head:

import Document, { Head } from "next/document";

export default class MyDocument extends Document {
  render() {
    return (
      // ...
      <Head>
        <script
          async
          src="https://www.googletagmanager.com/gtag/js?id=[Tracking ID]"
        />

        <script
          dangerouslySetInnerHTML={{
            __html: `
              window.dataLayer = window.dataLayer || [];
              function gtag(){dataLayer.push(arguments);}
              gtag('js', new Date());
              gtag('config', '[Tracking ID]', { page_path: window.location.pathname });
            `,
          }}
        />
      </Head>
    );
  }
}

The above will track page views on page load. To track navigation add the following to _app.js:

import { useRouter } from 'next/router';
import { useEffect } from "react";

export default const App = () => {
  const router = useRouter();

  const handleRouteChange = (url) => {
    window.gtag('config', '[Tracking ID]', {
      page_path: url,
    });
  };

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

  return (
    // ...
  );
};

See also:

Solution 2

To setup Google analytics with NextJS using Typescript

I'm using below setup for my personal site (https://github.com/GorvGoyl/Personal-Site-Gourav.io) and it's working fine without any linting errors. Analytics is enabled only for production.

  • Create a Google analytics project and get Measurement ID.
  • In your NextJS project, create /lib/gtag.ts file and add your Google Measurement ID:
export const GA_TRACKING_ID = "<INSERT_TAG_ID>";

// https://developers.google.com/analytics/devguides/collection/gtagjs/pages
export const pageview = (url: URL): void => {
  window.gtag("config", GA_TRACKING_ID, {
    page_path: url,
  });
};

type GTagEvent = {
  action: string;
  category: string;
  label: string;
  value: number;
};

// https://developers.google.com/analytics/devguides/collection/gtagjs/events
export const event = ({ action, category, label, value }: GTagEvent): void => {
  window.gtag("event", action, {
    event_category: category,
    event_label: label,
    value,
  });
};
  • Also install gtag types:
npm i -D @types/gtag.js
  • Create /pages/_document.tsx:
import Document, { Html, Head, Main, NextScript } from "next/document";

import { GA_TRACKING_ID } from "../lib/gtag";

const isProduction = process.env.NODE_ENV === "production";

export default class MyDocument extends Document {
  render(): JSX.Element {
    return (
      <Html>
        <Head>
          {/* enable analytics script only for production */}
          {isProduction && (
            <>
              <script
                async
                src={`https://www.googletagmanager.com/gtag/js?id=${GA_TRACKING_ID}`}
              />
              <script
                // eslint-disable-next-line react/no-danger
                dangerouslySetInnerHTML={{
                  __html: `
            window.dataLayer = window.dataLayer || [];
            function gtag(){dataLayer.push(arguments);}
            gtag('js', new Date());
            gtag('config', '${GA_TRACKING_ID}', {
              page_path: window.location.pathname,
            });
          `,
                }}
              />
            </>
          )}
        </Head>
        <body>
          <Main />
          <NextScript />
        </body>
      </Html>
    );
  }
}

  • Create /pages/_app.tsx:
import { AppProps } from "next/app";
import { useRouter } from "next/router";
import { useEffect } from "react";
import * as gtag from "../lib/gtag";
const isProduction = process.env.NODE_ENV === "production";

const App = ({ Component, pageProps }: AppProps): JSX.Element => {
  const router = useRouter();

  useEffect(() => {
    const handleRouteChange = (url: URL) => {
      /* invoke analytics function only for production */
      if (isProduction) gtag.pageview(url);
    };
    router.events.on("routeChangeComplete", handleRouteChange);
    return () => {
      router.events.off("routeChangeComplete", handleRouteChange);
    };
  }, [router.events]);
  // eslint-disable-next-line react/jsx-props-no-spreading
  return <Component {...pageProps} />;
};

export default App;

More info: https://gourav.io/blog/nextjs-cheatsheet

Solution 3

In your _document.js you override the getInitialProps method. You can also override the render method. Simply add

  render() {
    return (
      <Html lang={this.props.lang || "en"}>
        <Head>
          <script
            dangerouslySetInnerHTML={{
              __html: `[google analytics tracking code here]`
            }}
          />
        </Head>
        <body>
          <Main />
          <NextScript />
        </body>
      </Html>
    );
  }

Make sure you import the required components:

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

Solution 4

Do not use the top answer here: using the native <script> tag is forbidden and it should be defined outside of the <head> tag.

This is the proper way to include a script tag and configure up Google Analytics in NextJS:

import Script from 'next/script'
import Head from 'next/head'

export default function Index() {
  return (
    <>
      <Head>
        <title>Next.js</title>
      </Head>
      <Script
        src="https://www.googletagmanager.com/gtag/js?id=GA_MEASUREMENT_ID"
        strategy="afterInteractive"
      />
      <Script id="google-analytics" strategy="afterInteractive">
        {`
          window.dataLayer = window.dataLayer || [];
          function gtag(){window.dataLayer.push(arguments);}
          gtag('js', new Date());

          gtag('config', 'GA_MEASUREMENT_ID');
        `}
      </Script>
    </>
  )
}

For more info: https://nextjs.org/docs/messages/next-script-for-ga

Solution 5

This is the method recommended by next.js.

/components/GoogleAnalytics.jsx

import Script from 'next/script'
import { useEffect } from 'react'
import { useRouter } from 'next/router'

const GA_TRACKING_ID = '...'

export default () => {
  const router = useRouter()
  useEffect(() => {
    const handleRouteChange = url => {
      window.gtag('config', GA_TRACKING_ID, { page_path: url })
    }
    router.events.on('routeChangeComplete', handleRouteChange)
    return () => {
      router.events.off('routeChangeComplete', handleRouteChange)
    }
  }, [router.events])

  return (
    <>
      <Script
        strategy='afterInteractive'
        src={`https://www.googletagmanager.com/gtag/js?id=${GA_TRACKING_ID}`}
      />
      <Script
        id='gtag-init'
        strategy='afterInteractive'
        dangerouslySetInnerHTML={{
          __html: `
            window.dataLayer = window.dataLayer || [];
            function gtag(){dataLayer.push(arguments);}
            gtag('js', new Date());
            gtag('config', '${GA_TRACKING_ID}', {
              page_path: window.location.pathname,
            });
          `
        }}
      />
    </>
  )
}

/pages/_app.jsx

import GoogleAnalytics from './../components/GoogleAnalytics'

export default function App ({ Component, pageProps }) {
  return (
    <>
      <Component {...pageProps} />

      {
        process.env.NODE_ENV === 'production' &&
          <GoogleAnalytics />
      }
    </>
  )
}
Share:
56,828
0xsh
Author by

0xsh

Updated on February 19, 2022

Comments

  • 0xsh
    0xsh about 2 years

    I'm using styled-components with next.js so my styles need to be server-side rendered, hence how can I add google analytics to my website?

    I checked next.js google analytics example but as I said my _document file is different because of using styled-components.

    // _document.js
    
    import React from 'react'
    import Document from 'next/document'
    import { ServerStyleSheet } from 'styled-components'
    
    class MyDocument extends Document {
      static async getInitialProps(ctx) {
        const sheet = new ServerStyleSheet()
        const originalRenderPage = ctx.renderPage
    
        try {
          ctx.renderPage = () => originalRenderPage({
            enhanceApp: (App) => (props) => sheet.collectStyles(<App {...props} />),
          })
    
          const initialProps = await Document.getInitialProps(ctx)
          return {
            ...initialProps,
            styles: (
              <>
                {initialProps.styles}
                {sheet.getStyleElement()}
              </>
            ),
          }
        } finally {
          sheet.seal()
        }
      }
    }
    
    export default MyDocument
    
    
  • Cully
    Cully almost 4 years
    Does this register a new page visit on all navigation changes?
  • thisismydesign
    thisismydesign almost 4 years
    It does so on every page load. So as long as you're using Next's router, yes. For client-side navigation, you could look at this: stackoverflow.com/a/63249329/2771889
  • Cully
    Cully almost 4 years
    The nextjs router doesn't reload the page on navigation changes, though.
  • Cully
    Cully almost 4 years
    I just tested this out. Pageviews are not registered on navigation changes. This is because nextjs doesn't refresh the page on navigation changes, it just changes some props and lets React re-render accordingly.
  • thisismydesign
    thisismydesign almost 4 years
    I'm using next/link and it does register pageviews.
  • Cully
    Cully almost 4 years
    Do you have a "history change trigger" set up in Google Tag Manager? That would register pageviews on history changes.
  • 蔡佳峰
    蔡佳峰 over 3 years
    Thanks mate, I had same issue, your answer really helped!!
  • Matteo Frana
    Matteo Frana over 3 years
    Just one thing: the value should not be required in the GTagEvent type and not sent if not provided.
  • dortonway
    dortonway over 3 years
    Why is it recommended to do in _document.js, if the file is rendered on the back-end only?
  • Olumide
    Olumide over 3 years
    @MatteoFrana I don't seem to understand your comment above, can you shed more light? Thanks
  • Laurentiu Petrea
    Laurentiu Petrea about 3 years
    Kudos! Worked like a charm!
  • cbdeveloper
    cbdeveloper about 3 years
    I can confirm that this only fires once per browser refresh. No additional hits on subsequent page changes.
  • thisismydesign
    thisismydesign almost 3 years
    You're right. Updated my answer to track navigation using gtag.
  • Ciro Santilli OurBigBook.com
    Ciro Santilli OurBigBook.com over 2 years
    On Next.js 10.2.2, a page build with the navigation part gives Type error: Property 'gtag' does not exist on type 'Window & typeof globalThis'. for typescript _app.tsx instead of _app.js BTW. Gotta have a look at the Typescript specific answer: stackoverflow.com/a/65081431/895245
  • otto
    otto over 2 years
    I get the warning Use the `next/script` component for loading third party scripts
  • Dendi Handian
    Dendi Handian over 2 years
    everyone may want to check my answer of using next/script
  • P-A
    P-A over 2 years
    this is what I needed, thank you !
  • mbianchidev
    mbianchidev over 2 years
    I'd put the GA_TRACKING_ID in an .env file, having it in plain view doesn't seem a good idea to me
  • TJ Mazeika
    TJ Mazeika about 2 years
    It appears that the handleRouteChange and useEffect are no longer needed with the newest versions of GA. In fact, it appears that you'll get double the expected page_view events if you're manually listening to router events. The newer tracking IDs start with "GA-".
  • Ravi kumar
    Ravi kumar about 2 years
    Thanks you for the solution worked like a charm.
  • a paid nerd
    a paid nerd about 2 years
    If you're having TypeScript errors, try installing @types/gtag.js
  • Dan Zuzevich
    Dan Zuzevich about 2 years
    This results in double the page views
  • serraosays
    serraosays almost 2 years
    This doesn't use next/script and throws a warning in Next v12+
  • serraosays
    serraosays almost 2 years
    This is the right way to do this, thank you.
  • Jonathan Gruber
    Jonathan Gruber almost 2 years
    @TJMazeika I can confirm this and also think the hook is no longer necessary. In the Google Analytics Debugger in Chrome you can see an event being fired with { event: "gtm.historyChange-v2", gtm.historyChangeSource: "pushState", ... } on client side navigation. Adding the two <Script>s seems sufficient now.