Events not tracking correctly on Prismic sites can result from framework-specific issues, client-side rendering problems, or configuration errors. This guide helps diagnose and fix common tracking issues.
Common Symptoms
- GA4 events don't appear in DebugView or Real-time reports
- Meta Pixel Helper shows no pixel detected
- GTM Preview Mode shows no tags firing
- Page views track, but custom events don't
- Events fire in development but not production
- Events fire on initial load but not on navigation
Diagnostic Steps
Step 1: Verify Tracking Scripts Load
Check in Browser Console:
// Check if GA4 loaded
console.log(typeof window.gtag); // Should output 'function'
console.log(window.dataLayer); // Should output an array
// Check if Meta Pixel loaded
console.log(typeof window.fbq); // Should output 'function'
console.log(window._fbq); // Should output an object
// Check if GTM loaded
console.log(typeof window.google_tag_manager); // Should output 'object'
console.log(window.dataLayer); // Should output an array
If scripts are undefined:
Step 2: Check Network Requests
- Open DevTools → Network tab
- Filter by:
analytics,gtag,fbevents, orgtm - Reload page
- Verify tracking requests are sent
Expected requests:
- GA4:
https://www.google-analytics.com/g/collect - Meta Pixel:
https://www.facebook.com/tr/ - GTM:
https://www.googletagmanager.com/gtm.js
If no requests:
- Tracking code not loading
- Ad blocker blocking requests
- CORS or CSP policy blocking
Step 3: Enable Debug Mode
GA4 Debug Mode:
// Add to your GA4 initialization
gtag('config', 'G-XXXXXXXXXX', {
'debug_mode': true
});
Then check GA4 → Configure → DebugView for real-time events.
Meta Pixel Debug:
Use Meta Pixel Helper Chrome Extension
GTM Debug:
Use GTM Preview Mode to see tags, triggers, and variables in real-time.
Common Issues & Solutions
Issue 1: Events Not Firing on Client-Side Navigation
Symptoms:
- Events fire on initial page load
- Events don't fire when navigating between pages
- Page view count is lower than expected
Cause: Single-page application (SPA) navigation not tracked
Solution for Next.js App Router:
// components/PageViewTracking.js
'use client';
import { useEffect } from 'react';
import { usePathname, useSearchParams } from 'next/navigation';
export function PageViewTracking() {
const pathname = usePathname();
const searchParams = useSearchParams();
useEffect(() => {
const url = pathname + searchParams.toString();
// Track GA4 page view
if (typeof window.gtag !== 'undefined') {
window.gtag('config', 'G-XXXXXXXXXX', {
page_path: url,
});
}
// Track Meta Pixel page view
if (typeof window.fbq !== 'undefined') {
window.fbq('track', 'PageView');
}
// Track GTM page view
if (typeof window.dataLayer !== 'undefined') {
window.dataLayer.push({
event: 'pageview',
page: url,
});
}
}, [pathname, searchParams]);
return null;
}
Add to layout:
// app/layout.js
import { PageViewTracking } from '@/components/PageViewTracking';
export default function RootLayout({ children }) {
return (
<html>
<body>
{children}
<PageViewTracking />
</body>
</html>
);
}
Solution for Next.js Pages Router:
// pages/_app.js
import { useRouter } from 'next/router';
import { useEffect } from 'react';
function MyApp({ Component, pageProps }) {
const router = useRouter();
useEffect(() => {
const handleRouteChange = (url) => {
if (typeof window.gtag !== 'undefined') {
window.gtag('config', 'G-XXXXXXXXXX', {
page_path: url,
});
}
if (typeof window.fbq !== 'undefined') {
window.fbq('track', 'PageView');
}
};
router.events.on('routeChangeComplete', handleRouteChange);
return () => {
router.events.off('routeChangeComplete', handleRouteChange);
};
}, [router.events]);
return <Component {...pageProps} />;
}
export default MyApp;
Solution for Gatsby:
// gatsby-browser.js
export const location }) => {
if (typeof window.gtag !== 'undefined') {
window.gtag('config', 'G-XXXXXXXXXX', {
page_path: location.pathname + location.search,
});
}
if (typeof window.fbq !== 'undefined') {
window.fbq('track', 'PageView');
}
if (typeof window.dataLayer !== 'undefined') {
window.dataLayer.push({
event: 'pageview',
page: location.pathname,
});
}
};
Issue 2: Scripts Not Loading in Production
Symptoms:
- Tracking works in development
- No tracking in production build
- Environment variable issues
Cause: Environment variables not set correctly
Solution:
Check environment variables:
# .env.local (development)
NEXT_PUBLIC_GA_MEASUREMENT_ID=G-XXXXXXXXXX
NEXT_PUBLIC_META_PIXEL_ID=1234567890123456
NEXT_PUBLIC_GTM_ID=GTM-XXXXXXX
# .env.production (production)
NEXT_PUBLIC_GA_MEASUREMENT_ID=G-XXXXXXXXXX
NEXT_PUBLIC_META_PIXEL_ID=1234567890123456
NEXT_PUBLIC_GTM_ID=GTM-XXXXXXX
Verify in component:
export default function GoogleAnalytics() {
const measurementId = process.env.NEXT_PUBLIC_GA_MEASUREMENT_ID;
// Debug: Log to verify
console.log('GA Measurement ID:', measurementId);
if (!measurementId) {
console.error('GA4 Measurement ID not found!');
return null;
}
return (
<Script
src={`https://www.googletagmanager.com/gtag/js?id=${measurementId}`}
strategy="afterInteractive"
/>
);
}
For Vercel deployments:
- Go to Settings → Environment Variables
- Add variables for Production, Preview, and Development
- Redeploy
Issue 3: Data Layer Not Populating
Symptoms:
- GTM Preview Mode shows empty data layer
- Variables show
undefined - Custom events don't fire
Cause: Data layer pushed before GTM loads or incorrect variable path
Solution:
Ensure data layer initializes before GTM:
// app/layout.js
export default function RootLayout({ children }) {
return (
<html>
<head>
{/* Initialize dataLayer BEFORE GTM script */}
<script
dangerouslySetInnerHTML={{
__html: `window.dataLayer = window.dataLayer || [];`,
}}
/>
{/* Then load GTM */}
<GoogleTagManager gtmId="GTM-XXXXXXX" />
</head>
<body>{children}</body>
</html>
);
}
Verify data layer pushes:
// components/PrismicDataLayer.js
'use client';
import { useEffect } from 'react';
export function PrismicDataLayer({ document }) {
useEffect(() => {
if (!document) return;
// Debug: Log before push
console.log('Pushing to dataLayer:', {
event: 'prismic_data_ready',
prismic: {
id: document.id,
type: document.type,
},
});
window.dataLayer = window.dataLayer || [];
window.dataLayer.push({
event: 'prismic_data_ready',
prismic: {
id: document.id,
type: document.type,
uid: document.uid,
},
});
// Debug: Verify push
console.log('dataLayer after push:', window.dataLayer);
}, [document]);
return null;
}
Check GTM variable paths:
In GTM, ensure variable names match data layer structure:
- Correct:
prismic.type - Incorrect:
prismic.documentType(if not in data layer)
Issue 4: Events Fire Multiple Times
Symptoms:
- Duplicate events in analytics
- Event count higher than expected
- Multiple pixel fires
Cause: Multiple tracking scripts or re-renders triggering events
Solution:
Prevent duplicate gtag initialization:
// components/GoogleAnalytics.js
'use client';
import Script from 'next/script';
import { useEffect, useRef } from 'react';
export default function GoogleAnalytics({ measurementId }) {
const initialized = useRef(false);
useEffect(() => {
if (initialized.current) return;
if (typeof window.gtag !== 'undefined') {
initialized.current = true;
}
}, []);
return (
<Script
src={`https://www.googletagmanager.com/gtag/js?id=${measurementId}`}
strategy="afterInteractive" => {
if (!initialized.current) {
window.dataLayer = window.dataLayer || [];
function gtag() {
window.dataLayer.push(arguments);
}
window.gtag = gtag;
gtag('js', new Date());
gtag('config', measurementId);
initialized.current = true;
}
}}
/>
);
}
Prevent duplicate event tracking:
// Use ref to track if event already sent
export function TrackedSlice({ slice, children }) {
const hasTracked = useRef(false);
const handleClick = () => {
if (hasTracked.current) return; // Prevent duplicates
hasTracked.current = true;
if (typeof window.gtag !== 'undefined') {
window.gtag('event', 'slice_click', {
slice_type: slice.slice_type,
});
}
};
return <div
}
Issue 5: Slice Events Not Tracking
Symptoms:
- Page views work, but Slice interactions don't track
- Intersection Observer not firing
- Click events not detected
Cause: Slice components not wrapped with tracking
Solution:
Wrap Slices with tracking component:
// components/TrackedSlice.js
'use client';
import { useEffect, useRef } from 'react';
export function TrackedSlice({ slice, index, children }) {
const sliceRef = useRef(null);
const hasTracked = useRef(false);
useEffect(() => {
if (!sliceRef.current || hasTracked.current) return;
const observer = new IntersectionObserver(
(entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting && !hasTracked.current) {
hasTracked.current = true;
console.log('Slice viewed:', slice.slice_type); // Debug
if (typeof window.gtag !== 'undefined') {
window.gtag('event', 'view_item', {
event_category: 'slice',
event_label: slice.slice_type,
slice_position: index + 1,
});
}
}
});
},
{ threshold: 0.5 }
);
observer.observe(sliceRef.current);
return () => observer.disconnect();
}, [slice, index]);
return (
<div ref={sliceRef} data-slice-type={slice.slice_type}>
{children}
</div>
);
}
Use in Slice components:
// slices/HeroSlice/index.js
import { TrackedSlice } from '@/components/TrackedSlice';
export default function HeroSlice({ slice, index }) {
return (
<TrackedSlice slice={slice} index={index}>
<section className="hero">
{/* Hero content */}
</section>
</TrackedSlice>
);
}
Issue 6: Ad Blockers Blocking Tracking
Symptoms:
- Tracking works in incognito mode
- Works without browser extensions
- No tracking in normal browsing
Cause: Ad blocker extensions blocking analytics
Solution:
Test without ad blockers:
- Open incognito/private browsing mode
- Disable extensions
- Test tracking
Consider server-side tracking:
For Next.js, use Conversion API or Measurement Protocol:
// app/api/track/route.js
import { NextResponse } from 'next/server';
export async function POST(request) {
const { event, eventData } = await request.json();
// Send to GA4 Measurement Protocol
const measurementId = process.env.GA_MEASUREMENT_ID;
const apiSecret = process.env.GA_API_SECRET;
await fetch(
`https://www.google-analytics.com/mp/collect?measurement_id=${measurementId}&api_secret=${apiSecret}`,
{
method: 'POST',
body: JSON.stringify({
client_id: eventData.clientId,
events: [
{
name: event,
params: eventData,
},
],
}),
}
);
return NextResponse.json({ success: true });
}
Issue 7: Preview Mode Events Not Tracking
Symptoms:
- Production events work
- Preview mode events don't fire
- Preview pages excluded from tracking
Cause: Preview routes excluded or detection logic missing
Solution:
Don't exclude preview routes:
// Gatsby example - Don't exclude preview
{
resolve: 'gatsby-plugin-google-gtag',
options: {
pluginConfig: {
exclude: ['/admin/**'], // Don't exclude /preview
},
},
}
Add preview detection:
// components/PrismicPreviewTracking.js
'use client';
import { useEffect } from 'react';
import { useSearchParams } from 'next/navigation';
export function PrismicPreviewTracking() {
const searchParams = useSearchParams();
const isPreview = searchParams.get('preview') === 'true';
useEffect(() => {
console.log('Preview mode:', isPreview); // Debug
if (isPreview && typeof window.gtag !== 'undefined') {
window.gtag('event', 'preview_accessed', {
event_category: 'preview',
});
}
}, [isPreview]);
return null;
}
Debugging Checklist
- Verify tracking scripts load (check console for
gtag,fbq,dataLayer) - Check Network tab for analytics requests
- Enable debug mode (GA4 DebugView, Meta Pixel Helper, GTM Preview)
- Test in incognito mode without extensions
- Verify environment variables are set
- Check for client-side navigation tracking
- Ensure data layer initializes before GTM
- Look for duplicate script loads
- Test on production build, not just development
- Check CSP headers aren't blocking scripts
- Verify Slice components wrapped with tracking
- Test preview mode separately
Testing Tools
Browser Console Testing
// Test GA4
gtag('event', 'test_event', { test: true });
console.log(window.dataLayer);
// Test Meta Pixel
fbq('track', 'Lead', { test: true });
console.log(window._fbq);
// Test GTM
window.dataLayer.push({ event: 'test_event', test: true });
console.log(window.dataLayer);