Skip to content

Best Practices

This guide covers patterns and recommendations for implementing Campaign Cart analytics across your application.

Auto mode handles most tracking automatically:

window.nextConfig = {
apiKey: 'your-api-key',
analytics: {
enabled: true,
mode: 'auto' // Automatic tracking
}
};

Auto Mode Tracks:

  • dl_user_data - Every page load
  • dl_view_item_list - Collection/category pages
  • dl_add_to_cart - Cart additions
  • dl_begin_checkout - Checkout initiation

Manual Tracking Required:

  • Purchase events (order confirmation page)
  • Login/signup events
  • Custom business events

The storeName parameter is required for Facebook Pixel purchase deduplication. Without it, duplicate purchases may be reported:

window.nextConfig = {
apiKey: 'your-api-key',
storeName: 'my-store', // Required for Facebook deduplication
analytics: {
enabled: true,
providers: {
facebook: {
enabled: true,
settings: { pixelId: 'YOUR_PIXEL_ID' }
}
}
}
};

Use a consistent, unique store identifier across all environments to prevent duplicate purchase attribution in Facebook Ads Manager.

Enable debug mode only during development to see detailed console logs without polluting production data:

window.nextConfig = {
apiKey: 'your-api-key',
analytics: {
enabled: true,
debug: process.env.NODE_ENV === 'development' // Enable only in dev
}
};

Debug Mode Shows:

  • Event names and data
  • Provider routing decisions
  • Validation errors
  • Transform function results

Use environment variables to manage different configurations across environments:

window.nextConfig = {
apiKey: process.env.REACT_APP_API_KEY,
analytics: {
enabled: process.env.REACT_APP_ANALYTICS_ENABLED === 'true',
debug: process.env.NODE_ENV === 'development',
mode: 'auto',
providers: {
gtm: { enabled: true },
facebook: {
enabled: true,
settings: { pixelId: process.env.REACT_APP_FACEBOOK_PIXEL_ID }
}
}
}
};

Adopt a consistent naming convention across all custom events. Use snake_case (lowercase with underscores):

// Good - Consistent snake_case
'newsletter_subscribe'
'video_completed'
'form_submitted'
'feature_enabled'
'product_reviewed'
'wishlist_added'
// Avoid - Inconsistent formats
'subscribeNewsletter' // camelCase
'VideoCompleted' // PascalCase
'form-submitted' // kebab-case
'FEATURE_ENABLED' // UPPER_CASE

Follow these naming patterns to make event names self-documenting:

// Format: [verb]_[noun]
'newsletter_subscribe'
'video_play'
'form_submit'
'product_add'
'account_create'
'payment_process'

Track purchase events on the order confirmation page immediately when the page loads, before users navigate away:

// On order confirmation page
window.addEventListener('DOMContentLoaded', () => {
// Get order data from page or API
const orderData = window.__ORDER_DATA__ || getOrderFromAPI();
if (orderData) {
// Track immediately
next.trackPurchase({
id: orderData.orderId,
total: orderData.total,
currency: orderData.currency,
items: orderData.items
});
}
});

Users often navigate away quickly. Track immediately to capture the event before they leave.

For events that might cause navigation, track before the navigation occurs:

// Bad - Event might not send before navigation
document.getElementById('checkout-btn').addEventListener('click', () => {
next.trackBeginCheckout();
window.location.href = '/checkout'; // May interrupt tracking
});
// Good - Track with callback
document.getElementById('checkout-btn').addEventListener('click', (e) => {
e.preventDefault();
next.trackBeginCheckout();
// Give the event time to send
setTimeout(() => {
window.location.href = '/checkout';
}, 100);
});
// Better - Use async tracking
document.getElementById('checkout-btn').addEventListener('click', async (e) => {
e.preventDefault();
await window.NextAnalytics.trackAsync({
event: 'checkout_initiated',
from_page: window.location.pathname
});
window.location.href = '/checkout';
});

Provide context data with custom events:

// Good - With context
window.NextAnalytics.track({
event: 'video_completed',
video_id: 'intro-video',
video_title: 'Product Introduction',
video_duration: 120,
video_category: 'onboarding',
user_watched_percent: 100,
completion_time_seconds: 118,
playback_quality: '720p'
});
// Avoid - Minimal context
window.NextAnalytics.track({
event: 'video_completed'
// Missing critical context
});

For multi-step flows, track key checkpoints using consistent flow IDs:

