112

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

Molly
  • 1,887
  • 3
  • 17
  • 34
0xsh
  • 1,395
  • 3
  • 10
  • 18

13 Answers13

197

Next.js since v11 recommends using their <Script> tag, and the right place to add it is the App component.

pages/_app.jsx

import React from 'react';
import Script from 'next/script';

const App = ({ Component, pageProps }) => {
  return (
    <>
      <Script
        src="https://www.googletagmanager.com/gtag/js?id=G-xxxxxxxxxx"
        strategy="afterInteractive"
      />
      <Script id="google-analytics" strategy="afterInteractive">
        {`
          window.dataLayer = window.dataLayer || [];
          function gtag(){window.dataLayer.push(arguments);}
          gtag('js', new Date());

          gtag('config', 'G-xxxxxxxxxx');
        `}
      </Script>

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

export default App;

You can see this solution working in nestjs-starter where I'm also setting the tag from an env var.

For v10 and lower use regular <script> tags according to Google's guide.

Keep in mind that Google Analytics does automatic page tracking, but this will not work for every use case. For example, hash and search parameter changes are not tracked. This can lead to a lot of confusion. For example, when using HashRouter or anchor links the navigation will not be tracked. To have full control over page view tracking you can disable automatic tracking. See for a detailed explanation: The Ultimate Guide to Google Analytics (UA & GA4) on React (Or Anything Else

Manual page tracking: https://stackoverflow.com/a/63249329/2771889

thisismydesign
  • 21,553
  • 9
  • 123
  • 126
  • 2
    Does this register a new page visit on all navigation changes? – Cully Aug 07 '20 at 21:51
  • 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: https://stackoverflow.com/a/63249329/2771889 – thisismydesign Aug 07 '20 at 22:37
  • 3
    The nextjs router doesn't reload the page on navigation changes, though. – Cully Aug 08 '20 at 03:11
  • 6
    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. – Cully Aug 09 '20 at 01:37
  • I'm using `next/link` and it does register pageviews. – thisismydesign Aug 09 '20 at 17:50
  • Do you have a "history change trigger" set up in Google Tag Manager? That would register pageviews on history changes. – Cully Aug 10 '20 at 05:36
  • Thanks mate, I had same issue, your answer really helped!! – 蔡佳峰 Aug 15 '20 at 04:35
  • Why is it recommended to do in _document.js, if the file is rendered on the back-end only? – dortonway Dec 26 '20 at 21:52
  • I can confirm that this only fires once per browser refresh. No additional hits on subsequent page changes. – cbdeveloper May 04 '21 at 16:19
  • You're right. Updated my answer to track navigation using `gtag`. – thisismydesign May 24 '21 at 20:00
  • 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: https://stackoverflow.com/a/65081431/895245 – Ciro Santilli OurBigBook.com Aug 26 '21 at 11:02
  • I get the warning ```Use the `next/script` component for loading third party scripts``` – otto Dec 30 '21 at 15:48
  • 7
    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-". – TJ Mazeika Feb 10 '22 at 21:29
  • If you're having TypeScript errors, try installing `@types/gtag.js` – a paid nerd Apr 19 '22 at 02:10
  • This doesn't use `next/script` and throws a warning in Next v12+ – serraosays Jun 11 '22 at 19:11
  • 1
    @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 ` – Jonathan Gruber Jun 12 '22 at 14:56
  • Strange, it raise me an error: https://stackoverflow.com/questions/74064377/why-typeerror-window-gtag-is-not-a-function – János Oct 14 '22 at 12:16
54

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

GorvGoyl
  • 42,508
  • 29
  • 229
  • 225
33

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"

0xsh
  • 1,395
  • 3
  • 10
  • 18
Jørgen
  • 8,820
  • 9
  • 47
  • 67
30

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

Jason R Stevens CFA
  • 2,232
  • 1
  • 22
  • 28
Ben Yee
  • 1,547
  • 13
  • 19
12

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 />
      }
    </>
  )
}
ozgrozer
  • 1,824
  • 1
  • 23
  • 35
5

Another way that worked well to me without dangerouslySetInnerHTML:

  • Create a new js file in the public folder with the contents in dangerouslySetInnerHTML.
  • Add that created js file to the _document.js file.

The sample for what my _document.js returned:

<Html>
  <Head>
    <script async src="https://www.googletagmanager.com/gtag/js?id=UA-xxx-x"></script>
    <script src="/ga.js" async></script>
    {/*other scripts*/}
  </Head>
  <body>
    <Main />
      <NextScript />
  </body>
</Html>
Kien
  • 133
  • 1
  • 7
3

It is little differend question, but I found quick and easy solution on this page: https://www.learnbestcoding.com/post/9/easiest-way-to-integrate-google-analytics-with-react-js-and-next-js. You can use App component instead of custom Document. All you need to do is install react-gtm-module and then add useEffect to your App component. Final page can look like this:

import '../styles/globals.css';
import Layout from "../components/Layout";
import Head from "next/head";
import {useEffect} from "react";
import TagManager from "react-gtm-module";

function MyApp({ Component, pageProps }) {

    useEffect(() => {
        TagManager.initialize({ gtmId: 'GTM-XXXXX' });
    }, []);

    return(
      <Layout>
          <Head>
             ...
          </Head>

        <Component {...pageProps} />
      </Layout>
    )
}

export default MyApp

In GTM-XXXXX will be your generated ID from Google Tag Manager. When you are done with GTM, then just connect it with your Google Analytics with your tag.

Kretiss
  • 657
  • 4
  • 15
2

I have created a React component that I normally just copy/paste to integrate Google analytics into a new project. Full process takes around 2 minutes. Just follow these 2 steps:

Step 1

Create a React component named GoogleAnalytics.jsx or GoogleAnalytics.tsx with the following content. Just replace the GA_TRACKING_ID variable with your Google analytics tracking ID.

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

// **Important** Replace this tracking ID by your Analytics code
// Or you can put it into the environment file.
const GA_TRACKING_ID = 'G-XXXXXXXXX';

// @ts-ignore
const addPageView = (url) => {
  // @ts-ignore
  window.gtag('config', GA_TRACKING_ID, {
    page_path: url,
  });
};

const GoogleAnalytics = () => {
  const router = useRouter();

  useEffect(() => {
    // @ts-ignore
    const handleRouteChange = (url) => {
      addPageView(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,
      });
    `,
        }}
      />
    </>
  );
};

