How to Fix Prismic Tracking Events Not Firing | OpsBlu Docs

How to Fix Prismic Tracking Events Not Firing

Fix GA4, GTM, and pixel events not firing on Prismic — Slice Machine component rendering, route resolver navigation events, and preview mode debugging

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:

  • Tracking code not included in layout
  • Ad blocker is active
  • Script loading error (check Network tab)

Step 2: Check Network Requests

  1. Open DevTools → Network tab
  2. Filter by: analytics, gtag, fbevents, or gtm
  3. Reload page
  4. 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:

  1. Go to Settings → Environment Variables
  2. Add variables for Production, Preview, and Development
  3. 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:

  1. Open incognito/private browsing mode
  2. Disable extensions
  3. 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);

Chrome Extensions


Next Steps