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
- Log in to Google Tag Manager
- Click Create Account (if new) or Add Container
- Enter:
- Account Name: Your company name
- Container Name: Your website name
- Target Platform: Web
- Accept Terms of Service
- 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
- In GTM, go to Tags > New
- Tag Configuration:
- Tag Type: Google Analytics: GA4 Configuration
- Measurement ID:
G-XXXXXXXXXX
- Triggering:
- Trigger: All Pages
- Save and name it "GA4 Configuration"
Create Data Layer Variables
- Go to Variables > New
- Variable Configuration:
- Variable Type: Data Layer Variable
- 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
- Go to Triggers > New
- Trigger Configuration:
- Trigger Type: Custom Event
- Event name:
pageview
- 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
- In GTM, click Preview
- Enter your Prismic site URL
- Click Connect
- Verify container loads and tags fire
- Check Data Layer tab for Prismic data
2. Google Tag Assistant
- Install Tag Assistant
- Visit your site
- Click Tag Assistant icon
- Verify GTM container is detected
- 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
- Open DevTools > Network tab
- Filter by
gtm.js - Verify GTM script loads
- 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_IDis 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 timebeforeInteractive- Load before page is interactive (blocking)
Defer Non-Critical Tags in GTM
In GTM container:
- Go to Tags > [Your Tag]
- Advanced Settings > Tag firing options
- Select Once per page (prevent duplicates)
Next Steps
- Configure Prismic GTM Data Layer for comprehensive tracking
- Set up GA4 via GTM
- Configure Meta Pixel via GTM
- Troubleshoot Events Not Firing