This guide provides step-by-step instructions for implementing Google Analytics 4 on static sites built with Netlify CMS (now Decap CMS) across different static site generators.
Prerequisites
Before installing GA4:
Create a GA4 Property in Google Analytics
- Sign in to analytics.google.com
- Create a new GA4 property
- Copy your Measurement ID (format:
G-XXXXXXXXXX)
Have a Netlify CMS site using one of these static site generators:
Set up environment variables in Netlify:
- Production Measurement ID
- Staging/Preview Measurement ID (optional but recommended)
Method 1: Hugo + GA4
Step 1: Configure Environment Variables
Add to netlify.toml:
[context.production.environment]
HUGO_GA_ID = "G-PRODUCTION-ID"
[context.deploy-preview.environment]
HUGO_GA_ID = "G-STAGING-ID"
[context.branch-deploy.environment]
HUGO_GA_ID = "G-STAGING-ID"
Step 2: Create Analytics Partial
Create layouts/partials/analytics.html:
{{ if not .Site.IsServer }}
{{ with getenv "HUGO_GA_ID" }}
<!-- Google Analytics 4 -->
<script async src="https://www.googletagmanager.com/gtag/js?id={{ . }}"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', '{{ . }}', {
'anonymize_ip': true,
'send_page_view': true
});
// Inject content metadata
{{ with $.Params.author }}
gtag('set', 'user_properties', {
'content_author': '{{ . }}'
});
{{ end }}
{{ with $.Section }}
gtag('event', 'page_view', {
'content_category': '{{ . }}'
});
{{ end }}
</script>
{{ end }}
{{ end }}
Step 3: Include in Base Template
Add to layouts/_default/baseof.html (or your base template):
<!DOCTYPE html>
<html lang="{{ .Site.Language.Lang }}">
<head>
<meta charset="UTF-8">
<title>{{ .Title }}</title>
{{ partial "analytics.html" . }}
</head>
<body>
{{ block "main" . }}{{ end }}
</body>
</html>
Step 4: Optional - Enhanced Content Tracking
Create layouts/partials/analytics-data-layer.html:
<script>
window.dataLayer = window.dataLayer || [];
window.dataLayer.push({
'page_type': '{{ .Type }}',
'content_category': '{{ .Section }}',
'publish_date': '{{ .Date.Format "2006-01-02" }}',
'word_count': {{ .WordCount }},
'reading_time': {{ .ReadingTime }},
{{ if .Params.tags }}'tags': {{ .Params.tags | jsonify }},{{ end }}
{{ if .Params.author }}'author': '{{ .Params.author }}'{{ end }}
});
</script>
Include before the main analytics partial in baseof.html.
Step 5: Deploy and Verify
# Commit changes
git add .
git commit -m "Add GA4 tracking"
git push origin main
# Netlify will rebuild and deploy
# Check Real-Time reports in GA4
Method 2: Jekyll + GA4
Step 1: Configure Tracking ID
Add to _config.yml:
google_analytics: G-XXXXXXXXXX
# Or use environment-specific config
production:
google_analytics: G-PRODUCTION-ID
development:
google_analytics: G-STAGING-ID
Step 2: Create Analytics Include
Create _includes/analytics.html:
{% if jekyll.environment == "production" %}
{% if site.google_analytics %}
<!-- Google Analytics 4 -->
<script async src="https://www.googletagmanager.com/gtag/js?id={{ site.google_analytics }}"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', '{{ site.google_analytics }}', {
'anonymize_ip': true
});
{% if page.author %}
gtag('set', 'user_properties', {
'content_author': '{{ page.author }}'
});
{% endif %}
{% if page.categories %}
gtag('event', 'page_view', {
'content_category': '{{ page.categories | first }}'
});
{% endif %}
</script>
{% endif %}
{% endif %}
Step 3: Include in Default Layout
Add to _layouts/default.html:
<!DOCTYPE html>
<html lang="{{ site.lang | default: "en-US" }}">
<head>
<meta charset="UTF-8">
<title>{{ page.title | default: site.title }}</title>
{% include analytics.html %}
</head>
<body>
{{ content }}
</body>
</html>
Step 4: Configure Netlify Environment
In Netlify UI:
Site Settings → Build & Deploy → Environment
JEKYLL_ENV = production
Or in netlify.toml:
[build]
command = "JEKYLL_ENV=production bundle exec jekyll build"
publish = "_site"
[context.production.environment]
JEKYLL_ENV = "production"
[context.deploy-preview.environment]
JEKYLL_ENV = "development"
Step 5: Deploy and Test
# Local testing (no tracking)
bundle exec jekyll serve
# Production deploy
git add .
git commit -m "Add GA4 tracking"
git push origin main
Method 3: Gatsby + GA4
Step 1: Install Plugin
npm install gatsby-plugin-google-gtag
Step 2: Configure Plugin
Add to gatsby-config.js:
module.exports = {
plugins: [
{
resolve: `gatsby-plugin-google-gtag`,
options: {
// Use environment variables
trackingIds: [
process.env.GATSBY_GA_MEASUREMENT_ID, // GA4 Measurement ID
],
// This object gets passed directly to the gtag config command
gtagConfig: {
anonymize_ip: true,
cookie_expires: 0,
},
// This object is used for configuration specific to this plugin
pluginConfig: {
// Puts tracking script in the head instead of the body
head: true,
// Setting this parameter is also optional
respectDNT: true,
// Avoids sending pageview hits from custom paths
exclude: ["/preview/**", "/do-not-track/me/too/"],
// Delays processing pageview events on route update (in milliseconds)
delayOnRouteUpdate: 0,
},
},
},
],
};
Step 3: Set Environment Variables
Create .env.production:
GATSBY_GA_MEASUREMENT_ID=G-PRODUCTION-ID
Create .env.development:
GATSBY_GA_MEASUREMENT_ID=G-STAGING-ID
Add to Netlify environment variables:
Site Settings → Build & Deploy → Environment
GATSBY_GA_MEASUREMENT_ID = G-PRODUCTION-ID
Step 4: Configure Route Change Tracking
Create gatsby-browser.js (or add to existing):
// Track page views on route changes (SPA behavior)
export const location, prevLocation }) => {
if (process.env.NODE_ENV === 'production' && typeof window.gtag === 'function') {
// Don't track initial page load (plugin handles this)
if (prevLocation) {
window.gtag('event', 'page_view', {
page_path: location.pathname + location.search,
page_location: window.location.href,
});
}
}
};
Step 5: Optional - Content Metadata Tracking
Create src/components/Analytics.js:
import { useEffect } from 'react';
const Analytics = ({ pageContext }) => {
useEffect(() => {
if (typeof window.gtag !== 'undefined') {
// Send custom dimensions
window.gtag('event', 'page_metadata', {
content_type: pageContext.type || 'page',
author: pageContext.author || 'unknown',
publish_date: pageContext.date || '',
category: pageContext.category || '',
});
}
}, [pageContext]);
return null;
};
export default Analytics;
Use in page templates:
import Analytics from '../components/Analytics';
const BlogPost = ({ data, pageContext }) => {
return (
<>
<Analytics pageContext={pageContext} />
{/* Page content */}
</>
);
};
Step 6: Build and Deploy
# Local development (uses staging ID)
gatsby develop
# Production build
gatsby build
gatsby serve
# Deploy
git add .
git commit -m "Add GA4 tracking"
git push origin main
Method 4: Next.js + GA4
Step 1: Create Analytics Component
Create lib/analytics.js:
// lib/analytics.js
export const GA_MEASUREMENT_ID = process.env.NEXT_PUBLIC_GA_ID;
// Log page views
export const pageview = (url) => {
if (typeof window.gtag !== 'undefined') {
window.gtag('config', GA_MEASUREMENT_ID, {
page_path: url,
});
}
};
// Log specific events
export const event = ({ action, category, label, value }) => {
if (typeof window.gtag !== 'undefined') {
window.gtag('event', action, {
event_category: category,
event_label: label,
value: value,
});
}
};
Step 2: Add to _app.js
Modify pages/_app.js:
import Script from 'next/script';
import { useRouter } from 'next/router';
import { useEffect } from 'react';
import * as gtag from '../lib/analytics';
function MyApp({ Component, pageProps }) {
const router = useRouter();
useEffect(() => {
const handleRouteChange = (url) => {
gtag.pageview(url);
};
router.events.on('routeChangeComplete', handleRouteChange);
return () => {
router.events.off('routeChangeComplete', handleRouteChange);
};
}, [router.events]);
return (
<>
{/* Google Analytics */}
{process.env.NODE_ENV === 'production' && (
<>
<Script
strategy="afterInteractive"
src={`https://www.googletagmanager.com/gtag/js?id=${gtag.GA_MEASUREMENT_ID}`}
/>
<Script
id="google-analytics"
strategy="afterInteractive"
dangerouslySetInnerHTML={{
__html: `
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', '${gtag.GA_MEASUREMENT_ID}', {
page_path: window.location.pathname,
anonymize_ip: true
});
`,
}}
/>
</>
)}
<Component {...pageProps} />
</>
);
}
export default MyApp;
Step 3: Environment Variables
Create .env.local:
NEXT_PUBLIC_GA_ID=G-XXXXXXXXXX
Add to Netlify:
# netlify.toml
[context.production.environment]
NEXT_PUBLIC_GA_ID = "G-PRODUCTION-ID"
[context.deploy-preview.environment]
NEXT_PUBLIC_GA_ID = "G-STAGING-ID"
Step 4: Track Events in Components
import * as gtag from '../lib/analytics';
const NewsletterForm = () => {
const handleSubmit = (e) => {
e.preventDefault();
// Track conversion
gtag.event({
action: 'generate_lead',
category: 'Newsletter',
label: 'Footer Signup',
});
// Submit form
// ...
};
return (
<form
{/* Form fields */}
</form>
);
};
Step 5: Deploy
# Development
npm run dev
# Build and test
npm run build
npm run start
# Deploy
git add .
git commit -m "Add GA4 tracking"
git push origin main
Method 5: 11ty (Eleventy) + GA4
Step 1: Configure Data File
Create _data/site.js:
module.exports = {
title: "My Site",
url: "https://example.com",
analytics: {
ga4: process.env.GA_MEASUREMENT_ID || "G-XXXXXXXXXX"
}
};
Step 2: Create Analytics Partial
Create _includes/analytics.njk (Nunjucks):
{% if site.analytics.ga4 and not env.local %}
<!-- Google Analytics 4 -->
<script async src="https://www.googletagmanager.com/gtag/js?id={{ site.analytics.ga4 }}"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', '{{ site.analytics.ga4 }}', {
'anonymize_ip': true
});
{% if author %}
gtag('set', 'user_properties', {
'content_author': '{{ author }}'
});
{% endif %}
{% if category %}
gtag('event', 'page_view', {
'content_category': '{{ category }}'
});
{% endif %}
</script>
{% endif %}
Step 3: Include in Base Layout
Modify _includes/layouts/base.njk:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>{{ title }}</title>
{% include "analytics.njk" %}
</head>
<body>
{{ content | safe }}
</body>
</html>
Step 4: Configure Environment
Add to .eleventy.js:
module.exports = function(eleventyConfig) {
// Pass environment to templates
eleventyConfig.addGlobalData("env", {
local: process.env.ELEVENTY_ENV === 'development'
});
return {
dir: {
input: "src",
output: "_site",
includes: "_includes",
data: "_data"
}
};
};
Create netlify.toml:
[build]
command = "npm run build"
publish = "_site"
[build.environment]
GA_MEASUREMENT_ID = "G-PRODUCTION-ID"
[context.deploy-preview.environment]
GA_MEASUREMENT_ID = "G-STAGING-ID"
Step 5: Build and Deploy
# Development (no tracking)
ELEVENTY_ENV=development npx @11ty/eleventy --serve
# Production build
npx @11ty/eleventy
# Deploy
git add .
git commit -m "Add GA4 tracking"
git push origin main
Verification Steps
1. Check Real-Time Reports
After deploying:
- Visit your live site
- Open Google Analytics → Reports → Realtime
- Confirm pageview appears within 30 seconds
2. Browser Developer Tools
// Open browser console
console.log(window.gtag); // Should be a function
console.log(window.dataLayer); // Should be an array
// Manually fire test event
gtag('event', 'test_event', { 'test_parameter': 'hello' });
3. Network Tab
- Open DevTools → Network tab
- Filter by "collect" or "gtag"
- Reload page
- Verify requests to
www.google-analytics.com
4. Tag Assistant
- Install Tag Assistant Chrome Extension
- Visit your site
- Click Tag Assistant icon
- Verify GA4 tag is present and firing
Common Setup Issues
Tracking Not Working on Localhost
Issue: GA4 doesn't fire during local development.
Solution: This is intentional. Use conditional loading:
// Hugo
{{ if not .Site.IsServer }}
// Jekyll
{% if jekyll.environment == "production" %}
// Gatsby/Next.js
if (process.env.NODE_ENV === 'production')
Environment Variables Not Loading
Issue: process.env.GA_ID is undefined.
Solution:
- Gatsby: Prefix with
GATSBY_ - Next.js: Prefix with
NEXT_PUBLIC_ - Hugo/Jekyll: Use Netlify build environment variables
- Verify in Netlify UI: Site Settings → Build & Deploy → Environment
Preview Deploys Polluting Production Data
Issue: Preview URLs send data to production GA4.
Solution: Use environment-specific tracking IDs:
# netlify.toml
[context.production.environment]
GA_ID = "G-PRODUCTION-ID"
[context.deploy-preview.environment]
GA_ID = "G-STAGING-ID"
Duplicate Pageviews
Issue: SPA frameworks (Gatsby, Next.js) send multiple pageviews per route change.
Solution: Remove send_page_view: true from config, handle manually:
// Gatsby: Use onRouteUpdate
// Next.js: Use router.events listener
Next Steps
- Configure Event Tracking - Track custom interactions
- Set Up Data Layer - Advanced content metadata
- Debug Tracking Issues - Troubleshoot common problems
Related Resources
- GA4 Fundamentals - Universal GA4 concepts
- GTM Setup - Alternative implementation
- Performance Impact - Minimize tracking overhead