// Generate a unique flow ID
const flowId = generateUUID();
// Track flow start
window.NextAnalytics.track({
event: 'checkout_flow_started',
flow_id: flowId,
entry_point: 'cart_page',
cart_value: cartTotal,
item_count: cartItems.length
});
// Track each step
window.NextAnalytics.track({
event: 'checkout_step_completed',
flow_id: flowId,
step: 'shipping_info',
step_number: 1,
duration_ms: Date.now() - stepStartTime
});
// Track completion or abandonment
window.NextAnalytics.track({
event: 'checkout_flow_completed',
flow_id: flowId,
steps_completed: 4,
total_duration_ms: Date.now() - flowStartTime,
conversion: true
});

Capture error events for debugging and monitoring:

// Track checkout errors
window.NextAnalytics.track({
event: 'checkout_error',
error_code: 'PAYMENT_DECLINED',
error_message: 'Your card was declined',
step: 'payment_processing',
attempted_amount: 99.99
});
// Track form validation failures
window.NextAnalytics.track({
event: 'form_validation_error',
form_id: 'contact_form',
field_errors: ['email', 'phone'],
error_count: 2
});
// Track API failures
window.NextAnalytics.track({
event: 'api_call_failed',
endpoint: '/api/orders',
status_code: 500,
retry_attempt: 1,
error_type: 'server_error'
});

Never let analytics errors break your application functionality:

function trackAnalyticsEvent(eventName, eventData) {
try {
window.NextAnalytics.track({
event: eventName,
...eventData
});
} catch (error) {
// Log the error for monitoring
console.error('Analytics tracking failed:', {
event: eventName,
error: error.message,
stack: error.stack
});
// Don't re-throw - continue with app functionality
// Consider sending to error tracking service
if (window.errorReporter) {
window.errorReporter.captureException(error);
}
}
}
// Usage
trackAnalyticsEvent('form_submitted', {
form_id: 'contact',
fields_count: 5
});

Ensure the app works even if analytics fails:

// Wrap the entire initialization
try {
window.nextConfig = {
apiKey: 'your-api-key',
analytics: { enabled: true }
};
} catch (error) {
console.warn('Analytics initialization failed:', error);
// App continues to work without analytics
}
// For critical tracking, use a wrapper
async function trackPurchaseWithFallback(orderData) {
try {
await window.NextAnalytics.trackAsync({
event: 'purchase',
...orderData
});
} catch (error) {
console.error('Failed to track purchase:', error);
// Fallback: Send to error service
if (window.errorReporter) {
window.errorReporter.captureException(error, {
tags: { event: 'purchase_tracking_failed' },
extra: { orderData }
});
}
}
}

For multiple events in quick succession, batch them together:

// Avoid - Multiple individual calls
items.forEach(item => {
window.NextAnalytics.track({
event: 'item_viewed',
item_id: item.id
});
});
// Better - Batch in a single event
window.NextAnalytics.track({
event: 'items_batch_viewed',
items: items.map(item => ({
item_id: item.id,
item_title: item.title
})),
batch_size: items.length,
timestamp: Date.now()
});

For non-critical tracking, defer analytics initialization:

// Defer analytics to idle time
if ('requestIdleCallback' in window) {
requestIdleCallback(() => {
initializeAnalytics();
});
} else {
// Fallback for browsers without requestIdleCallback
setTimeout(initializeAnalytics, 2000);
}
function initializeAnalytics() {
window.nextConfig = {
apiKey: 'your-api-key',
analytics: { enabled: true }
};
}

Choose the API that matches your timing needs:

// Immediate tracking (synchronous)
next.trackAddToCart('item-123', 1);
// For critical events that must complete
await window.NextAnalytics.trackAsync({
event: 'purchase_confirmation',
order_id: 'ORD-12345'
});
// For background tracking
window.NextAnalytics.track({
event: 'page_interaction',
interaction_type: 'scroll'
// Fires in background, doesn't block
});

For high-frequency events, debounce to reduce overhead:

// Debounce scroll tracking
let scrollTimeout;
window.addEventListener('scroll', () => {
clearTimeout(scrollTimeout);
scrollTimeout = setTimeout(() => {
window.NextAnalytics.track({
event: 'page_scrolled',
scroll_depth: (window.scrollY / document.body.scrollHeight) * 100
});
}, 1000); // Track once per second max
});
// Debounce resize events
let resizeTimeout;
window.addEventListener('resize', () => {
clearTimeout(resizeTimeout);
resizeTimeout = setTimeout(() => {
window.NextAnalytics.track({
event: 'viewport_changed',
width: window.innerWidth,
height: window.innerHeight
});
}, 500);
});

