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
- Open DevTools → Network tab
- Filter by
google-analytics.comorfacebook.com - Trigger an event
- 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
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 />}
</>
);
Related Resources
- Global Events Not Firing Guide - Universal debugging
- E-commerce Tracking Issues - Revenue tracking problems
- Server-Side Tracking - Alternative approach