Installing Google Tag Manager on Prismic with Next.js | OpsBlu Docs

Installing Google Tag Manager on Prismic with Next.js

Complete guide to setting up GTM on Prismic-powered sites for centralized tag and analytics management

Google Tag Manager (GTM) provides centralized management of tracking tags on Prismic-powered websites. This guide covers GTM implementation for Next.js, Gatsby, and custom React applications.


Prerequisites

Before you begin:

  • Have a Google Tag Manager account created
  • Know your GTM Container ID (format: GTM-XXXXXXX)
  • Have a Prismic repository set up
  • Be using Next.js, Gatsby, or another JavaScript framework

Create GTM Container

  1. Log in to Google Tag Manager
  2. Click Create Account (if new) or Add Container
  3. Enter:
    • Account Name: Your company name
    • Container Name: Your website name
    • Target Platform: Web
  4. Accept Terms of Service
  5. Copy your Container ID (GTM-XXXXXXX)

Method 1: Next.js with Prismic (App Router)

Step 1: Create GTM Component

Create components/GoogleTagManager.js:

'use client';

import Script from 'next/script';

export default function GoogleTagManager({ gtmId }) {
  return (
    <>
      {/* GTM Script */}
      <Script id="google-tag-manager" strategy="afterInteractive">
        {`
          (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','${gtmId}');
        `}
      </Script>
    </>
  );
}

Step 2: Create GTM NoScript Component

Create components/GoogleTagManagerNoScript.js:

export default function GoogleTagManagerNoScript({ gtmId }) {
  return (
    <noscript>
      <iframe
        src={`https://www.googletagmanager.com/ns.html?id=${gtmId}`}
        height="0"
        width="0"
        style={{ display: 'none', visibility: 'hidden' }}
      />
    </noscript>
  );
}

Step 3: Add to Root Layout

In app/layout.js:

import GoogleTagManager from '@/components/GoogleTagManager';
import GoogleTagManagerNoScript from '@/components/GoogleTagManagerNoScript';
import { PrismicPreview } from '@prismicio/next';

const GTM_ID = process.env.NEXT_PUBLIC_GTM_ID;

export default function RootLayout({ children }) {
  return (
    <html lang="en">
      <head>
        {GTM_ID && <GoogleTagManager gtmId={GTM_ID} />}
      </head>
      <body>
        {GTM_ID && <GoogleTagManagerNoScript gtmId={GTM_ID} />}
        {children}
        <PrismicPreview repositoryName="your-repo-name" />
      </body>
    </html>
  );
}

Step 4: Environment Variables

Create .env.local:

NEXT_PUBLIC_GTM_ID=GTM-XXXXXXX

Method 2: Next.js Pages Router

In pages/_app.js:

import Script from 'next/script';
import { PrismicProvider } from '@prismicio/react';
import Link from 'next/link';

const GTM_ID = process.env.NEXT_PUBLIC_GTM_ID;

function MyApp({ Component, pageProps }) {
  return (
    <>
      {/* GTM Script in Head */}
      <Script id="google-tag-manager" strategy="afterInteractive">
        {`
          (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}');
        `}
      </Script>

      <PrismicProvider internalLinkComponent={(props) => <Link {...props} />}>
        {/* GTM NoScript - place at top of body */}
        <noscript>
          <iframe
            src={`https://www.googletagmanager.com/ns.html?id=${GTM_ID}`}
            height="0"
            width="0"
            style={{ display: 'none', visibility: 'hidden' }}
          />
        </noscript>
        <Component {...pageProps} />
      </PrismicProvider>
    </>
  );
}

export default MyApp;

Method 3: Gatsby with Prismic

Step 1: Install Gatsby GTM Plugin

npm install gatsby-plugin-google-tagmanager gatsby-source-prismic

Step 2: Configure gatsby-config.js

module.exports = {
  plugins: [
    {
      resolve: 'gatsby-source-prismic',
      options: {
        repositoryName: 'your-repo-name',
        accessToken: process.env.PRISMIC_ACCESS_TOKEN,
        schemas: {
          // Your custom type schemas
        },
      },
    },
    {
      resolve: 'gatsby-plugin-google-tagmanager',
      options: {
        id: process.env.GTM_ID,
        includeInDevelopment: false,
        defaultDataLayer: { platform: 'gatsby' },
        routeChangeEventName: 'gatsby-route-change',
        enableWebVitalsTracking: true,
      },
    },
  ],
};

