Netlify CMS / Decap CMS Integrations Overview | OpsBlu Docs

Netlify CMS / Decap CMS Integrations Overview

Available analytics, tracking, and marketing integrations for Netlify CMS and Decap CMS static sites

Netlify CMS (now Decap CMS) is a Git-based headless CMS that works with static site generators. Integrating analytics and tracking requires a different approach than traditional CMSs, as content is compiled at build time. This section covers implementation strategies for static site workflows.

Available Integrations

Analytics Platforms

Google Analytics 4 (GA4)

For static sites built with Netlify CMS/Decap CMS, GA4 integration depends on your static site generator and build process:

  • Hugo - Partial templates, config.toml parameters
  • Jekyll - Include files, _config.yml variables
  • Gatsby - React components, gatsby-plugin-google-gtag
  • Next.js - Custom App component, next-seo, or GTM
  • 11ty/Eleventy - Base layout templates, JavaScript data files

Learn more about Google Analytics setup →

Tag Management

Google Tag Manager (GTM)

GTM provides centralized control over all marketing and analytics tags for static sites. Essential for teams managing multiple tracking tools without rebuilding the site for each change.

  • Build-time injection - Add GTM container to base templates
  • Script tag placement - Head and body snippets in layout files
  • Data layer configuration - Inject content metadata at build time
  • Preview deploy testing - Test tags on Netlify preview URLs

Learn more about GTM setup →

Marketing Pixels

Meta Pixel (Facebook Pixel)

Track visitor behavior and optimize Facebook/Instagram advertising campaigns on static sites:

  • Template-level implementation - Base layout or header partial
  • Static site plugin integration - Framework-specific plugins
  • GTM deployment - Manage via Tag Manager (recommended)
  • Conversion API (CAPI) - Server-side tracking via Netlify Functions

Learn more about Meta Pixel setup →

Netlify CMS / Decap CMS-Specific Considerations

Git-Based Workflow

Every change triggers a new build and deployment:

  • Template changes require rebuild - Adding/updating tracking codes means redeploying
  • Content changes trigger builds - Editorial workflow affects deployment frequency
  • Branch deploys need separate tracking - Development/staging should use test properties
  • Preview deploys complicate tracking - Unique URLs for each preview can inflate analytics

Static Site Generator Integration

Hugo

Hugo uses Go templating with partials and configuration:

# config.toml
[params]
  googleAnalytics = "G-XXXXXXXXXX"
  googleTagManager = "GTM-XXXXXXX"
  facebookPixel = "XXXXXXXXXXXXXXXX"

Implementation pattern:

  • Store tracking IDs in config.toml
  • Reference in partials: \{\{ .Site.Params.googleAnalytics \}\}
  • Conditional loading based on environment: \{\{ if not .Site.IsServer \}\}

Jekyll

Jekyll uses Liquid templating with includes and configuration:

# _config.yml
google_analytics: G-XXXXXXXXXX
google_tag_manager: GTM-XXXXXXX
facebook_pixel: XXXXXXXXXXXXXXXX

Implementation pattern:

  • Store tracking IDs in _config.yml
  • Reference in includes: \{\{ site.google_analytics \}\}
  • Conditional loading: {% if jekyll.environment == "production" %}

Gatsby

Gatsby uses React components with plugins:

// gatsby-config.js
{
  resolve: `gatsby-plugin-google-gtag`,
  options: {
    trackingIds: ["G-XXXXXXXXXX"],
    gtagConfig: {
      anonymize_ip: true,
    },
    pluginConfig: {
      head: true,
    },
  },
}

Implementation pattern:

  • Install npm packages: gatsby-plugin-google-gtag, gatsby-plugin-facebook-pixel
  • Configure in gatsby-config.js
  • Use React context for data layer values

Next.js

Next.js uses React with custom App component:

// pages/_app.js
import Script from 'next/script'

