Common issues and solutions for tracking problems on PayloadCMS frontends (React/Next.js).
Common Causes
- Client-side rendering issues - Tracking code runs before DOM ready
- Server-side rendering conflicts - Accessing
windowduring SSR - Environment variables - Missing or incorrect tracking IDs
- Ad blockers - Browser extensions blocking tracking
- Timing issues - Events firing before trackers initialize
Diagnostic Steps
Step 1: Check Browser Console
Open DevTools (F12) and look for:
For GA4:
// Check if gtag is loaded
typeof window.gtag !== 'undefined'
// View dataLayer
console.log(window.dataLayer);
For Meta Pixel:
// Check if fbq is loaded
typeof window.fbq !== 'undefined'
// Check pixel status
window.fbq('getState');
For GTM:
// Check if dataLayer exists
console.log(window.dataLayer);
// Check GTM loaded
console.log(window.google_tag_manager);
Step 2: Check Network Tab
- Open DevTools > Network
- Filter by "analytics", "facebook", or "gtm"
- Reload page
- Verify tracking requests are sent
Step 3: Use Browser Extensions
- GA4: Google Tag Assistant
- Meta Pixel: Meta Pixel Helper
- GTM: Built into Tag Assistant
Issue: Tracking Not Loading
Symptom
No tracking requests in Network tab, undefined in console.
Causes & Solutions
Missing Environment Variables
Check .env.local:
# Ensure these are set
NEXT_PUBLIC_GA_ID=G-XXXXXXXXXX
NEXT_PUBLIC_META_PIXEL_ID=123456789
NEXT_PUBLIC_GTM_ID=GTM-XXXXXXX
Verify in code:
console.log('GA ID:', process.env.NEXT_PUBLIC_GA_ID);
console.log('Pixel ID:', process.env.NEXT_PUBLIC_META_PIXEL_ID);
console.log('GTM ID:', process.env.NEXT_PUBLIC_GTM_ID);
Note: Environment variables must start with NEXT_PUBLIC_ to be accessible in the browser.
Script Not Loaded
Check Script component usage:
// Correct
import Script from 'next/script';
<Script
src="https://www.googletagmanager.com/gtag/js?id=G-XXXXXXXXXX"
strategy="afterInteractive"
/>
// Wrong
<script src="https://www.googletagmanager.com/gtag/js?id=G-XXXXXXXXXX"></script>
Ensure script in correct location:
- Pages Router:
pages/_app.js - App Router:
app/layout.js
Issue: SSR Errors
Symptom
Error: window is not defined or document is not defined
Solution: Check for Client-Side Only Code
Wrap in useEffect:
import { useEffect } from 'react';
export default function MyComponent() {
useEffect(() => {
// Safe - runs only on client
if (window.gtag) {
window.gtag('event', 'page_view');
}
}, []);
return <div>Content</div>;
}
Or use typeof check:
if (typeof window !== 'undefined' && window.gtag) {
window.gtag('event', 'page_view');
}
Issue: Events Fire on Some Pages, Not Others
Symptom
Tracking works on homepage but not on dynamic routes.
Solution: Ensure Event Tracking in Each Page
Check dynamic pages have tracking:
// pages/blog/[slug].js
import { useEffect } from 'react';
export default function BlogPost({ post }) {
useEffect(() => {
// Track page view
if (window.gtag) {
window.gtag('event', 'page_view', {
page_title: post.title,
page_location: window.location.href,
});
}
}, [post]);
return <article>{post.title}</article>;
}
Issue: Duplicate Events
Symptom
Same event fires twice in analytics.
Causes & Solutions
Multiple Implementations
Check for:
- Script in
_app.jsAND_document.js - GA4 installed AND GTM with GA4 tag
- Both native implementation AND package
Remove duplicates:
// Choose ONE method:
// Option 1: Direct implementation
<Script src="https://www.googletagmanager.com/gtag/js?id=G-XXX" />
// Option 2: GTM (includes GA4 tag)
<Script src="https://www.googletagmanager.com/gtm.js?id=GTM-XXX" />
// NOT BOTH
React StrictMode
In development, React StrictMode causes double rendering.
This is expected in development:
// pages/_app.js
import React from 'react';
export default function MyApp({ Component, pageProps }) {
return (
<React.StrictMode> {/* Causes double renders in dev */}
<Component {...pageProps} />
</React.StrictMode>
);
}
Not an issue in production.
Issue: Events Fire in Development, Not Production
Symptom
Tracking works on localhost, fails on live site.
Causes & Solutions
Environment Variable Not Set in Production
For Vercel:
- Go to Project Settings > Environment Variables
- Add
NEXT_PUBLIC_GA_IDetc. - Redeploy
For Netlify:
- Site Settings > Build & Deploy > Environment
- Add variables
- Trigger new deploy
Build Cache Issue
# Clear Next.js cache and rebuild
rm -rf .next
npm run build
Issue: GTM Not Firing Tags
Symptom
GTM loads, but tags don't fire.
Diagnostic Steps
Check Preview Mode
Check Triggers
Ensure trigger conditions met:
// If trigger is "Custom Event = pageview"
// Ensure you're pushing the event:
window.dataLayer.push({
event: 'pageview', // Must match exactly
page: window.location.pathname,
});
Check Variables
Verify data layer variables populate:
// In console:
window.dataLayer.filter(item => item.event === 'yourEvent');
Issue: Ad Blockers Blocking Tracking
Symptom
Tracking works in incognito, not in normal browsing.
Solution
Ad blockers prevent tracking - this is expected behavior.
For testing:
- Disable ad blocker
- Use incognito mode
- Test on mobile device
For users:
- Accept that some traffic won't be tracked
- Consider server-side tracking (Conversions API)
Issue: Events Fire Too Early
Symptom
Events fire before tracker initialized.
Solution: Wait for Tracker
For GA4:
function trackEvent(eventName, params) {
// Wait for gtag to be defined
if (typeof window.gtag === 'undefined') {
setTimeout(() => trackEvent(eventName, params), 100);
return;
}
window.gtag('event', eventName, params);
}
For Meta Pixel:
function trackPixelEvent(eventName, params) {
if (typeof window.fbq === 'undefined') {
setTimeout(() => trackPixelEvent(eventName, params), 100);
return;
}
window.fbq('track', eventName, params);
}
Better: Use callback:
// pages/_app.js
const [analyticsReady, setAnalyticsReady] = useState(false);
useEffect(() => {
// Wait for scripts to load
const checkAnalytics = setInterval(() => {
if (window.gtag && window.fbq) {
setAnalyticsReady(true);
clearInterval(checkAnalytics);
}
}, 100);
return () => clearInterval(checkAnalytics);
}, []);
Issue: Events Fire But Don't Appear in Reports
Symptom
Events show in DebugView but not in standard reports.
Solutions
GA4 Real-Time vs Standard Reports
- Real-Time: Instant
- Standard Reports: 24-48 hour delay
Check Real-Time first:
- GA4 > Reports > Realtime
- Perform action on site
- Verify event appears
Event Name Formatting
GA4 event names:
- Must be lowercase with underscores
- Max 40 characters
- No spaces
// Correct
gtag('event', 'form_submission');
// Wrong
gtag('event', 'Form Submission'); // Spaces not allowed
gtag('event', 'formSubmission'); // CamelCase not recommended
Debugging Payload API-Related Issues
Issue: Events Not Tracking After API Calls
Check async/await:
const handleSubmit = async (e) => {
e.preventDefault();
try {
const response = await fetch(`${process.env.NEXT_PUBLIC_PAYLOAD_URL}/api/contact`, {
method: 'POST',
body: JSON.stringify(formData),
});
// Track AFTER successful response
if (response.ok) {
window.gtag('event', 'form_submission', {
form_name: 'contact',
});
}
} catch (error) {
console.error('Error:', error);
// Track error
window.gtag('event', 'form_error', {
error_message: error.message,
});
}
};
Best Practices for Reliable Tracking
1. Initialize Trackers Early
// In _app.js, before any components
useEffect(() => {
// Initialize all trackers
if (window.gtag) {
window.gtag('config', process.env.NEXT_PUBLIC_GA_ID);
}
if (window.fbq) {
window.fbq('init', process.env.NEXT_PUBLIC_META_PIXEL_ID);
}
}, []);
2. Use Defensive Coding
// Always check if tracker exists
if (typeof window !== 'undefined' && window.gtag) {
window.gtag('event', 'my_event');
}
3. Log Events in Development
function trackEvent(eventName, params) {
if (process.env.NODE_ENV === 'development') {
console.log('Tracking:', eventName, params);
}
if (window.gtag) {
window.gtag('event', eventName, params);
}
}