How to add Google Analytics to Next.js using the Script Component (🙏 Stop using inline script)

How to add Google Analytics to Next.js using the Script Component (🙏 Stop using inline script)

If you are going to use Next.js for your future project, or you've already deployed a Next.js web app, please be sure that the Google Analytics script is not added via an inline script ⚠️

please gif

It's widely known that the website loading performance hugely impacts on User Experience and, since the latest Google Algorithm updates, on SEO (Search Engine Optimization).

We can literally make our website fall down in Google SERPs rankings if we don't follow the best practices in loading strategy of third-party scripts. Going deep into the complex world of third-party scripts is out of the scope of this guide, if you want to read more about it I suggest this amazing article..

So now you may be wondering: Which is the "right way" to add Google Analytics script to a Next.js website?

That is an excellent question question gif

The answer comes directly from Vercel

If you are using the gtag.js script to add analytics, use the 'next/script' component with the right loading strategy to defer loading of the script until necessary.

Thanks to the Next.js Script Component we can choose the right load strategy and defer the load of the Google Analytics script until necessary, so it will no longer be able to render-block and delay any page content from loading. 🥳

Globally Inject Google Analytics Script in Next.js

Navigate to Google Analytics website and grab the Measurement ID. If you don't have one, all you have to do is to create a new Data Stream.

-----> Admin ----> Data Streams

data streams.png

----> Web

choose web.png

  • Add your website url
  • Add your website name
  • Click Create Stream button

create stream.png

  • Copy the Measurement ID

measurement id and script.png

Let's open now the Next.js project into Visual Studio Code and create (if you don't already have one) a .env.local file (at root level) so we can set our Measurement ID inside an Environment Variable

NEXT_PUBLIC_MEASUREMENT_ID: 'xxxxxx Your measurement id xxxxxx'

NEXT_PUBLIC prefix is needed to let our Next.js app know that this env variable has to be exposed to the browser.

Now that we have our Measurement ID set up inside and env variable, we can globally inject the Google Analytics script code via the Next.js Script Component and define in it our loading strategy.

Open the pages/_app.js file type/paste 🤫 the code below

import '../styles/globals.css'
import Script from 'next/script'

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

          gtag('config', '${process.env.NEXT_PUBLIC_MEASUREMENT_ID}');
        `}
      </Script>
      <Component {...pageProps} />
    </>
  ) 
}

export default MyApp

As you can see the two Script Components handle the script code taken from the Google Analytics website, as shown in the image above, inject it globally (since the MyApp component exported from the _app.js wraps the entire app) and load it in base of the value passed inside the strategy prop.

The strategy prop accepts three possible loading strategies:

  • beforeInteractive
  • afterInteractive
  • lazyOnload

afterInteractive, the loading strategy we've chosen, is the one suggested by Next.js to handle the Google Analytics script. What it does is to:

Load immediately after the page becomes interactive

The id prop we pass inside the second Script Component is a unique arbitrary attribute we are obliged to set in order to allow Next.js track and optimize, under the hood, all the scripts it needs to handle and load. Imagine a common scenario in which you have multiple third-party scripts loaded by multiple Script Components (for ex. Ads, Social Media Widget, Cookie Consent Banner etc etc), having these unique ids for each of them will let Next.js "organize its work" to handle them in the best and fastest way (just like the unique key prop we need to pass for each <li> tag inside a list).

Hei hei hei!!! Where are you going now?!?! We are not done yet!!!

not done yet gif

Add a custom function to track pageviews

Now that the Google Analytics script is globally injected we need to define a custom function to track pageviews.

It’s a common practice to put all the third-party library related code into a separate folder. So, always starting at the root level of our project, let's create this folders structure:

folder structure.png

Inside index.js we define and export an anonymous function named pageview, that takes an url as parameter. Inside this function we call the gtag method on the window object, and pass to gtag three arguments:

  • 'config'
  • our measurement id via env variable
  • an options object in which we assign the value url to the key path_url
export const pageview = (url) => {
    window.gtag('config', process.env.NEXT_PUBLIC_MEASUREMENT_ID, {
        path_url: url,
    })
}

Subscribe and listen to Next.js Router routeChangeComplete event

We still have one problem to face to let Google Analytics know how to track pageviews when someone on our website is browsing between different pages/routes.

we can fix this gif

Time to go back to pages/_app.js and make the magic happen.

import '../styles/globals.css'
import {useEffect} from 'react'
import {useRouter} from 'next/router'
import Script from 'next/script'

import * as ga from '../lib/google-analytics'

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

  useEffect(() => {
    const handleRouteChange = (url) => {
      ga.pageview(url)
    }

    router.events.on('routeChangeComplete', handleRouteChange)
    return () => {
      router.events.off('routeChangeComplete', handleRouteChange)
    }
  }, [router.events])
  return(
    <>
      <Script src={`https://www.googletagmanager.com/gtag/js?id=${process.env.NEXT_PUBLIC_MEASUREMENT_ID}`} strategy='afterInteractive' />
      <Script id="google-analytics" strategy='afterInteractive'>
        {`
          window.dataLayer = window.dataLayer || [];
          function gtag(){dataLayer.push(arguments);}
          gtag('js', new Date());

          gtag('config', '${process.env.NEXT_PUBLIC_MEASUREMENT_ID}');
        `}
      </Script>
      <Component {...pageProps} />
    </>
  ) 
}

export default MyApp

Thanks to the useRouter hook we can use the router object inside the _app.js and subscribe to the Next.js Router routeChangeComplete event. This event fires when a route change process is completed (as the name suggests 😅).

We can subscribe to it calling the on method on router.events and, passing the pageview custom function (previously created), we let Google Analytics know how track pageviews on pages/routes changes.

The subscription to this event is done inside a useEffect hook that fires at any router.events change. As you can see router.events is the second argument passed to useEffect.

To avoid any memory leak we return from useEffect a clean up function in which we unsubscribe from routeChangeComplete event calling the off method on router.events.

The Google Analytics script is now successfully integrated into our Next.js app!! 🥳

GTmetrix analysis

Time for a little test!

Try to analyze your Next.js app with a free tool like GTmetrix to check if what we have done so far works properly.

In the waterfall section of Gtmetrix (selecting the JS tab) you can easily see that the Google Analytics script is loaded only after all the scripts that the web app needs to execute first to be interactive.

gtmetrix.png

So it is clear how the Google Analytics script is not render-blocking and delaying any page content from loading.

How to add Google Analytics to Next.js - YouTube Video from my YT Channel

Feel free to check out my Youtube Channel:

p.s: I hope this guide could be useful to you. For any suggestion please comment in the section down below.

Thank you for reading. From the bottom of my heart. theItalianDev