function MyApp({ Component, pageProps }) {
  return (
    <>
      <Script
        src={`https://www.googletagmanager.com/gtag/js?id=G-XXXXXXXXXX`}
        strategy="afterInteractive"
      />
      <Script id="google-analytics" strategy="afterInteractive">
        {`
          window.dataLayer = window.dataLayer || [];
          function gtag(){dataLayer.push(arguments);}
          gtag('js', new Date());
          gtag('config', 'G-XXXXXXXXXX');
        `}
      </Script>
      <Component {...pageProps} />
    </>
  )
}

Implementation pattern:

  • Use Next.js Script component for optimal loading
  • Environment variables for tracking IDs: process.env.NEXT_PUBLIC_GA_ID
  • Separate tracking for preview/production deployments

Netlify Platform Considerations

Deploy Contexts

Netlify provides different deploy contexts - configure tracking accordingly:

# netlify.toml
[context.production.environment]
  GATSBY_GA_TRACKING_ID = "G-PRODUCTION-ID"
  GATSBY_GTM_ID = "GTM-PROD-XXX"

[context.deploy-preview.environment]
  GATSBY_GA_TRACKING_ID = "G-STAGING-ID"
  GATSBY_GTM_ID = "GTM-DEV-XXX"

[context.branch-deploy.environment]
  GATSBY_GA_TRACKING_ID = "G-STAGING-ID"
  GATSBY_GTM_ID = "GTM-DEV-XXX"

Deploy contexts:

  • Production - Main branch, live site tracking
  • Deploy Preview - Pull request previews, test tracking property
  • Branch Deploy - Feature branches, development tracking

Preview Deploy Tracking

Preview URLs (e.g., deploy-preview-123--site.netlify.app) should use separate analytics properties:

Why separate tracking:

  • Prevents preview traffic from inflating production analytics
  • Allows testing tracking implementation before merge
  • Isolates editorial workflow testing from real user data

Implementation:

// Conditional tracking based on URL
const isPreview = window.location.hostname.includes('deploy-preview');
const gaId = isPreview ? 'G-STAGING-ID' : 'G-PRODUCTION-ID';

Editorial Workflow

Netlify CMS editorial workflow creates branches for content review:

Editorial workflow stages:

  1. Draft - Content created, saved in CMS
  2. In Review - Pull request created, preview deploy generated
  3. Ready - Approved, merged to main branch
  4. Published - Main branch deployed to production

Tracking implications:

  • Draft stage - No deployment, no tracking
  • In review - Preview deploy with test tracking
  • Published - Production deploy with live tracking

Best practices:

  • Test tracking on preview deploys before merging
  • Use branch deploys to validate event tracking
  • Review Analytics Real-Time reports after merge

Branch Deploys and Staging

# netlify.toml - Deploy all branches to separate URLs
[build]
  publish = "public"
  command = "hugo"

# Deploy every branch (enables testing tracking changes)
[context.branch-deploy]
  command = "hugo --buildDrafts --buildFuture"

Branch deploy strategy:

  • Main branch - Production tracking IDs
  • Develop branch - Staging tracking IDs
  • Feature branches - Test tracking or no tracking

Implementation Approaches

1. Template-Based (Most Common)

Best for: All static site generators, full control, no build overhead

Pros:

  • Complete control over implementation
  • Minimal build time impact
  • No additional dependencies
  • Easy to customize per environment

Cons:

  • Requires template editing
  • Must rebuild to update tracking codes
  • Manual implementation for each site generator
  • No GUI for non-technical users

2. Plugin-Based (Framework-Specific)

Best for: Gatsby, Next.js, 11ty - frameworks with plugin ecosystems

Pros:

  • Easy configuration via config files
  • Automatic optimization (script loading, defer/async)
  • Community-maintained updates
  • Often includes helper functions

Cons:

  • Limited to frameworks with plugin systems
  • Adds build dependencies
  • May not support all tracking features
  • Plugin updates can break builds

