Events Not Firing on Commercetools | Blue Frog Docs

Events Not Firing on Commercetools

Debug and fix tracking events that aren't firing on Commercetools headless storefronts

Events Not Firing on Commercetools

General Guide: See Global Events Not Firing Guide for universal concepts.

Commercetools' headless architecture means tracking is entirely implemented in your frontend, creating unique debugging scenarios.

Commercetools-Specific Causes

1. Async Data Not Available

Tracking fires before Commercetools API data loads:

  • Product data fetched after component mounts
  • useEffect runs before API response
  • Race conditions between data and tracking

2. React/Vue Hydration Issues

SSR/SSG creates hydration mismatches:

  • Server render doesn't include tracking
  • Client hydration triggers duplicate events
  • Conditional rendering based on window object

3. SPA Navigation Events

Single-page app navigation doesn't reload tracking:

  • Page views not sent on route change
  • Previous page data persists
  • Data layer not cleared between views

4. Component Re-renders

React strict mode and state updates cause issues:

  • Events fire multiple times
  • useEffect dependencies trigger re-tracking
  • State updates re-execute tracking code

Debugging Steps

Step 1: Check Console for Errors

// Open browser console and check for errors
// Common errors:

// SDK not loaded
// Uncaught ReferenceError: gtag is not defined

// Data undefined
// Cannot read properties of undefined (reading 'id')

// Network errors
// POST https://www.google-analytics.com 403

Step 2: Verify Tracking Code Loads

// Check if tracking libraries are present
console.log('GA4:', typeof window.gtag);      // Should be 'function'
console.log('GTM:', window.dataLayer?.length); // Should be > 0
console.log('Meta:', typeof window.fbq);       // Should be 'function'

// Check dataLayer contents
console.table(window.dataLayer);

Step 3: Verify Data Availability

// In your component, log data before tracking
useEffect(() => {
  console.log('Product data:', product);
  console.log('Product ID:', product?.id);
  console.log('Master Data:', product?.masterData?.current);

  if (!product?.id) {
    console.warn('Product data not yet available');
    return;
  }

  // Now track
  trackViewItem(product);
}, [product]);

Step 4: Check Network Requests

  1. Open DevTools → Network tab
  2. Filter by google-analytics.com or facebook.com
  3. Trigger an event
  4. Verify request is sent with correct payload

Commercetools-Specific Fixes

Fix 1: Wait for API Data

Ensure data is available before tracking:

// hooks/useProductTracking.ts
import { useEffect, useRef } from 'react';
import { ProductProjection } from '@commercetools/platform-sdk';

export function useProductTracking(product: ProductProjection | null | undefined) {
  const hasTracked = useRef(false);

  useEffect(() => {
    // Only track once, when product data is available
    if (product?.id && !hasTracked.current) {
      hasTracked.current = true;

      const variant = product.masterData.current.masterVariant;
      const price = variant.prices?.[0];

      window.dataLayer?.push({
        event: 'view_item',
        ecommerce: {
          currency: price?.value.currencyCode || 'USD',
          value: price ? price.value.centAmount / 100 : 0,
          items: [{
            item_id: product.id,
            item_name: product.masterData.current.name['en-US'],
            price: price ? price.value.centAmount / 100 : 0
          }]
        }
      });
    }
  }, [product?.id]); // Only depend on ID to prevent re-tracking

  // Reset when product changes
  useEffect(() => {
    return () => {
      hasTracked.current = false;
    };
  }, [product?.id]);
}

Fix 2: Handle SPA Navigation

Track page views on route changes:

// Next.js App Router
'use client';

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

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

  useEffect(() => {
    const url = pathname + (searchParams.toString() ? `?${searchParams.toString()}` : '');

    // Clear previous ecommerce data
    window.dataLayer?.push({ ecommerce: null });

    // Track page view
    window.gtag?.('config', 'G-XXXXXXXXXX', {
      page_path: url
    });

  }, [pathname, searchParams]);

  return null;
}
// Vue Router
import { watch } from 'vue';
import { useRoute } from 'vue-router';

export function usePageTracking() {
  const route = useRoute();

  watch(
    () => route.fullPath,
    (newPath) => {
      window.dataLayer?.push({ ecommerce: null });
      window.gtag?.('config', 'G-XXXXXXXXXX', {
        page_path: newPath
      });
    }
  );
}

Fix 3: Prevent Duplicate Events

Use refs and keys to prevent re-tracking:

