Integrate Google Analytics 4 with Next.js in Typescript

There are numerous examples of integrating Next.js in javascript, and even then, these articles focus on integrations with previous versions of GA 4.

This article focuses only on GA 4 and how you can integrate it with your Next.js application written in typescript.

This article does not cover the following topics — Let me know in the comments if you want them to be.

  1. How to create a new web property in GA

  2. How to deploy Next.js Application with the environment variable for GA_ID

Let’s get started!

Step 1: Create or modify _document.tsx

import Document, { Head, Html, Main, NextScript } from 'next/document' 
// You can import from other folder based on your project. 
import { GA_TRACKING_ID } from '../utils/constants' 

export default class MyDocument extends Document { 
  render() { 
    return ( 
      <Html> 
        <Head> 
          {/* Global Site Tag (gtag.js) - Google Analytics */} 
          <script async src={`https://www.googletagmanager.com/gtag/js?id=${GA_TRACKING_ID}`} /> 
          <script dangerouslySetInnerHTML={{ 
            __html: `
              window.dataLayer = window.dataLayer || []; 
              function gtag(){dataLayer.push(arguments);} 
              gtag('js', new Date()); 
              gtag('config', '${GA_TRACKING_ID}', { page_path: window.location.pathname, }); 
            `, 
          }} /> 
        </Head> 
        <body> 
          <Main /> 
          <NextScript /> 
        </body> 
      </Html> 
    ) 
  } 
}

Step 2: Add GA_TRACKING_ID constant

// File: constants.ts 
export const GA_TRACKING_ID = process.env.NEXT_PUBLIC_GA_ID

Your editor might complain that NEXT_PUBLIC_GA_ID does not exist on env object. We will handle that later.

Step 3: Modify or create _app.tsx

import App, { NextWebVitalsMetric } from 'next/app' 
import '../styles/globals.css' 
import type { AppProps, AppContext } from 'next/app' 

// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types 
function MyApp({ Component, pageProps }: AppProps) { 
  return ( 
    <Component {…pageProps} /> 
  ) 
} 

MyApp.getInitialProps = async (appContext: AppContext) => { 
  // calls page's `getInitialProps` and fills `appProps.pageProps` 
  const appProps = await App.getInitialProps(appContext) 
  return { …appProps } 
} 

export function reportWebVitals({ id, name, label, value, }: NextWebVitalsMetric): void { 
  window.gtag('event', name, { 
    event_category: label === 'web-vital' ? 'Web Vitals' : 'Next.js custom metric', 
    value: Math.round(name === 'CLS' ? value * 1000 : value), // values must be integers 
    event_label: id, // id unique to current page load 
    non_interaction: true, // avoids affecting bounce rate. 
  }) 
} 

export default MyApp

Note: Your editor will complain that gtag does not exist on the window object. So let’s handle that.

Step 4: Handle global variables types

Create a new file in your application directory (does not matter where you put it)

// File: environment.d.ts (You can name this anything you want) declare global { 
  interface Window { 
    gtag: any // you can create a Type for this. I left it to any 
  } 
  namespace NodeJS { 
    interface ProcessEnv { 
      NEXT_PUBLIC_GA_ID: string 
    } 
  } 
} // If this file has no import/export statements (i.e. is a script) // convert it into a module by adding an empty export statement. export {}

You can verify if your code is working by starting the server in localhost and checking the real-time metrics in google analytics.