Tracking issues on static sites often relate to build-time configuration, environment variables, or client-side JavaScript execution. This guide helps debug GA4, GTM, and Meta Pixel tracking problems specific to Netlify CMS sites.
Common Tracking Issues
1. No Tracking on Production Site
Symptom: Tracking works locally but not on deployed site.
Diagnosis Checklist:
- Check browser console for errors
- Verify tracking scripts load (Network tab)
- Confirm environment variables set in Netlify
- Check for ad blocker interference
- Verify tracking ID is correct for environment
Solutions:
Check Environment Variables
# .env.production must have GATSBY_ prefix
GATSBY_GA_ID=G-XXXXXXXXXX # ✓ Works
GA_ID=G-XXXXXXXXXX # ✗ Doesn't work
Verify in Netlify UI:
Site Settings → Build & Deploy → Environment
GATSBY_GA_ID = G-XXXXXXXXXX
# Must have NEXT_PUBLIC_ prefix
NEXT_PUBLIC_GA_ID=G-XXXXXXXXXX # ✓ Works
GA_ID=G-XXXXXXXXXX # ✗ Doesn't work
Hugo/Jekyll:
# netlify.toml
[context.production.environment]
HUGO_GA_ID = "G-XXXXXXXXXX"
Verify Build Logs
Check Netlify deploy log:
- Netlify Dashboard → Site → Deploys
- Click latest deploy
- View deploy log
- Search for tracking ID
- Verify no build errors
2. Tracking Only Works on Localhost
Symptom: Events fire during development but not in production.
Common Causes:
- Development-only conditional
- Missing production environment variable
- Ad blocker only active on certain domains
Hugo Fix:
<!-- Before (only works in dev) -->
{{ if .Site.IsServer }}
{{ partial "analytics.html" . }}
{{ end }}
<!-- After (works in production) -->
{{ if not .Site.IsServer }}
{{ partial "analytics.html" . }}
{{ end }}
Jekyll Fix:
<!-- Before (only loads in development) -->
{% if jekyll.environment == "development" %}
{% include analytics.html %}
{% endif %}
<!-- After (loads in production) -->
{% if jekyll.environment == "production" %}
{% include analytics.html %}
{% endif %}
Gatsby/Next.js Fix:
// Before (excludes production)
if (process.env.NODE_ENV !== 'production') {
// Load analytics
}
// After (only in production)
if (process.env.NODE_ENV === 'production') {
// Load analytics
}
3. Preview Deploys Don't Track
Symptom: Production works, but preview deploys don't fire events.
Expected Behavior: Preview deploys should use staging/test tracking IDs.
Solution: Environment-Specific IDs
Netlify.toml:
[context.production.environment]
GATSBY_GA_ID = "G-PRODUCTION-ID"
[context.deploy-preview.environment]
GATSBY_GA_ID = "G-STAGING-ID"
[context.branch-deploy.environment]
GATSBY_GA_ID = "G-STAGING-ID"
Client-side detection:
// Detect environment by URL
const hostname = window.location.hostname;
const isProduction = hostname === 'www.yoursite.com';
const isPreview = hostname.includes('deploy-preview');
const isBranchDeploy = hostname.includes('--') && !isPreview;
let gaId;
if (isProduction) {
gaId = 'G-PRODUCTION-ID';
} else if (isPreview || isBranchDeploy) {
gaId = 'G-STAGING-ID';
} else {
gaId = null; // localhost
}
if (gaId) {
gtag('config', gaId);
}
4. GTM Container Not Loading
Symptom: google_tag_manager is undefined, no tags fire.
Diagnosis:
// Check if GTM loaded
console.log(typeof google_tag_manager); // Should be 'object'
console.log(window.dataLayer); // Should be array
Common Causes:
Missing GTM ID
// Hugo
{{ with getenv "HUGO_GTM_ID" }}
// GTM code here
{{ end }}
// If HUGO_GTM_ID is not set, nothing loads
Fix: Set environment variable in Netlify.
Syntax Error in GTM Code
<!-- Check for typos -->
<script>
(function(w,d,s,l,i){w[l]=w[l]||[];...})(window,document,'script','dataLayer','GTM-XXXXXX');
// Make sure all parameters are correct
</script>
CSP (Content Security Policy) Blocking
<!-- If CSP headers too restrictive -->
<meta http-equiv="Content-Security-Policy" content="default-src 'self'">
<!-- GTM won't load -->
<!-- Fix: Allow GTM domains -->
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self' 'unsafe-inline' https://www.googletagmanager.com; img-src 'self' https://www.google-analytics.com; connect-src 'self' https://www.google-analytics.com">
5. Data Layer Variables Undefined
Symptom: GTM variables show "undefined" in preview mode.
Diagnosis:
// Check data layer
console.log(window.dataLayer);
// Look for your variables
window.dataLayer.forEach((item, index) => {
console.log(`[${index}]`, item);
});
Common Causes:
Data Layer After GTM
<!-- Wrong order -->
<script>
(function(w,d,s,l,i){...})(window,document,'script','dataLayer','GTM-XXXXXX');
</script>
<script>
window.dataLayer = window.dataLayer || [];
window.dataLayer.push({
'pageType': 'blog_post'
});
</script>
Fix: Data layer BEFORE GTM
<script>
window.dataLayer = window.dataLayer || [];
window.dataLayer.push({
'pageType': 'blog_post'
});
</script>
<script>
(function(w,d,s,l,i){...})(window,document,'script','dataLayer','GTM-XXXXXX');
</script>
Incorrect Variable Names
// Data layer
window.dataLayer.push({
'contentCategory': 'tutorials' // camelCase
});
// GTM Variable configuration
// Data Layer Variable Name: content_category // ✗ Wrong
// Data Layer Variable Name: contentCategory // ✓ Correct
Template Syntax Errors
Hugo:
<!-- Wrong -->
{{ .Params.author }} <!-- Error if author is nil -->
<!-- Right -->
{{ with .Params.author }}{{ . }}{{ end }}
Jekyll:
<!-- Wrong -->
{{ page.author }} <!-- Empty if not set -->
<!-- Right -->
{% if page.author %}
"author": "{{ page.author }}"
{% endif %}
6. Events Fire Multiple Times
Symptom: Same event fires twice or more per action.
Common Causes:
Multiple GA4 Implementations
<!-- Duplicate tracking -->
<!-- Both Site Kit plugin AND manual implementation -->
<script>
gtag('config', 'G-XXXXXXXXXX');
</script>
<!-- ... later ... -->
<script>
gtag('config', 'G-XXXXXXXXXX'); // Duplicate!
</script>
Fix: Remove duplicate implementations.
SPA Route Change Tracking
Gatsby:
// Wrong - fires on every route change including initial load
export const => {
gtag('event', 'page_view'); // Fires on initial load too
};
// Right - only on subsequent route changes
export const prevLocation }) => {
if (prevLocation) { // Skip initial load
gtag('event', 'page_view');
}
};
Next.js:
// Wrong - fires immediately on mount
useEffect(() => {
router.events.on('routeChangeComplete', handleRouteChange);
handleRouteChange(); // Fires on mount
return () => {
router.events.off('routeChangeComplete', handleRouteChange);
};
}, []);
// Right - only fires on route change
useEffect(() => {
router.events.on('routeChangeComplete', handleRouteChange);
return () => {
router.events.off('routeChangeComplete', handleRouteChange);
};
}, [router.events]);
Event Listeners Not Cleaned Up
// Wrong - adds new listener every render
function Component() {
document.querySelector('.button').addEventListener('click', () => {
gtag('event', 'button_click');
});
}
// Right - cleanup in React
function Component() {
useEffect(() => {
const button = document.querySelector('.button');
const handleClick = () => gtag('event', 'button_click');
button.addEventListener('click', handleClick);
return () => {
button.removeEventListener('click', handleClick);
};
}, []);
}
7. Meta Pixel Not Firing
Symptom: Meta Pixel Helper shows no pixel detected.
Diagnosis:
// Check if fbq loaded
console.log(typeof fbq); // Should be 'function'
// Manually fire test event
fbq('trackCustom', 'TestEvent');
Common Causes:
Ad Blocker
Ad blockers often block Facebook/Meta scripts. Test in:
- Incognito/Private mode
- Different browser
- Mobile device
Script Blocked by CSP
<!-- CSP blocking Facebook -->
<meta http-equiv="Content-Security-Policy" content="default-src 'self'">
<!-- Fix: Allow Facebook domains -->
<meta http-equiv="Content-Security-Policy" content="script-src 'self' 'unsafe-inline' https://connect.facebook.net">
Incorrect Pixel ID
// Wrong format
fbq('init', 'G-XXXXXXXXXX'); // This is a GA4 ID!
// Correct format
fbq('init', '123456789012345'); // 15-16 digit number
8. Tracking Works in Preview Mode, Not Live
Symptom: GTM preview mode shows tags firing, but not in production.
Diagnosis:
- Open GTM
- Click Preview
- Connect to your production site
- Verify tags fire in preview
- Click Submit to publish changes
- Test again on production
Common Causes:
Forgot to Publish GTM Workspace
Solution:
- GTM → Workspace
- Click Submit
- Add version name/description
- Click Publish
Environment Mismatch
Check GTM environment settings:
- Container uses production environment ID
- Preview mode uses correct environment
9. Events Fire but Don't Appear in GA4
Symptom: Network requests sent, but no data in GA4 reports.
Diagnosis:
- Open Network tab
- Filter by "collect"
- Click request
- Check payload for errors
Common Causes:
Measurement ID Mismatch
// Sending to wrong property
gtag('config', 'G-XXXXXXXXXX'); // Different from your actual property
Fix: Verify Measurement ID matches GA4 property.
Events in Debug View Only
// Debug mode enabled
gtag('config', 'G-XXXXXXXXXX', {
'debug_mode': true // Events only show in DebugView, not reports
});
Fix: Remove debug_mode for production.
Data Processing Delay
GA4 can take 24-48 hours to process data.
Immediate verification:
- Use Real-Time reports (< 30 seconds)
- Use DebugView (requires debug_mode)
Debugging Workflow
Step 1: Verify Scripts Load
// Browser console
console.log('GA4 loaded:', typeof gtag !== 'undefined');
console.log('GTM loaded:', typeof google_tag_manager !== 'undefined');
console.log('Meta Pixel loaded:', typeof fbq !== 'undefined');
Step 2: Check Network Requests
- DevTools → Network
- Filter by domain:
google-analytics.comgoogletagmanager.comfacebook.com
- Trigger event
- Verify request sent
- Check payload
Step 3: Use Browser Extensions
GA Debugger:
- Chrome Extension
- Logs all GA hits to console
Meta Pixel Helper:
- Chrome Extension
- Shows pixel status, events fired
Tag Assistant:
- Tag Assistant
- Debugs all Google tags
Step 4: GTM Preview Mode
- GTM → Preview
- Enter site URL
- Debug panel opens
- Click tags to see:
- Firing triggers
- Variable values
- Data layer state
Step 5: Check Real-Time Reports
GA4:
- Reports → Real-time
- Should see events within 30 seconds
Meta Events Manager:
- Test Events tab
- Visit site with
?fbclid=testparameter
Quick Fixes
Reset All Tracking
# Clear all caches
rm -rf node_modules .cache public
# Reinstall dependencies
npm install
# Rebuild
npm run build
# Clear Netlify cache
# Netlify UI → Site Settings → Build & deploy → Clear cache
Verify Environment Variables
# In Netlify deploy log, add debug output
# Hugo example
echo "GA ID: ${HUGO_GA_ID}"
# Gatsby example
echo "GA ID: ${GATSBY_GA_ID}"
Test Tracking Code Manually
<!-- Temporary test script -->
<script>
console.log('Testing tracking...');
console.log('GA ID:', '{{ getenv "HUGO_GA_ID" }}'); // Hugo
console.log('GA ID:', process.env.GATSBY_GA_ID); // Gatsby
// Manual GA4 test
if (typeof gtag !== 'undefined') {
console.log('GA4 loaded, sending test event');
gtag('event', 'test_event', { 'test': 'value' });
} else {
console.error('GA4 not loaded');
}
</script>
Next Steps
- Fix LCP Issues - Ensure tracking doesn't slow site
- Fix CLS Issues - Prevent layout shifts from tracking
- Global Tracking Issues - Universal debugging