DEV Community

Marco Valsecchi
Marco Valsecchi

Posted on • Updated on

How to setup Google Tag Manager in a Next 13 App Router website

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">
        <Analytics />
Enter fullscreen mode Exit fullscreen mode

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) {
  }, [pathname, searchParams])

  if (process.env.NEXT_PUBLIC_VERCEL_ENV !== "production") {
    return null

  return (
          style={{ display: "none", visibility: "hidden" }}
          __html: `
    new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],
    })(window,document,'script','dataLayer', '${GTM_ID}');
Enter fullscreen mode Exit fullscreen mode

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

And that's all... now you're ready to test and publish your GTM configuration and collect all your user views ;)

Top comments (7)

ux_bob profile image
Bobby Smith

I continue to get an error at window.dataLayer.push (cannot read properties of undefined). For some reason, with this setup, dataLayer is not a property of window by the time the useEffect runs. You run into this problem?

ux_bob profile image
Bobby Smith

ah! only getting the error in development because of process.env.NEXT_PUBLIC_VERCEL_ENV !== 'production' not set so returns null, but useEffect still runs and tries to fire pageview(pathname)

any way, besides checking for window.dataLayer in lib/gtm, to get around that?

diazsasak profile image
Diaz Guntur Febrian

you can remove the checking on Analytics component and add the checking on pageView() method instead. so it not break the rule of hook.

export const GTM_ID = process.env.NEXT_PUBLIC_GOOGLE_TAG_MANAGER_ID;

export const pageview = (url: string) => {
if (process.env.NEXT_PUBLIC_VERCEL_ENV !== "production") {

// @ts-ignore
event: "pageview",
page: url,

volterra profile image
Steve Doig • Edited

FTR, using this lib/gtm.js meant I received an error in VS Code / analytics.tsx: "lib/gtm"' has no exported member named 'gtmId'. Did you mean 'GTM_ID'? ; replacing gtmId with GTM_ID resolved that error in analytics.tsx and GTM now loads nicely. Cheers.

valse profile image
Marco Valsecchi

Hi, thanks I updated the post using the original GTM_ID variable.

bryanjhickey profile image
Bryan Hickey

Thanks for the article. Very clear and well-written.

You've referenced lib/gtm in Analytics.tsx. How do you deal with pageview in that file? Would be great to get some insight into that part of your solution.


valse profile image
Marco Valsecchi

Hi, thank you!
You can find it on the current with-google-tag-manager example ๐Ÿ˜‰