Use EventBuilder for e-commerce events to ensure GA4 compliance:

import { EventBuilder, dataLayer } from '@/utils/analytics';
// Track wishlist addition
const item = {
id: 'pkg-123',
title: 'Premium Package',
price: 99.99,
category: 'packages'
};
const event = EventBuilder.createEvent('wishlist_add', {
ecommerce: {
currency: EventBuilder.getCurrency(),
items: [EventBuilder.formatEcommerceItem(item, 0)]
}
});
dataLayer.push(event);

2. Use Transform Functions for Data Enrichment

Section titled “2. Use Transform Functions for Data Enrichment”

Enrich events globally without modifying every tracking call:

import { dataLayer } from '@/utils/analytics';
// Add context to all events
dataLayer.setTransformFunction((event) => {
// Add app metadata
event.app_version = window.APP_VERSION;
event.environment = window.ENV;
// Add user context
if (window.currentUser) {
event.user_id = window.currentUser.id;
event.user_tier = window.currentUser.tier;
}
// Add session data
event.session_duration = Date.now() - window.sessionStartTime;
return event;
});

Use transform functions to filter out unwanted events:

dataLayer.setTransformFunction((event) => {
// Filter out internal events in production
const internalEvents = ['internal_test', 'dev_event'];
if (internalEvents.includes(event.event) && window.ENV === 'production') {
return null; // Event won't be sent
}
// Filter test users
if (event.user_properties?.customer_email?.includes('@test.com')) {
return null;
}
return event;
});

Enable event validation to catch issues early:

if (process.env.NODE_ENV === 'development') {
import { EventValidator } from '@/utils/analytics';
const validator = new EventValidator(true);
// Create a wrapper function
const trackWithValidation = (eventName, eventData) => {
const event = {
event: eventName,
...eventData
};
// Validate before tracking
const result = validator.validateEvent(event);
if (!result.valid) {
console.error('Invalid event:', {
event: eventName,
errors: result.errors,
data: eventData
});
return; // Don't send invalid events
}
window.NextAnalytics.track(event);
};
// Use throughout app
window.trackWithValidation = trackWithValidation;
}
window.nextConfig = {
analytics: {
providers: {
gtm: {
enabled: true
// Optional: custom settings
}
}
}
};
window.nextConfig = {
storeName: 'my-store', // Critical for deduplication
analytics: {
providers: {
facebook: {
enabled: true,
settings: {
pixelId: 'YOUR_PIXEL_ID'
}
}
}
}
};
window.nextConfig = {
analytics: {
providers: {
rudderstack: {
enabled: true,
settings: {
writeKey: 'YOUR_WRITE_KEY',
dataPlaneURL: 'https://your-dataplane.rudderstack.com'
}
}
}
}
};
window.nextConfig = {
analytics: {
providers: {
custom: {
enabled: true,
settings: {
endpoint: 'https://api.yourapp.com/events',
batchSize: 10,
flushInterval: 5000
}
}
}
}
};

Use this checklist when testing analytics implementation:

// 1. Verify initialization
console.assert(
typeof window.NextAnalytics !== 'undefined',
'NextAnalytics not initialized'
);
// 2. Check debug mode
window.NextAnalytics.setDebugMode(true);
// 3. Test simple event
window.NextAnalytics.track({
event: 'test_event',
timestamp: new Date().toISOString()
});
// 4. Test add to cart
next.trackAddToCart('test-item', 1);
// 5. Test begin checkout
next.trackBeginCheckout();
// 6. Verify in console
const status = window.NextAnalytics.getStatus();
console.log('Analytics status:', status);

Monitor events in real-time using the browser console:

// Enable debug mode
window.NextAnalytics.setDebugMode(true);
// Monitor all events
window.addEventListener('NextAnalyticsEvent', (event) => {
console.log('Event tracked:', event.detail);
});
// Check data layer
console.log('Data layer:', window.NextDataLayer);
// Verify provider routing
const status = window.NextAnalytics.getStatus();
console.table(status.providers);

Test the complete purchase flow:

