How to Fix Storyblok Tracking Events Not Firing | OpsBlu Docs

How to Fix Storyblok Tracking Events Not Firing

Fix GA4, GTM, and pixel events not firing on Storyblok — Visual Editor iframe isolation, bridge.js conflicts, and frontend framework SSR debugging

Events not tracking correctly on Storyblok sites can result from framework-specific issues, Visual Editor isolation, or configuration errors. This guide helps diagnose and fix common tracking issues.


Common Symptoms

  • GA4 events don't appear in DebugView
  • Meta Pixel Helper shows no pixel detected
  • GTM Preview Mode shows no tags firing
  • Events fire in development but not production
  • Events don't fire in Visual Editor
  • Events fire on initial load but not on navigation

Diagnostic Steps

Step 1: Verify Tracking Scripts Load

// Check if GA4 loaded
console.log(typeof window.gtag); // Should output 'function'

// Check if Meta Pixel loaded
console.log(typeof window.fbq); // Should output 'function'

// Check if GTM loaded
console.log(window.dataLayer); // Should output an array

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

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

Cause: SPA navigation not tracked

Solution for Nuxt 3:

// plugins/page-view-tracking.client.ts
export default defineNuxtPlugin((nuxtApp) => {
  nuxtApp.hook('page:finish', () => {
    // Track GA4 page view
    if (typeof window.gtag !== 'undefined') {
      window.gtag('config', 'G-XXXXXXXXXX', {
        page_path: window.location.pathname,
      });
    }

    // 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: window.location.pathname,
      });
    }
  });
});

Issue 2: Scripts Not Loading in Visual Editor

Symptoms:

  • Tracking works on published site
  • No tracking in Visual Editor
  • Visual Editor iframe isolation

Cause: Visual Editor runs in iframe, isolating tracking scripts

Solution:

// plugins/storyblok-editor-tracking.client.ts
export default defineNuxtPlugin(() => {
  const route = useRoute();

  // Check if in Visual Editor
  const isEditor = computed(() => route.query._storyblok !== undefined);

  // Only track if NOT in iframe or if parent window tracking is available
  const canTrack = window === window.parent || window.parent.gtag;

  if (canTrack || !isEditor.value) {
    // Safe to track
    if (typeof window.gtag !== 'undefined') {
      window.gtag('event', 'page_view');
    }
  }
});

Issue 3: Environment Variables Not Set

Symptoms:

  • Tracking works in development
  • No tracking in production build

Cause: Environment variables not configured

Solution:

Check .env:

# Development
NUXT_PUBLIC_GA_MEASUREMENT_ID=G-XXXXXXXXXX
NUXT_PUBLIC_META_PIXEL_ID=1234567890123456
NUXT_PUBLIC_GTM_ID=GTM-XXXXXXX
STORYBLOK_TOKEN=your-token

# Production (use same or separate)

Verify in code:

// plugins/gtag.client.ts
export default defineNuxtPlugin(() => {
  const config = useRuntimeConfig();
  const measurementId = config.public.gaMeasurementId;

  console.log('GA Measurement ID:', measurementId); // Debug

  if (!measurementId) {
    console.error('GA4 Measurement ID not found!');
    return;
  }

  // Load GA4
});

Issue 4: Data Layer Not Populating

Symptoms:

  • GTM Preview shows empty data layer
  • Variables show undefined

Cause: Data layer pushed before GTM loads

Solution:

// Ensure dataLayer initializes before GTM
export default defineNuxtConfig({
  app: {
    head: {
      script: [
        {
          innerHTML: 'window.dataLayer = window.dataLayer || [];',
          type: 'text/javascript',
        },
      ],
    },
  },
});

Verify data layer pushes:

// Debug data layer
export default defineNuxtPlugin(() => {
  const pushStoryData = (story: any) => {
    console.log('Pushing to dataLayer:', {
      event: 'storyblok_data_ready',
      storyblok: { id: story.id },
    });

    window.dataLayer = window.dataLayer || [];
    window.dataLayer.push({
      event: 'storyblok_data_ready',
      storyblok: { id: story.id, component: story.content.component },
    });

    console.log('dataLayer after push:', window.dataLayer);
  };

  return { provide: { pushStoryData } };
});

Issue 5: Duplicate Events

Symptoms:

  • Event count higher than expected
  • Multiple pixel fires

Cause: Multiple tracking scripts or re-renders

Solution:

// Use ref to prevent duplicates
export const useComponentTracking = (blok: any) => {
  const hasTracked = ref(false);

  const track = () => {
    if (hasTracked.value) return; // Prevent duplicates
    hasTracked.value = true;

    if (typeof window.gtag !== 'undefined') {
      window.gtag('event', 'component_view', {
        component_type: blok.component,
      });
    }
  };

  onMounted(track);

  return { track };
};

Issue 6: Component Events Not Tracking

Symptoms:

  • Page views work
  • Component interactions don't track
  • Intersection Observer not firing

Cause: Components not wrapped with tracking

Solution:

<!-- components/TrackedComponent.vue -->
<template>
  <div ref="elementRef" v-editable="blok">
    <slot />
  </div>
</template>

<script setup>
const { blok } = defineProps(['blok']);
const elementRef = ref<HTMLElement | null>(null);
const hasTracked = ref(false);

onMounted(() => {
  if (!elementRef.value || hasTracked.value) return;

  const observer = new IntersectionObserver(
    (entries) => {
      entries.forEach((entry) => {
        if (entry.isIntersecting && !hasTracked.value) {
          hasTracked.value = true;

          console.log('Component viewed:', blok.component); // Debug

          if (typeof window.gtag !== 'undefined') {
            window.gtag('event', 'view_item', {
              event_category: 'component',
              event_label: blok.component,
            });
          }
        }
      });
    },
    { threshold: 0.5 }
  );

  observer.observe(elementRef.value);

  onUnmounted(() => observer.disconnect());
});
</script>

Issue 7: Ad Blockers Blocking Tracking

Symptoms:

  • Tracking works in incognito mode
  • Works without browser extensions

Cause: Ad blocker extensions blocking analytics

Solution:

Test without ad blockers:

  1. Open incognito/private mode
  2. Disable extensions
  3. Test tracking

Consider server-side tracking for Nuxt:

// server/api/track.post.ts
export default defineEventHandler(async (event) => {
  const { eventType, eventData } = await readBody(event);

  // 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: {
        client_id: eventData.clientId,
        events: [{ name: eventType, params: eventData }],
      },
    }
  );

  return { success: true };
});

Debugging Checklist

  • Verify tracking scripts load (check console)
  • 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
  • Check if Visual Editor isolation is the issue
  • Verify component tracking is implemented
  • Test with different browsers

Testing Tools

Browser Console Testing

// Test GA4
gtag('event', 'test_event', { test: true });

// Test Meta Pixel
fbq('track', 'Lead', { test: true });

// Test GTM
window.dataLayer.push({ event: 'test_event' });

Chrome Extensions


Next Steps