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 β οΈ
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?
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
----> Web
- Add your website url
- Add your website name
- Click Create Stream button
- Copy the Measurement ID
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!!!
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:
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 keypath_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.
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.
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
Top comments (4)
Nice article, thank you so much! π©
Do you have some advices on adding GDPR consent and load this script conditionally?
Would be interested in a function for cookie consent as well!
In the end, I used react-cookie-consent and made a simple check for Script render.
Thanks