How to Fix Netlify CMS / Decap CMS Events Not Firing | OpsBlu Docs

How to Fix Netlify CMS / Decap CMS Events Not Firing

Fix GA4, GTM, and pixel events not firing on Netlify CMS sites — static site build config, environment variable injection, and deploy preview debugging

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

Gatsby:

# .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

Next.js:

# 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:

  1. Netlify Dashboard → Site → Deploys
  2. Click latest deploy
  3. View deploy log
  4. Search for tracking ID
  5. 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:

  1. Open GTM
  2. Click Preview
  3. Connect to your production site
  4. Verify tags fire in preview
  5. Click Submit to publish changes
  6. Test again on production

Common Causes:

Forgot to Publish GTM Workspace

Solution:

  1. GTM → Workspace
  2. Click Submit
  3. Add version name/description
  4. 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:

  1. Open Network tab
  2. Filter by "collect"
  3. Click request
  4. 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

  1. DevTools → Network
  2. Filter by domain:
    • google-analytics.com
    • googletagmanager.com
    • facebook.com
  3. Trigger event
  4. Verify request sent
  5. Check payload

Step 3: Use Browser Extensions

GA Debugger:

Meta Pixel Helper:

Tag Assistant:

Step 4: GTM Preview Mode

  1. GTM → Preview
  2. Enter site URL
  3. Debug panel opens
  4. 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=test parameter

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