Step 3: Environment Variables

Create .env.production:

GTM_ID=GTM-XXXXXXX
PRISMIC_ACCESS_TOKEN=your-access-token

Step 4: Push Data Layer Events

In gatsby-browser.js:

export const location, prevLocation }) => {
  if (typeof window !== 'undefined' && window.dataLayer) {
    window.dataLayer.push({
      event: 'gatsby-route-change',
      page: location.pathname,
      previousPage: prevLocation?.pathname || '',
    });
  }
};

Method 4: Custom React Implementation

For vanilla React or other frameworks:

// hooks/useGTM.js
import { useEffect } from 'react';

export function useGTM(gtmId) {
  useEffect(() => {
    if (!gtmId) return;

    // Initialize dataLayer
    window.dataLayer = window.dataLayer || [];
    window.dataLayer.push({
      'gtm.start': new Date().getTime(),
      event: 'gtm.js',
    });

    // Load GTM script
    const script = document.createElement('script');
    script.async = true;
    script.src = `https://www.googletagmanager.com/gtm.js?id=${gtmId}`;

    const firstScript = document.getElementsByTagName('script')[0];
    firstScript.parentNode.insertBefore(script, firstScript);

    // Add noscript iframe
    const noscript = document.createElement('noscript');
    const iframe = document.createElement('iframe');
    iframe.src = `https://www.googletagmanager.com/ns.html?id=${gtmId}`;
    iframe.height = '0';
    iframe.width = '0';
    iframe.style.display = 'none';
    iframe.style.visibility = 'hidden';
    noscript.appendChild(iframe);
    document.body.insertBefore(noscript, document.body.firstChild);
  }, [gtmId]);
}

Usage in App:

import { useGTM } from './hooks/useGTM';
import { PrismicProvider } from '@prismicio/react';

function App() {
  useGTM('GTM-XXXXXXX');

  return (
    <PrismicProvider>
      {/* Your app */}
    </PrismicProvider>
  );
}

Initialize Data Layer for Prismic

Create a data layer initialization component:

// components/PrismicDataLayer.js
'use client';

import { useEffect } from 'react';

export function PrismicDataLayer({ document }) {
  useEffect(() => {
    if (!document || typeof window === 'undefined') return;

    window.dataLayer = window.dataLayer || [];
    window.dataLayer.push({
      event: 'prismic_document_loaded',
      prismic: {
        documentId: document.id,
        documentType: document.type,
        documentUid: document.uid,
        documentTags: document.tags || [],
        language: document.lang,
        lastPublished: document.last_publication_date,
      },
    });
  }, [document]);

  return null;
}

Usage in page component:

import { PrismicDataLayer } from '@/components/PrismicDataLayer';

export default function PrismicPage({ document }) {
  return (
    <>
      <PrismicDataLayer document={document} />
      {/* Render page content */}
    </>
  );
}

Track Client-Side Navigation

Next.js App Router

Create components/GTMPageView.js:

'use client';

import { useEffect } from 'react';
import { usePathname, useSearchParams } from 'next/navigation';

export function GTMPageView() {
  const pathname = usePathname();
  const searchParams = useSearchParams();

  useEffect(() => {
    if (typeof window.dataLayer !== 'undefined') {
      window.dataLayer.push({
        event: 'pageview',
        page: pathname + searchParams.toString(),
      });
    }
  }, [pathname, searchParams]);

  return null;
}

Include in app/layout.js:

import { GTMPageView } from '@/components/GTMPageView';

export default function RootLayout({ children }) {
  return (
    <html>
      <body>
        {children}
        <GTMPageView />
      </body>
    </html>
  );
}

Configure GTM Container

Basic GA4 Tag Setup

  1. In GTM, go to Tags > New
  2. Tag Configuration:
  3. Triggering:
    • Trigger: All Pages
  4. Save and name it "GA4 Configuration"

