130

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

17 Answers 17

213

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

19
  • 2
    Does this register a new page visit on all navigation changes?
    – Cully
    Commented Aug 7, 2020 at 21:51
  • 3
    The nextjs router doesn't reload the page on navigation changes, though.
    – Cully
    Commented Aug 8, 2020 at 3: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
    Commented Aug 9, 2020 at 1:37
  • 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
    Commented Feb 10, 2022 at 21:29
  • 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 <Script>s seems sufficient now. Commented Jun 12, 2022 at 14:56
60

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

7
  • 1
    Just one thing: the value should not be required in the GTagEvent type and not sent if not provided. Commented Dec 22, 2020 at 10:38
  • @MatteoFrana I don't seem to understand your comment above, can you shed more light? Thanks
    – Olumide
    Commented Jan 17, 2021 at 21:52
  • Kudos! Worked like a charm! Commented Mar 15, 2021 at 21:57
  • this is what I needed, thank you !
    – P-A
    Commented Jan 13, 2022 at 10:30
  • I'd put the GA_TRACKING_ID in an .env file, having it in plain view doesn't seem a good idea to me Commented Jan 20, 2022 at 9:33
36

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"

1
  • everyone may want to check my answer of using next/script Commented Jan 7, 2022 at 22:23
31

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

2
14

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 />
      }
    </>
  )
}
2
  • 3
    This results in double the page views Commented Apr 29, 2022 at 14:39
  • This will not work with Nextjs 14 as there are no router.events in Nextjs 14 Commented Dec 18, 2023 at 7:22
11

Next.js 14+

For Next.js 14+ it's pretty straightforward — you can use the @next/third-parties/google package to set up Google Analytics alone or through Google Tag Manager(GTM).

For app directory:

To load analytics for all routes, include the component in root layout:

import { GoogleAnalytics } from '@next/third-parties/google'
 
export default function RootLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return (
    <html lang="en">
      <body>{children}</body>
      <GoogleAnalytics gaId="G-XYZ" />
    </html>
  )
}

To load analytics for a single route, include the component in your page file:

import { GoogleAnalytics } from '@next/third-parties/google'
 
export default function Page() {
  return <GoogleAnalytics gaId="G-XYZ" />
}

For pages directory:

To load analytics for all routes, include the component in the _app file:

import { GoogleAnalytics } from '@next/third-parties/google'
 
export default function MyApp({ Component, pageProps }) {
  return (
    <>
      <Component {...pageProps} />
      <GoogleAnalytics gaId="G-XYZ" />
    </>
  )
}

To load analytics for a single route, include the component in your page file:

import { GoogleAnalytics } from '@next/third-parties/google'
 
export default function Page() {
  return <GoogleAnalytics gaId="G-XYZ" />
}

For sending custom events

You can use either sendGAEvent(if configured through <GoogleAnalytics />):

import { sendGAEvent } from '@next/third-parties/google'
 
export function EventButton() {
  return (
    <div>
      <button
        onClick={() => sendGAEvent('event', 'buttonClicked', { value: 'xyz' })}
      >
        Send Event
      </button>
    </div>
  )
}

Or through sendGTMEvent(if configured through <GoogleTagManager />):

import { sendGTMEvent } from '@next/third-parties/google'
 
export function EventButton() {
  return (
    <div>
      <button
        onClick={() => sendGTMEvent({ event: 'buttonClicked', value: 'xyz' })}
      >
        Send Event
      </button>
    </div>
  )
}
7

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>
5

Nextjs 14

You can use Google Tag Manager and Google Analytics from nextjs third party libraries.

Google Tag Manager

import { GoogleTagManager } from '@next/third-parties/google'
 
export default function MyApp({ Component, pageProps }) {
  return (
    <>
      <Component {...pageProps} />
      <GoogleTagManager gtmId="GTM-XYZ" />
    </>
  )
}

Google Analytics

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

export default function MyApp({ Component, pageProps }) {
  return (
    <>
      <Component {...pageProps} />
      <GoogleAnalytics gaId="G-XYZ" />
    </>
  )
}
4

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.

3

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>
   )
}
3

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

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

1

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

1

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!!

1

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.

1

Create new _document.tsx in your project via: pages/_document.tsx

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

export default function Document() {
  return (
    <Html lang='en'>
      <Head>
        <meta name='viewport' content='width=device-width, initial-scale=1' />
        <Script
          src='https://www.googletagmanager.com/gtag/js?id=[YOUR_GA_ID]'
          async
          crossOrigin='anonymous'
          strategy='lazyOnload'
          id='google-tag-manager'
        />
      </Head>
      <body>
        <Main />
        <NextScript />

        <Script id='google-analytics' async strategy='lazyOnload' crossOrigin='anonymous'>
          {`
          window.dataLayer = window.dataLayer || [];
          function gtag(){dataLayer.push(arguments);}
          gtag('js', new Date());
 
          gtag('config', '[YOUR_GA_ID]');
        `}
        </Script>
      </body>
    </Html>
  );
}
0

It can be helpful for someone: I used Script tag in layout.tsx Before using this tag you need to make import

2
  • Images of code should not be used, it's better if the code is actually posted.
    – quaabaam
    Commented Feb 16, 2024 at 18:59
  • While this link may answer the question, it is better to include the essential parts of the answer here and provide the link for reference. Link-only answers can become invalid if the linked page changes. - From Review
    – Stephan T.
    Commented Feb 19, 2024 at 13:59

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.