How to Add Google Analytics to Next.js

Cody Schneider7 min read

Adding Google Analytics to your Next.js app is the best way to understand how users find and interact with your site. Doing it right, however, means accounting for Next.js nature as a single-page application (SPA). This tutorial will walk you through two methods: a simple, direct approach for basic tracking and a more robust solution that correctly captures all page views as users navigate your app.

First, Why Even Bother?

Connecting Google Analytics isn't just a box to check, it’s how you answer critical questions about your audience and content. Without it, you’re flying blind. With it, you can:

  • Measure Traffic Sources: See whether users are coming from search, social media, or direct links.
  • Identify Popular Content: Discover which pages and blog posts get the most engagement.
  • Understand User Behavior: Track the path users take through your site and see where they drop off.
  • Track Conversions: Monitor key actions like form submissions, sign-ups, or purchases.

Prerequisite: Find Your Measurement ID in Google Analytics

Before you touch any code, you need a Google Analytics "Measurement ID." This unique identifier tells Google where to send the tracking data. If you already have one, you can skip this section.

Here’s how to find it:

  1. Log into your Google Analytics account.
  2. Click the Admin gear icon in the bottom-left corner.
  3. In the Property column, ensure the correct property is selected. If not, create a new one.
  4. Click on Data Streams and select your web data stream (e.g., your website's domain).
  5. In the top right, you'll see your Measurement ID, which looks like G-XXXXXXXXXX. Copy this ID, you'll need it in a moment.

Method 1: The Simple Approach with next/script

This method uses Next.js built-in <Script> component to load the Google Analytics tracking code. It's quick and works for tracking the initial page load but misses subsequent page navigations within the app. It's a fine starting point for simpler sites.

Step 1: Store Your Measurement ID in an Environment Variable

Never hard-code secrets like API keys or measurement IDs directly into your code. Instead, use environment variables. Create a file named .env.local in the root of your project (the same level as package.json).

Inside .env.local, add your Measurement ID:

NEXT_PUBLIC_GA_MEASUREMENT_ID=G-XXXXXXXXXX

Note: The NEXT_PUBLIC_ prefix is crucial. It tells Next.js to make this variable available to the browser, which is necessary for Google Analytics to work.

Step 2: Add the Tracking Scripts

You’ll add two scripts that load Google’s minimal tracking library (gtag.js) and then initialize it with your unique ID.

Where you place these scripts depends on whether you're using the App Router or the Pages Router.

For the App Router (app/layout.js):

Open your root layout file, usually located at app/layout.js or app/layout.tsx, and add the two <Script> components inside the <body> tag.

import Script from 'next/script',

export default function RootLayout({ children }) {
  return (
    <html lang="en">
      <head>
        {/* Your other head elements */}
      </head>
      <body>
        {children}

        <Script
          strategy="afterInteractive"
          src={`https://www.googletagmanager.com/gtag/js?id=${process.env.NEXT_PUBLIC_GA_MEASUREMENT_ID}`}
        />

        <Script
          id="ga-init"
          strategy="afterInteractive"
          dangerouslySetInnerHTML={{
            __html: `
              window.dataLayer = window.dataLayer || [],
              function gtag(){dataLayer.push(arguments),}
              gtag('js', new Date()),
              gtag('config', '${process.env.NEXT_PUBLIC_GA_MEASUREMENT_ID}'),
            `,
          }}
        />
      </body>
    </html>
  ),
}

For the Pages Router (pages/_app.js):

Open your pages/_app.js or pages/_app.tsx file and add the scripts alongside your main <Component>.

import Script from 'next/script',

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

export default MyApp,

The strategy="afterInteractive" prop tells Next.js to load this script after the page becomes interactive, preventing it from blocking your page content from rendering.

Method 2: The Robust SPA Approach for Tracking Route Changes

The biggest limitation of the simple method is that it only fires a "pageview" event when the website first loads. When a user clicks an <a> link in Next.js, the browser doesn't do a full page refresh. To Google Analytics, it looks like the user is still on the same page.

This method fixes that by explicitly sending a page_view event to Google Analytics every time the route changes.

Step 1: Set Up Helper Functions

It's good practice to centralize your Google Analytics logic. Create a lib folder in your project's root and add a new file named gtag.js inside.

// lib/gtag.js
export const GA_MEASUREMENT_ID = process.env.NEXT_PUBLIC_GA_MEASUREMENT_ID,

// Track pageviews
export const pageview = (url) => {
  // Only send pageviews in production
  if (process.env.NODE_ENV === 'production' && typeof window.gtag === 'function') {
    window.gtag('config', GA_MEASUREMENT_ID, {
      page_path: url,
    }),
  }
},

// Track specific events
export const event = ({ action, category, label, value }) => {
  if (process.env.NODE_ENV === 'production' && typeof window.gtag === 'function') {
    window.gtag('event', action, {
      event_category: category,
      event_label: label,
      value: value,
    }),
  }
},

This file defines two functions: one for tracking pageview events and another for custom event tracking (like button clicks), which we'll address later. Notice we've added a check to ensure we only send data to Analytics when the app is in production, keeping your local development data separate.

Step 2: Listen for Route Changes and Fire Events

The next steps are slightly different for the App Router and Pages Router.

For the App Router (app/):

Create a new component components/Analytics.js:

'use client',

import { useEffect } from 'react',
import { usePathname, useSearchParams } from 'next/navigation',
import { pageview } from '@/lib/gtag',

export default function Analytics() {
  const pathname = usePathname(),
  const searchParams = useSearchParams(),

  useEffect(() => {
    if (pathname) {
      pageview(pathname),
    }
  }, [pathname, searchParams]),

  return null,
}

Next, import and render this Analytics component in your app/layout.js. Also, remember to include the base Google Analytics scripts from Method 1.

// In app/layout.js
import Analytics from '@/components/Analytics',
import Script from 'next/script',

export default function RootLayout({ children }) {
  return (
    <html lang="en">
      <head>
        {/* ... */}
      </head>
      <body>
        <Analytics />

        {/* Base GA scripts from Method 1 */}
        <Script
          strategy="afterInteractive"
          src={`https://www.googletagmanager.com/gtag/js?id=${process.env.NEXT_PUBLIC_GA_MEASUREMENT_ID}`}
        />
        <Script
          id="ga-init"
          strategy="afterInteractive"
          dangerouslySetInnerHTML={{
            __html: `
              window.dataLayer = window.dataLayer || [],
              function gtag(){dataLayer.push(arguments),}
              gtag('js', new Date()),
              gtag('config', '${process.env.NEXT_PUBLIC_GA_MEASUREMENT_ID}'),
            `,
          }}
        />
        {children}
      </body>
    </html>
  ),
}

This setup wraps the Analytics component in React's Suspense, which is necessary because the navigation hooks may rely on parts of the framework not available during server-side rendering. Wrapping ensures the component only renders on the client side.

For the Pages Router (pages/_app.js):

In your pages/_app.js:

import { useEffect } from 'react',
import { useRouter } from 'next/router',
import * as gtag from '../lib/gtag',

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

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

    router.events.on('routeChangeComplete', handleRouteChange),
    return () => {
      router.events.off('routeChangeComplete', handleRouteChange),
    },
  }, [router.events]),

  return (
    <>
      <Component {...pageProps} />
    </>
  ),
},

