·5 min read

Install Google Analytics 4 on Next.js: App Router and Pages Router

How to install GA4 on Next.js correctly: @next/third-parties, manual gtag.js, GTM. Guide for App Router (Next.js 13+) and Pages Router.

google analytics 4 nextjsga4 nextjs app routerinstall ga4 nextjsgoogle analytics nextjs 14

By Matheo Zimmer

Installing GA4 on Next.js has a few specifics compared to a classic site: hybrid rendering (SSR/SSG), client-side navigation without page reload (SPA), and performance best practices. Here's the recommended method for each configuration.


Since Next.js 14, the official @next/third-parties package offers an optimised GoogleAnalytics component: deferred loading, no render blocking, compatible with Server Components.

Install

npm install @next/third-parties

Integrate in the root layout

// src/app/layout.tsx
import { GoogleAnalytics } from '@next/third-parties/google'

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en">
      <body>
        {children}
        <GoogleAnalytics gaId="G-XXXXXXXXXX" />
      </body>
    </html>
  )
}

Replace G-XXXXXXXXXX with your GA4 Measurement ID.

Pros:

  • Optimised loading via automatic Strategy.afterInteractive
  • Respects Core Web Vitals best practices (no blocking script)
  • Official Next.js, maintained by Vercel
  • Compatible with Server Components

Method 2: manual gtag.js with the Script component

If you don't want an extra dependency or if you need precise control over events:

// src/app/layout.tsx
import Script from 'next/script'

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en">
      <head>
        <Script
          src="https://www.googletagmanager.com/gtag/js?id=G-XXXXXXXXXX"
          strategy="afterInteractive"
        />
        <Script id="google-analytics" strategy="afterInteractive">
          {`
            window.dataLayer = window.dataLayer || [];
            function gtag(){dataLayer.push(arguments);}
            gtag('js', new Date());
            gtag('config', 'G-XXXXXXXXXX');
          `}
        </Script>
      </head>
      <body>{children}</body>
    </html>
  )
}

Important: always use strategy="afterInteractive" and not strategy="beforeInteractive". The GA4 script is not critical for the initial render and shouldn't block page display.


Method 3: Pages Router (Next.js 12 and earlier)

For projects on the old Pages Router, the tag is added in _app.tsx or _document.tsx:

// pages/_app.tsx
import Script from 'next/script'
import type { AppProps } from 'next/app'

export default function App({ Component, pageProps }: AppProps) {
  return (
    <>
      <Script
        src="https://www.googletagmanager.com/gtag/js?id=G-XXXXXXXXXX"
        strategy="afterInteractive"
      />
      <Script id="ga4-init" strategy="afterInteractive">
        {`
          window.dataLayer = window.dataLayer || [];
          function gtag(){dataLayer.push(arguments);}
          gtag('js', new Date());
          gtag('config', 'G-XXXXXXXXXX');
        `}
      </Script>
      <Component {...pageProps} />
    </>
  )
}

If your site is subject to GDPR (which is the case for any EU-facing site), the GA4 tag should only load after user consent.

// src/app/layout.tsx
'use client'
import { GoogleAnalytics } from '@next/third-parties/google'
import { useConsent } from '@/hooks/useConsent' // your consent hook

export function AnalyticsProvider() {
  const { analyticsConsented } = useConsent()

  if (!analyticsConsented) return null

  return <GoogleAnalytics gaId="G-XXXXXXXXXX" />
}

GA4 supports Consent Mode v2: you can load the tag without data before consent, and enable full tracking after agreement.

<Script id="ga4-consent-init" strategy="beforeInteractive">
  {`
    window.dataLayer = window.dataLayer || [];
    function gtag(){dataLayer.push(arguments);}
    gtag('consent', 'default', {
      analytics_storage: 'denied',
      ad_storage: 'denied'
    });
    gtag('js', new Date());
    gtag('config', 'G-XXXXXXXXXX');
  `}
</Script>

After user consent, update the status:

gtag('consent', 'update', {
  analytics_storage: 'granted'
})

Tracking page changes (SPA)

Next.js is a Single Page Application: navigations between pages don't reload the document, so GA4 doesn't automatically detect page changes.

Good news: since GA4, page-view tracking in SPAs is handled automatically via the History API. You normally don't need extra code to track Next.js navigations.

If you see duplicates or missing pages in GA4, check you don't have two active GA4 tag instances (one via @next/third-parties and another via GTM or another script).


Sending custom events

// Example: track a CTA click
'use client'

function CTAButton() {
  const handleClick = () => {
    if (typeof window !== 'undefined' && window.gtag) {
      window.gtag('event', 'cta_click', {
        event_category: 'engagement',
        event_label: 'hero_button',
      })
    }
  }

  return <button onClick={handleClick}>Get started</button>
}

Declare the gtag type in your TypeScript declaration file if needed:

// src/types/gtag.d.ts
declare function gtag(...args: unknown[]): void
declare interface Window {
  gtag: typeof gtag
  dataLayer: unknown[]
}

Verification

  1. Run npm run dev
  2. Open http://localhost:3000 in a private window
  3. In GA4 → Reports → Realtime: you should appear as a visitor

If data doesn't come in locally, it's often due to:

  • An ad blocker active in the browser
  • The tag loaded only in production (env variable NODE_ENV)
  • Consent Mode set to "denied" by default

If you manage GA4 for multiple Next.js projects, NarratIQ centralises their properties in one dashboard and generates the monthly PDF report without manual extraction.


Install guides on other platforms

Frequently asked questions

For Next.js 13+ (App Router): the official `@next/third-parties/google`: install in 3 lines, Next.js-optimised. For Next.js 12 (Pages Router): manual `gtag.js` via `<Script>` in `_app.tsx`. GTM remains an option if you want to add other tags. Avoid old npm packages like `react-ga` which don't support GA4 properly.

Ready to automate your GA4 reports?

Connect your Google Analytics 4 in 5 minutes. 14-day free trial, no credit card.

Try NarratIQ free