How to use google analytics with next.js app?

JavascriptReactjsGoogle Analyticsnext.jsStyled Components

Javascript Problem Overview


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

Javascript Solutions


Solution 1 - Javascript

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

import { 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 - Javascript

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

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

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

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

Solution 6 - Javascript

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>

Solution 7 - Javascript

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.

Solution 8 - Javascript

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

Solution 9 - Javascript

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

Solution 10 - Javascript

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

Solution 11 - Javascript

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

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
Question0xshView Question on Stackoverflow
Solution 1 - JavascriptthisismydesignView Answer on Stackoverflow
Solution 2 - JavascriptGorvGoylView Answer on Stackoverflow
Solution 3 - JavascriptJørgenView Answer on Stackoverflow
Solution 4 - JavascriptBen YeeView Answer on Stackoverflow
Solution 5 - JavascriptozgrozerView Answer on Stackoverflow
Solution 6 - JavascriptKienView Answer on Stackoverflow
Solution 7 - JavascriptKretissView Answer on Stackoverflow
Solution 8 - JavascriptDendi HandianView Answer on Stackoverflow
Solution 9 - JavascriptcbdeveloperView Answer on Stackoverflow
Solution 10 - JavascriptAliView Answer on Stackoverflow
Solution 11 - JavascriptPalash ShrivastavaView Answer on Stackoverflow