export default MyApp,

This code uses a useEffect hook to subscribe to the router's routeChangeComplete event. When that event fires, it calls our pageview helper with the new URL. The cleanup function inside the hook prevents memory leaks when the component unmounts.

Bonus: Tracking Custom Events

With the event helper function ready, you can now track custom interactions. For example, if you want to track when a user signs up for your newsletter, you can add an event to the button's onClick handler.

import * as gtag from '../lib/gtag',

function NewsletterSignupButton() {
  const handleClick = () => {
    gtag.event({
      action: 'signup_newsletter',
      category: 'Engagement',
      label: 'User signed up for newsletter from footer',
      value: 1, // Optional value
    }),
    // ... rest of your form submission logic
  },

  return <button onClick={handleClick}>Sign Up</button>,
}

Final Thoughts

Implementing Google Analytics in Next.js is fairly simple once you know the right approach. For accurate tracking in modern web apps, it's essential to account for client-side navigation, and the robust "SPA" method outlined here ensures every page view gets counted, giving you a complete picture of user behavior.

Once your data is flowing into Google Analytics, the challenge shifts from collecting data to understanding it. The standard Google Analytics interface can be overwhelming, but we built Graphed to solve exactly that. We let you connect directly to Google Analytics and then use simple, natural language to build dashboards and ask questions. Instead of clicking through menus to create reports, you can just ask, "Show me my top 10 landing pages by sessions last month," and get an answer instantly.

Related Articles

How to Connect Facebook to Google Data Studio: The Complete Guide for 2026

Connecting Facebook Ads to Google Data Studio (now called Looker Studio) has become essential for digital marketers who want to create comprehensive, visually appealing reports that go beyond the basic analytics provided by Facebook's native Ads Manager. If you're struggling with fragmented reporting across multiple platforms or spending too much time manually exporting data, this guide will show you exactly how to streamline your Facebook advertising analytics.

Appsflyer vs Mixpanel​: Complete 2026 Comparison Guide

The difference between AppsFlyer and Mixpanel isn't just about features—it's about understanding two fundamentally different approaches to data that can make or break your growth strategy. One tracks how users find you, the other reveals what they do once they arrive. Most companies need insights from both worlds, but knowing where to start can save you months of implementation headaches and thousands in wasted budget.