Create Data Layer Variables

  1. Go to Variables > New
  2. Variable Configuration:
    • Variable Type: Data Layer Variable
  3. Create these variables:
Variable Name Data Layer Variable Name
Prismic Document ID prismic.documentId
Prismic Document Type prismic.documentType
Prismic Document UID prismic.documentUid
Prismic Tags prismic.documentTags
Prismic Language prismic.language

Create Page View Trigger

  1. Go to Triggers > New
  2. Trigger Configuration:
    • Trigger Type: Custom Event
    • Event name: pageview
  3. Save as "Pageview Trigger"

Environment-Specific Configuration

Development vs. Production

Only load GTM in production:

// components/GoogleTagManager.js
export default function GoogleTagManager({ gtmId }) {
  const isDevelopment = process.env.NODE_ENV === 'development';

  if (isDevelopment || !gtmId) {
    return null; // Don't load GTM in development
  }

  return (
    <Script id="google-tag-manager" strategy="afterInteractive">
      {`...GTM code...`}
    </Script>
  );
}

Environment variables:

# .env.local (development - no GTM)
NEXT_PUBLIC_GTM_ID=

# .env.production (production)
NEXT_PUBLIC_GTM_ID=GTM-XXXXXXX

Prismic Preview Mode Handling

Track when preview mode is active:

// components/PrismicPreviewGTM.js
'use client';

import { useEffect } from 'react';
import { useSearchParams } from 'next/navigation';

export function PrismicPreviewGTM() {
  const searchParams = useSearchParams();
  const isPreview = searchParams.get('preview') === 'true';

  useEffect(() => {
    if (isPreview && typeof window.dataLayer !== 'undefined') {
      window.dataLayer.push({
        event: 'prismic_preview_active',
        preview_mode: true,
      });
    }
  }, [isPreview]);

  return null;
}

Testing & Validation

1. GTM Preview Mode

  1. In GTM, click Preview
  2. Enter your Prismic site URL
  3. Click Connect
  4. Verify container loads and tags fire
  5. Check Data Layer tab for Prismic data

2. Google Tag Assistant

  1. Install Tag Assistant
  2. Visit your site
  3. Click Tag Assistant icon
  4. Verify GTM container is detected
  5. Check all tags fire correctly

3. Browser Console

Test data layer:

// In browser console
console.log(window.dataLayer);

// Should show array with GTM events and Prismic data
// [{ event: 'gtm.js', ... }, { event: 'prismic_document_loaded', prismic: {...} }]

4. Network Tab

  1. Open DevTools > Network tab
  2. Filter by gtm.js
  3. Verify GTM script loads
  4. Check for gtm.js?id=GTM-XXXXXXX

Common Issues & Solutions

Issue: GTM Not Loading

Cause: Script placement or environment variable issue

Solution:

  • Verify NEXT_PUBLIC_GTM_ID is set
  • Ensure script is in <head> (see examples above)
  • Check browser console for errors

Issue: Data Layer Not Populating

Cause: Data layer pushed before GTM loads

Solution: Ensure GTM loads before data layer pushes:

// Wait for GTM to load
useEffect(() => {
  const interval = setInterval(() => {
    if (window.dataLayer) {
      window.dataLayer.push({ event: 'custom_event' });
      clearInterval(interval);
    }
  }, 100);

  return () => clearInterval(interval);
}, []);

Issue: Tags Not Firing on Route Change

Cause: SPA navigation not tracked

Solution: Use route change listener (see GTMPageView component above)

Issue: Duplicate Container Loads

Cause: GTM component rendered multiple times

Solution: Ensure GTM only in root layout, not in individual pages


Performance Optimization

Use Next.js Script Component

<Script
  id="google-tag-manager"
  strategy="afterInteractive" // Loads after page is interactive
>
  {/* GTM code */}
</Script>

Strategies:

  • afterInteractive - Load after page is interactive (recommended)
  • lazyOnload - Load during idle time
  • beforeInteractive - Load before page is interactive (blocking)

Defer Non-Critical Tags in GTM

In GTM container:

  1. Go to Tags > [Your Tag]
  2. Advanced Settings > Tag firing options
  3. Select Once per page (prevent duplicates)

Next Steps


Additional Resources