3. Google Tag Manager (Most Flexible)

Best for: Marketing teams, multiple tracking tools, frequent changes

Pros:

  • No rebuild needed for tag changes
  • Centralized tag management
  • Version control and rollback within GTM
  • Built-in debugging tools

Cons:

  • Initial GTM container setup requires rebuild
  • Requires GTM knowledge
  • Additional container load time
  • Data layer must be configured at build time

4. Build Hooks + Netlify Functions

Best for: Advanced use cases, server-side tracking, dynamic tracking

Pros:

  • Server-side event tracking
  • Dynamic tracking without rebuilding
  • Conversion API support
  • Enhanced privacy compliance

Cons:

  • Complex setup
  • Requires serverless function knowledge
  • Netlify Functions usage limits
  • Additional infrastructure

Static Site-Specific Tracking Challenges

Pre-Rendered Content

Static sites generate HTML at build time:

  • Dynamic data must be injected client-side
  • User-specific tracking requires JavaScript
  • Content metadata embedded in data layer at build time
  • No server-side variables - can't track logged-in users server-side

Client-Side Routing (SPA)

Frameworks like Gatsby and Next.js use client-side routing:

  • Virtual pageviews must be tracked on route changes
  • History API navigation doesn't trigger page loads
  • Framework-specific listeners needed for route changes

Gatsby example:

// gatsby-browser.js
export const location }) => {
  if (typeof window !== 'undefined' && window.gtag) {
    window.gtag('event', 'page_view', {
      page_path: location.pathname + location.search,
    });
  }
};

Next.js example:

// pages/_app.js
import { useRouter } from 'next/router';
import { useEffect } from 'react';

function MyApp({ Component, pageProps }) {
  const router = useRouter();

  useEffect(() => {
    const handleRouteChange = (url) => {
      window.gtag('config', 'G-XXXXXXXXXX', {
        page_path: url,
      });
    };

    router.events.on('routeChangeComplete', handleRouteChange);
    return () => {
      router.events.off('routeChangeComplete', handleRouteChange);
    };
  }, [router.events]);

  return <Component {...pageProps} />;
}

Build-Time vs Runtime

Build-time (during deployment):

  • Template rendering
  • Content compilation
  • Environment variable injection
  • Asset optimization