export default GoogleAnalytics;

Step 2

Then just inject this component in your _app.js or _app.ts . No need to touch anything else and you are done

import '../styles/globals.css'
import GoogleAnalytics from 'path-of-the-GoogleAnalytics-component'

function MyApp({ Component, pageProps }) {
  return (
    <>
      <GoogleAnalytics />
      <Component {...pageProps} />
    </>
  );
}

export default MyApp;

Why I think this is better solution for this problem?

  1. All Google analytics integration logic goes to a Separate component. So, less code in _app.js & also less modification required.
  2. This works for all pages even though you just add it only _app.js file. Because we are listening on routeChangeComplete so this triggers on every page chages.

Notes:

  • You can use this same component in all your Next.js project.
  • I have tested it with Next.js version 12 & 13.
  • If you are curious how analytics is send on page change please check this document. Just check the last section.
1

refer to this documentation: https://nextjs.org/docs/api-reference/next/script .
in your pages/_app.js:

import Script from 'next/script'

...

function MyApp({Component, pageProps}) {
   return (
      <div>
         ...

         <Script
           id="google-analytics"
           src="https://www.googletagmanager.com/gtag/js?id=YOUR-ID"
           onLoad={() => {
             window.dataLayer = window.dataLayer || [];
             function gtag(){dataLayer.push(arguments);}
             gtag('js', new Date());
             gtag('config', 'YOUR-ID');
           }}
         />
      </div>
   )
}
Dendi Handian
  • 348
  • 2
  • 12
0

The selected answer only fires once per full refresh of the browser. It does not fire for subsequent internal route changes using Link from "next/link". For example:

  • User enters www.yourdomain.com/page_1 in his browser and hits enter (or maybe he clicked on your website result on Google).
  • That code will fire and send a page view for /page_1
  • User now clicks on a internal <Link> from "next/link" to navigate to another page, /page_2
  • That code will not fire fore /page_2. It will only re-fire on a full refresh, like when you refresh the browser by hitting F5.

This might be ok for some cases. But I think most people would like it to fire once every page change.

Here is what I'm using to fire on every pathname change.

_app.tsx

const App: React.FC<CustomAppProps> = ({ Component, pageProps }) => {

  useLogPageView();
  
  return (
    <>
      <Layout>                       // Layout includes Header Main and Footer for my pages
        <Component {...pageProps}/>  // Component is rendered inside Main
      </Layout>
    </>
  );
};

export default App;

useLogPageView.ts

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

export const useLogPageView = () : void => {

  const router = useRouter();
  const { pathname, asPath } = router;

  // IF YOU ARE USING DYNAMIC ROUTES LIKE /posts/[slug]
  // THEN YOU SHOULD USE asPath INSTEAD OF pathname

  // THIS EFFECT WILL RUN ON EVERY asPath CHANGE
  useEffect(() => {
    gtag('config', '${GA_TRACKING_ID}', {    // DON'T ADD THIS TO _document.tsx
      page_path: window.location.pathname,   // OTHERWISE YOU'LL GET DUPLICATE HITS
    });                                      // ON FIRST PAGE LOAD
  },[asPath]);

};