// Prevent duplicate view_item events
export function ProductPage({ product }: Props) {
  const trackedProductId = useRef<string | null>(null);

  useEffect(() => {
    if (product?.id && product.id !== trackedProductId.current) {
      trackedProductId.current = product.id;
      trackViewItem(product);
    }
  }, [product?.id]);

  return (/* JSX */);
}

// Prevent duplicate purchase events
export function CheckoutSuccess({ order }: Props) {
  useEffect(() => {
    const storageKey = `purchase_tracked_${order.id}`;

    if (!sessionStorage.getItem(storageKey)) {
      sessionStorage.setItem(storageKey, 'true');
      trackPurchase(order);
    }
  }, [order.id]);

  return (/* JSX */);
}

Fix 4: Fix SSR Hydration Issues

Only run tracking on client:

// components/Tracking.tsx
'use client';

import { useEffect, useState } from 'react';

export function TrackingProvider({ children }: { children: React.ReactNode }) {
  const [isClient, setIsClient] = useState(false);

  useEffect(() => {
    setIsClient(true);

    // Initialize tracking only on client
    window.dataLayer = window.dataLayer || [];
    window.gtag = function gtag() {
      window.dataLayer.push(arguments);
    };
    window.gtag('js', new Date());
    window.gtag('config', 'G-XXXXXXXXXX');
  }, []);

  if (!isClient) {
    return <>{children}</>;
  }

  return <>{children}</>;
}

Fix 5: Debug Mode Implementation

Add debug mode to see tracking in action:

// lib/analytics.ts
const DEBUG = process.env.NODE_ENV === 'development';

export function trackEvent(eventName: string, params: Record<string, any>) {
  if (DEBUG) {
    console.group(`📊 Analytics Event: ${eventName}`);
    console.log('Parameters:', params);
    console.log('Timestamp:', new Date().toISOString());
    console.groupEnd();
  }

  if (typeof window !== 'undefined' && window.gtag) {
    window.gtag('event', eventName, params);
  }
}

export function trackEcommerce(eventName: string, ecommerceData: any) {
  if (DEBUG) {
    console.group(`🛒 E-commerce Event: ${eventName}`);
    console.log('Ecommerce Data:', ecommerceData);
    console.log('Items:', ecommerceData.items);
    console.groupEnd();
  }

  if (typeof window !== 'undefined') {
    window.dataLayer?.push({ ecommerce: null });
    window.dataLayer?.push({
      event: eventName,
      ecommerce: ecommerceData
    });
  }
}

Fix 6: Validate Commercetools Data Format

Ensure data is correctly formatted:

// utils/validateTrackingData.ts
export function validateProductForTracking(product: any): boolean {
  const errors: string[] = [];

  if (!product?.id) {
    errors.push('Missing product ID');
  }

  if (!product?.masterData?.current?.name) {
    errors.push('Missing product name');
  }

  const variant = product?.masterData?.current?.masterVariant;
  if (!variant) {
    errors.push('Missing master variant');
  }

  const price = variant?.prices?.[0];
  if (!price?.value?.centAmount) {
    errors.push('Missing price');
  }

  if (errors.length > 0) {
    console.warn('Product validation errors:', errors);
    return false;
  }

  return true;
}

// Use before tracking
if (validateProductForTracking(product)) {
  trackViewItem(product);
}

Testing Checklist

Browser Testing

  • Events fire in Chrome (check DevTools Network)
  • Events fire in Firefox
  • Events fire in Safari
  • No JavaScript errors in console

SPA Navigation Testing

  • Page views fire on initial load
  • Page views fire on navigation
  • Ecommerce data clears between products
  • No duplicate events on back/forward

Data Accuracy Testing

  • Product IDs match Commercetools
  • Prices are correct (centAmount / 100)
  • Currency codes are correct
  • Quantities are accurate

Real-Time Verification

  • Events appear in GA4 Real-Time
  • Events appear in GTM Preview
  • Events appear in Meta Test Events

Common Pitfalls

Tracking Before Data Loads

// BAD - tracks before product loads
useEffect(() => {
  trackViewItem(product); // product might be undefined
}, []);

// GOOD - waits for product
useEffect(() => {
  if (product?.id) {
    trackViewItem(product);
  }
}, [product?.id]);

Not Handling Loading States

// BAD - renders tracking component during loading
if (!product) return <Loading />;
return (
  <>
    <ProductTracking product={product} />
    <ProductDetails product={product} />
  </>
);

// GOOD - tracking component handles its own loading
return (
  <>
    {product && <ProductTracking product={product} />}
    {product ? <ProductDetails product={product} /> : <Loading />}
  </>
);
// SYS.FOOTER