Storyblok is a headless CMS that requires client-side implementation of GA4 tracking. This guide covers GA4 integration for Nuxt.js, Next.js, and other JavaScript frameworks using Storyblok.
Prerequisites
Before you begin:
- Have a Google Analytics 4 property created
- Know your GA4 Measurement ID (format:
G-XXXXXXXXXX) - Have a Storyblok space set up
- Be using Nuxt.js, Next.js, or another JavaScript framework
Method 1: Nuxt.js with Storyblok (Recommended)
Nuxt is Storyblok's recommended framework and provides excellent integration.
Step 1: Install Dependencies
For Nuxt 3:
npm install @storyblok/nuxt @nuxtjs/google-analytics
Step 2: Configure nuxt.config.ts
// nuxt.config.ts
export default defineNuxtConfig({
modules: [
'@storyblok/nuxt',
'@nuxtjs/google-analytics',
],
storyblok: {
accessToken: process.env.STORYBLOK_TOKEN,
apiOptions: {
region: 'us', // or 'eu', 'ap', 'ca', 'cn'
},
},
googleAnalytics: {
id: process.env.NUXT_PUBLIC_GA_MEASUREMENT_ID,
config: {
send_page_view: true,
},
},
runtimeConfig: {
public: {
gaMeasurementId: process.env.NUXT_PUBLIC_GA_MEASUREMENT_ID,
},
},
});
Step 3: Create GA4 Plugin
Create plugins/gtag.client.ts:
// plugins/gtag.client.ts
export default defineNuxtPlugin((nuxtApp) => {
const config = useRuntimeConfig();
const measurementId = config.public.gaMeasurementId;
if (!measurementId) {
console.warn('GA4 Measurement ID not found');
return;
}
// Load gtag script
const script = document.createElement('script');
script.async = true;
script.src = `https://www.googletagmanager.com/gtag/js?id=${measurementId}`;
document.head.appendChild(script);
// Initialize gtag
window.dataLayer = window.dataLayer || [];
function gtag(...args) {
window.dataLayer.push(args);
}
window.gtag = gtag;
gtag('js', new Date());
gtag('config', measurementId, {
send_page_view: true,
});
// Track route changes
nuxtApp.hook('page:finish', () => {
gtag('config', measurementId, {
page_path: window.location.pathname,
});
});
});
Step 4: Environment Variables
Create .env:
STORYBLOK_TOKEN=your-storyblok-token
NUXT_PUBLIC_GA_MEASUREMENT_ID=G-XXXXXXXXXX
Method 2: Next.js with Storyblok
Step 1: Install Dependencies
npm install storyblok-js-client @storyblok/react
Step 2: Create GA4 Component
Create components/GoogleAnalytics.js:
'use client';
import Script from 'next/script';
export default function GoogleAnalytics({ measurementId }) {
return (
<>
<Script
src={`https://www.googletagmanager.com/gtag/js?id=${measurementId}`}
strategy="afterInteractive"
/>
<Script id="google-analytics" strategy="afterInteractive">
{`
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', '${measurementId}', {
page_path: window.location.pathname,
});
`}
</Script>
</>
);
}
Step 3: Add to Root Layout
In app/layout.js (Next.js 13+ App Router):
import { storyblokInit, apiPlugin } from '@storyblok/react';
import GoogleAnalytics from '@/components/GoogleAnalytics';
storyblokInit({
accessToken: process.env.NEXT_PUBLIC_STORYBLOK_TOKEN,
use: [apiPlugin],
});
const GA_MEASUREMENT_ID = process.env.NEXT_PUBLIC_GA_MEASUREMENT_ID;
export default function RootLayout({ children }) {
return (
<html lang="en">
<head>
{GA_MEASUREMENT_ID && <GoogleAnalytics measurementId={GA_MEASUREMENT_ID} />}
</head>
<body>{children}</body>
</html>
);
}
Step 4: Handle Client-Side Navigation
Create app/analytics.js:
'use client';
import { useEffect } from 'react';
import { usePathname, useSearchParams } from 'next/navigation';
export function Analytics() {
const pathname = usePathname();
const searchParams = useSearchParams();
useEffect(() => {
if (typeof window.gtag !== 'undefined') {
window.gtag('config', process.env.NEXT_PUBLIC_GA_MEASUREMENT_ID, {
page_path: pathname + searchParams.toString(),
});
}
}, [pathname, searchParams]);
return null;
}
Include in app/layout.js:
import { Analytics } from './analytics';
export default function RootLayout({ children }) {
return (
<html lang="en">
<body>
{children}
<Analytics />
</body>
</html>
);
}
Method 3: Nuxt 2 with Storyblok (Legacy)
Install Plugin
npm install @nuxtjs/google-analytics
Configure nuxt.config.js
export default {
modules: [
'@nuxtjs/google-analytics',
'@storyblok/nuxt',
],
googleAnalytics: {
id: process.env.GA_MEASUREMENT_ID,
},
storyblok: {
accessToken: process.env.STORYBLOK_TOKEN,
},
};
Storyblok-Specific Considerations
Tracking Visual Editor Usage
The Storyblok Visual Editor requires special handling:
// plugins/storyblok-editor-tracking.client.js (Nuxt 3)
export default defineNuxtPlugin(() => {
if (window.location.search.includes('_storyblok')) {
// Visual Editor is active
if (typeof window.gtag !== 'undefined') {
window.gtag('set', 'user_properties', {
storyblok_editor: 'active',
});
window.gtag('event', 'visual_editor_accessed', {
event_category: 'editor',
event_label: 'Storyblok Visual Editor',
});
}
}
});
Tracking Storyblok Story Views
When rendering Storyblok stories:
// In your story component (Nuxt 3)
<script setup>
const { story } = defineProps(['story']);
onMounted(() => {
if (story && typeof window.gtag !== 'undefined') {
window.gtag('event', 'page_view', {
page_title: story.name,
content_type: story.content.component,
story_id: story.id,
story_uuid: story.uuid,
created_at: story.created_at,
published_at: story.published_at,
});
}
});
</script>
Tracking Component Rendering
Track when Storyblok components are rendered:
// In Storyblok component (Nuxt 3)
<script setup>
const { blok } = defineProps(['blok']);
onMounted(() => {
if (typeof window.gtag !== 'undefined') {
window.gtag('event', 'component_view', {
event_category: 'storyblok_component',
event_label: blok.component,
component_uid: blok._uid,
});
}
});
</script>
Environment-Specific Configuration
Development vs. Production
Use environment variables to control GA4:
Nuxt - .env:
# Development
NUXT_PUBLIC_GA_MEASUREMENT_ID=G-DEV-XXXXXXXXXX
# Production (or use .env.production)
NUXT_PUBLIC_GA_MEASUREMENT_ID=G-XXXXXXXXXX
Conditional loading:
// nuxt.config.ts
export default defineNuxtConfig({
googleAnalytics: {
id: process.env.NODE_ENV === 'production'
? process.env.NUXT_PUBLIC_GA_MEASUREMENT_ID
: undefined,
},
});
Validation & Testing
1. Real-Time Reports
- Navigate to Reports > Realtime in GA4
- Visit your Storyblok-powered site
- Confirm page views appear within 30 seconds
2. DebugView
Enable debug mode:
gtag('config', 'G-XXXXXXXXXX', {
'debug_mode': true
});
Then check GA4 → Admin → DebugView for real-time events.
3. Browser Console
Test GA4 is loaded:
// Open browser console on your site
console.log(window.gtag);
console.log(window.dataLayer);
Should output the gtag function and dataLayer array.
4. Google Tag Assistant
Install Tag Assistant and verify:
- GA4 tag is detected
- Configuration is correct
- Events are firing
Common Issues & Solutions
Issue: GA4 Not Loading in Nuxt
Cause: Plugin not running on client side
Solution: Ensure plugin has .client suffix:
// plugins/gtag.client.ts (note the .client suffix)
Issue: Visual Editor Events Not Tracked
Cause: Visual Editor iframe isolation
Solution: Ensure tracking code in parent window, not iframe:
if (window === window.parent) {
// Not in iframe, safe to track
gtag('event', 'page_view');
}
Issue: Next.js Client-Side Navigation Not Tracked
Cause: SPA navigation doesn't trigger page views
Solution: Add route change listener (see Next.js example above)
Issue: Duplicate Page Views
Cause: GA4 loaded multiple times
Solution: Ensure GA4 component only rendered once in root layout
Performance Optimization
Use Script Strategy in Next.js
<Script
src="https://www.googletagmanager.com/gtag/js?id=G-XXXXXXXXXX"
strategy="afterInteractive" // Optimal for performance
/>
Defer Loading in Nuxt
// plugins/gtag.client.ts
export default defineNuxtPlugin((nuxtApp) => {
// Defer GA4 loading until after page interactive
if (typeof window !== 'undefined') {
window.addEventListener('load', () => {
// Load GA4 script
});
}
});
Next Steps
Now that GA4 is installed:
- Configure Storyblok Event Tracking for component interactions
- Set up GTM for Storyblok for advanced tracking
- Optimize LCP for Storyblok Images
- Troubleshoot Events Not Firing