Runtime (in user's browser):

  • Event tracking
  • User interactions
  • Dynamic data layer values
  • Cookie consent management

What goes where:

  • Tracking IDs → Build-time (environment variables)
  • Container code → Build-time (templates)
  • Event tracking → Runtime (JavaScript)
  • User properties → Runtime (cannot be pre-rendered)

Data Privacy and Compliance

// Load consent management based on build context
const consentScriptUrl = process.env.GATSBY_DEPLOY_CONTEXT === 'production'
  ? '/scripts/cookie-consent.js'
  : '/scripts/cookie-consent-dev.js';

GDPR Compliance for Static Sites

  • Consent before tracking - Load analytics only after user consent
  • Cookie banners - Implement via static site framework or third-party service
  • Anonymize IPs - Configure in tracking initialization
  • Data layer privacy - Don't include PII in build-time data layer

Cookie consent integration:

// Wait for consent before loading GA4
document.addEventListener('cookieConsentAccepted', function() {
  const script = document.createElement('script');
  script.src = 'https://www.googletagmanager.com/gtag/js?id=G-XXXXXXXXXX';
  script.async = true;
  document.head.appendChild(script);

  window.dataLayer = window.dataLayer || [];
  function gtag(){dataLayer.push(arguments);}
  gtag('js', new Date());
  gtag('config', 'G-XXXXXXXXXX', {
    'anonymize_ip': true
  });
});

Testing and Validation

Preview Deploy Testing

Test tracking implementation on Netlify preview deploys:

  1. Create test branch - Make tracking changes in feature branch
  2. Commit and push - Triggers preview deploy
  3. Test on preview URL - Verify tracking on deploy-preview-XXX--site.netlify.app
  4. Check Analytics - Confirm events appear in test GA4 property
  5. Merge to main - Deploy to production with confidence

Browser Extensions

  • Google Analytics Debugger - Console logging for GA hits
  • Meta Pixel Helper - Validate Facebook Pixel implementation
  • Tag Assistant - Debug Google tags (GA, GTM, Ads)
  • ObservePoint - Automated tag auditing

Framework-Specific Testing

  • Gatsby - Run gatsby develop locally, check browser console
  • Hugo - Run hugo server with --disableFastRender flag
  • Jekyll - Run bundle exec jekyll serve with --livereload
  • Next.js - Run npm run dev, check Network tab for tracking hits

Common Issues

See Troubleshooting → Tracking Issues for static site-specific debugging.

Performance Optimization

Resource Hints for Static Sites

<!-- Add to base template head -->
<link rel="preconnect" href="https://www.google-analytics.com">
<link rel="preconnect" href="https://www.googletagmanager.com">
<link rel="dns-prefetch" href="//connect.facebook.net">

Script Loading Strategies

Hugo partial:

{{ if not .Site.IsServer }}
<script async src="https://www.googletagmanager.com/gtag/js?id={{ .Site.Params.googleAnalytics }}"></script>
{{ end }}

Jekyll include:

{% unless jekyll.environment == "development" %}
<script async src="https://www.googletagmanager.com/gtag/js?id={{ site.google_analytics }}"></script>
{% endunless %}

Gatsby plugin (automatic):

// gatsby-config.js - plugin handles async loading
{
  resolve: `gatsby-plugin-google-gtag`,
  options: {
    trackingIds: ["G-XXXXXXXXXX"],
    pluginConfig: {
      head: true, // Load in <head> but asynchronously
    },
  },
}

Conditional Loading

Only load tracking on production builds:

// Next.js
const isProd = process.env.NODE_ENV === 'production';

{isProd && (
  <Script
    src={`https://www.googletagmanager.com/gtag/js?id=${process.env.NEXT_PUBLIC_GA_ID}`}
    strategy="afterInteractive"
  />
)}

Netlify-Specific Features

Build Plugins

Use Netlify Build Plugins to inject tracking automatically:

# netlify.toml
[[plugins]]
  package = "@netlify/plugin-gatsby"

[[plugins]]
  package = "netlify-plugin-inline-critical-css"

# Custom plugin to inject environment-specific tracking
[[plugins]]
  package = "./plugins/inject-analytics"

  [plugins.inputs]
    gaId = "G-XXXXXXXXXX"
    gtmId = "GTM-XXXXXXX"

Environment Variables

Manage tracking IDs via Netlify UI without code changes:

Site Settings → Build & Deploy → Environment → Environment variables

GATSBY_GA_ID = G-XXXXXXXXXX
GATSBY_GTM_ID = GTM-XXXXXXX
NEXT_PUBLIC_FB_PIXEL = XXXXXXXXXXXXXXXX

Netlify Functions for Server-Side Tracking

Implement Conversion API or Measurement Protocol:

// netlify/functions/track-conversion.js
exports.handler = async (event) => {
  const { eventName, eventData } = JSON.parse(event.body);

  // Send to GA4 Measurement Protocol
  const response = await fetch(
    `https://www.google-analytics.com/mp/collect?measurement_id=${process.env.GA_MEASUREMENT_ID}&api_secret=${process.env.GA_API_SECRET}`,
    {
      method: 'POST',
      body: JSON.stringify({
        client_id: eventData.clientId,
        events: [{
          name: eventName,
          params: eventData.params
        }]
      })
    }
  );

  return {
    statusCode: 200,
    body: JSON.stringify({ success: true })
  };
};

Next Steps

Choose your integration path:

  1. Google Analytics 4 - Setup Guide
  2. Google Tag Manager - Installation Guide
  3. Meta Pixel - Implementation Guide