// Simulate checkout initiation
window.addEventListener('DOMContentLoaded', () => {
// Step 1: View items
next.trackViewItem('item-1');
next.trackViewItem('item-2');
// Step 2: Add to cart
next.trackAddToCart('item-1', 1);
next.trackAddToCart('item-2', 2);
// Step 3: Begin checkout
next.trackBeginCheckout();
// Step 4: Track purchase
next.trackPurchase({
id: 'ORDER_' + Date.now(),
total: 199.99,
currency: 'USD',
items: [
{ id: 'item-1', title: 'Item 1', price: 99.99 },
{ id: 'item-2', title: 'Item 2', price: 100.00 }
]
});
});

Test error handling:

// Test error tracking
window.NextAnalytics.track({
event: 'payment_error',
error_code: 'CARD_DECLINED',
error_message: 'Your card was declined',
attempted_amount: 99.99
});
// Test recovery
window.NextAnalytics.track({
event: 'payment_retry',
retry_attempt: 2,
original_error: 'CARD_DECLINED'
});
// Verify error is tracked but doesn't break app
console.assert(
document.body !== null,
'App broken after tracking error'
);

Before deploying analytics to production, verify:

  • Analytics enabled in production config
  • Debug mode disabled (set based on environment)
  • storeName set for Facebook Pixel
  • All required API keys configured via environment variables
  • Mode set to ‘auto’ (unless specific reason for manual)
  • All required providers enabled
  • Custom webhook endpoints verified if using custom provider

Track complete user flows with contextual data:

class UserJourneyTracker {
constructor() {
this.journeyId = this.generateId();
this.events = [];
this.startTime = Date.now();
}
generateId() {
return `journey_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
}
trackStep(stepName, stepData = {}) {
const eventData = {
event: `journey_${stepName}`,
journey_id: this.journeyId,
step_timestamp: Date.now(),
elapsed_time: Date.now() - this.startTime,
...stepData
};
window.NextAnalytics.track(eventData);
this.events.push(eventData);
}
complete(metadata = {}) {
this.trackStep('completed', {
total_duration: Date.now() - this.startTime,
total_events: this.events.length,
...metadata
});
}
abandon(reason, metadata = {}) {
this.trackStep('abandoned', {
abandon_reason: reason,
last_step: this.events[this.events.length - 1]?.event,
...metadata
});
}
}
// Usage
const journey = new UserJourneyTracker();
journey.trackStep('initiated', { entry_point: 'homepage' });
journey.trackStep('browsing', { category_viewed: 'premium-packages' });
journey.trackStep('added_to_cart', { product_id: 'pkg-123' });
journey.complete({ conversion: true });

Track feature flag usage:

function trackFeatureUsage(featureName, enabled, metadata = {}) {
window.NextAnalytics.track({
event: 'feature_flag_evaluated',
feature_name: featureName,
enabled: enabled,
timestamp: Date.now(),
...metadata
});
}
// Usage
if (shouldEnableNewCheckout()) {
trackFeatureUsage('new_checkout_ui', true, {
variant: 'experimental',
user_segment: 'premium'
});
renderNewCheckout();
} else {
trackFeatureUsage('new_checkout_ui', false, {
reason: 'user_segment_not_matched'
});
renderLegacyCheckout();
}

Track conversion funnel steps:

class FunnelTracker {
constructor(funnelName) {
this.funnelName = funnelName;
this.funnelId = `funnel_${Date.now()}`;
this.steps = [];
}
trackStep(stepName, stepNumber, metadata = {}) {
const event = {
event: `funnel_step`,
funnel_name: this.funnelName,
funnel_id: this.funnelId,
step_name: stepName,
step_number: stepNumber,
steps_completed: stepNumber,
...metadata
};
window.NextAnalytics.track(event);
this.steps.push(event);
}
trackDropoff(stepName, stepNumber, reason, metadata = {}) {
window.NextAnalytics.track({
event: `funnel_dropoff`,
funnel_name: this.funnelName,
funnel_id: this.funnelId,
dropped_at_step: stepName,
step_number: stepNumber,
dropoff_reason: reason,
steps_completed: stepNumber - 1,
...metadata
});
}
}
// Usage
const checkoutFunnel = new FunnelTracker('checkout');
checkoutFunnel.trackStep('shipping', 1, { state_entered: 'CA' });
checkoutFunnel.trackStep('payment', 2, { payment_method: 'credit_card' });
checkoutFunnel.trackDropoff('confirmation', 3, 'payment_failed', {
error_code: 'CARD_DECLINED'
});

Following these practices:

  • Events reach their destinations consistently
  • Events contain context for analysis
  • Analytics doesn’t slow down your application
  • Consistent patterns make code easier to understand
  • Error handling and configuration provide stable systems

Review this guide regularly and update your implementation as needed.