Recently I finished a brand new Next.js 13 website using the latest App Router solution (I know that is not production ready but I love to learn new things by real projects) and before to go live I setup a new Google Tag Manager with all the needed tags... but how to add it to the new app directory?
Inspired by the new @vercel/analytics react component, I've added one in my layout.tsx
root component called Analytics
:
// layout.tsx
<html lang="it">
<body>
<Suspense>
<Analytics />
</Suspense>
...
</body>
I wrapped it in a Suspense
boundary to avoid the "deopted into client-side rendering" error for my static pages.
And this is its content:
// Analytics.tsx
"use client"
import { GTM_ID, pageview } from "lib/gtm"
import { usePathname, useSearchParams } from "next/navigation"
import Script from "next/script"
import { useEffect } from "react"
export default function Analytics() {
const pathname = usePathname()
const searchParams = useSearchParams()
useEffect(() => {
if (pathname) {
pageview(pathname)
}
}, [pathname, searchParams])
if (process.env.NEXT_PUBLIC_VERCEL_ENV !== "production") {
return null
}
return (
<>
<noscript>
<iframe
src={`https://www.googletagmanager.com/ns.html?id=${GTM_ID}`}
height="0"
width="0"
style={{ display: "none", visibility: "hidden" }}
/>
</noscript>
<Script
id="gtm-script"
strategy="afterInteractive"
dangerouslySetInnerHTML={{
__html: `
(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':
new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],
j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);
})(window,document,'script','dataLayer', '${GTM_ID}');
`,
}}
/>
</>
)
}
The idea is similar to the with-google-tag-manager example with the pages
solution.
Instead of using both the custom _document.tsx
and the _app.tsx
files I added all the configuration in our client
component and thanks to the native Script
tag, GTM is visible everywhere but loaded once after the required scripts.
The next views are triggered on page change event and in the new approach this is achieved monitoring the current pathname
(and the searchParams
too if needed).
This is my TypeScript version of the original lib/gtm
library:
// lib/gtm.ts
type WindowWithDataLayer = Window & {
dataLayer: Record<string, any>[]
}
declare const window: WindowWithDataLayer
export const GTM_ID = process.env.NEXT_PUBLIC_GTM
export const pageview = (url: string) => {
if (typeof window.dataLayer !== "undefined") {
window.dataLayer.push({
event: "pageview",
page: url,
})
} else {
console.log({
event: "pageview",
page: url,
})
}
}
I just added a check for the dataLayer
variable so you can use it during the development too, logging the event in the console for example.
And that's all... now you're ready to test and publish your GTM configuration and collect all your user views ;)
Latest comments (28)
This is working very well so far. Thank you!
But why isn't there a package in NextJS/Vercel?
I think there is a problem here when you wrap the Analytics component inside Suspense, the noscript tag will never be executed because when javascript is disabled, the Analytic component also does not execute
Having a problem where the pageview method is fired twice on each reload of the page. Anyone else experiencing the same? Might be something with my setup though 🤔
Ah.. Seems like that was caused by React Strict mode on localhost. This suggestion fixed the issue locally:
Thanks for a great post!
Really awesome Marcelo! Thanks for sharing this!
Thanks so much for the article!
Wrapping
<Analytics />
with the<Suspense />
boundary got SSR working for my site.By any chance, do you know what the reason for this is?
This was super helpful! Just want to add some help for the next explorer. The offical GTM JS location has changed. So in Analytics.tsx you'll want to update to:
https://www.googletagmanager.com/gtag/js?
you are such a life saver man, thank you.
I appreciate it
We've just published a package that does exactly that with a few lines of code, check it out here: github.com/XD2Sketch/next-google-t...
Hi @kgoedecke - i'm having issues on my next.js project...getting the following error message:
ReferenceError: $ is not defined
Followed by Uncaught TypeError: Cannot read properties of undefined (reading 'childNodes')
Any ideas on how to resolve this when using your library?
This does not work and is not picked up with GTM Tag assistant as the code needs to be in the head of the site, has anyone found a solution yet?
Hi, I don't know why this isn't working for you but the GTM Tag is placed correctly on the head of the page and also the Tag Assistant recognize it; this is because using the Script tag by Next.js put the script in the right place.
Hi, I have the same problem. After debugging I found out that it has to do with "use client". Without "use client" google tag manager has no problem finding the tag, unfortunately this is not the case when "use client" is added to make the useEffect work. Does anyone have a solution for this?
I'm getting
VM22299:2 Uncaught TypeError: Cannot read properties of null (reading 'parentNode')
error with this approach, have any of you encountered that?Hi, I added my version of
gtm
lib file; try to use it instead of the original one because I added a missing check for thedataLayer
variable. Let me know if it helps you!Thanks but I'm not even using this file, the error I get is throw during script initialisation. The issue is probably there -
f.parentNode.insertBefore(j,f)
, it seems like thef
variable isnull
(var f=d.getElementsByTagName(s)[0]
- looks like it's a firstscript
tag). Interestingly, the GTM works even with that error. Maybe it's being called more than once and the error occurs only for the first time?