Checkout https://nextjs.org/docs/api-reference/next/router#router-object

enter image description here

enter image description here

cbdeveloper
  • 27,898
  • 37
  • 155
  • 336
0

You need first to get your google analytics id through google then create _document.js in pages folder if it is not created yet and copy this code

_document.js

import Document, { Html, Head, Main, NextScript } from "next/document";
import { G_TAG } from "../lib/constants";

export default class MyDocument extends Document {
  render() {
    const url = "https://www.googletagmanager.com/gtag/js?id=" + `${G_TAG}`;
    return (
      <Html lang="en">
        <Head>
          <script async src={`${url}`}></script>
          <script
            dangerouslySetInnerHTML={{
              __html: `
            window.dataLayer = window.dataLayer || [];
            function gtag(){dataLayer.push(arguments);}
            gtag('js', new Date());
            gtag('config', '${G_TAG}', {
              page_path: window.location.pathname,
            });
          `,
            }}
          />
        </Head>
        <body>
          <Main />
          <NextScript />
        </body>
      </Html>
    );
  }
}

you need then to define your G_TAG depending on environment like this:

constants.js

export const G_TAG = {
  development: "dev-mode",
  production: "YOUR-MEASUREMENT-ID-FROM-GANALYTICS",
}[process.env.NODE_ENV];
you can check step by step how to create your google analytics ID and set it up with Next.js in: https://learnjsx.com/category/4/posts/nextjs-ganalytics
Ali
  • 118
  • 1
  • 3
0

This Answer from a GitHub issue helped me

With React hooks :

_app.js

import { useEffect } from 'react'
import { useRouter } from 'next/router'
import ReactGA from 'react-ga'
import Layout from '../components/Layout'

function MyApp ({ Component, pageProps }) {
  const router = useRouter()

  useEffect(() => {
    const handleRouteChange = (url, { shallow }) => {
      ReactGA.set({ page: url })
      ReactGA.pageview(url)
    }
    ReactGA.initialize('XX-XXXXXXXXX-X', { debug: false })
    ReactGA.set({ page: router.pathname })
    ReactGA.pageview(router.pathname)
    router.events.on('routeChangeComplete', handleRouteChange)
    return () => {
      router.events.off('routeChangeComplete', handleRouteChange)
    }
  }, [])

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

export default MyApp

Credits to @RiusmaX. Cheers!!

0

Steps to use with JavaScript:

  • /.env.local
process.env.NEXT_PUBLIC_GOOGLE_ANALYTICS=G-FEWE...
  • /pages/_app.js
import '../styles/globals.css'
import Script from 'next/script'

function MyApp({ Component, pageProps }) {
  return (
    <>
      <Script strategy="lazyOnload" src={`https://www.googletagmanager.com/gtag/js?id=${process.env.NEXT_PUBLIC_GOOGLE_ANALYTICS}`} />

      <Script id="google-analytics-script" strategy="lazyOnload">
        {`
                    window.dataLayer = window.dataLayer || [];
                    function gtag(){dataLayer.push(arguments);}
                    gtag('js', new Date());
                    gtag('config', '${process.env.NEXT_PUBLIC_GOOGLE_ANALYTICS}', {
                    page_path: window.location.pathname,
                    });
                `}
      </Script>

      <Component {...pageProps} />
    </>
  )
}

export default MyApp

Steps to use with TypeScript:

  • /.env.local
process.env.NEXT_PUBLIC_GOOGLE_ANALYTICS=G-FEWE...
  • /pages/_app.js
import '../styles/globals.css'
import type { AppProps } from 'next/app'
import Script from 'next/script'

function MyApp({ Component, pageProps }: AppProps) {
  return (
    <>
      <Script strategy="lazyOnload" src={`https://www.googletagmanager.com/gtag/js?id=${process.env.NEXT_PUBLIC_GOOGLE_ANALYTICS}`} />

      <Script id="google-analytics-script" strategy="lazyOnload">
        {`
                    window.dataLayer = window.dataLayer || [];
                    function gtag(){dataLayer.push(arguments);}
                    gtag('js', new Date());
                    gtag('config', '${process.env.NEXT_PUBLIC_GOOGLE_ANALYTICS}', {
                    page_path: window.location.pathname,
                    });
                `}
      </Script>

      <Component {...pageProps} />
    </>
  )
}

export default MyApp

It's how I use my next.js apps.

Flavio Oliveira
  • 381
  • 4
  • 12