Installing Google Analytics 4 on Storyblok with Nuxt and | OpsBlu Docs

Installing Google Analytics 4 on Storyblok with Nuxt and

Complete guide to setting up GA4 tracking on Storyblok-powered sites using Nuxt.js, Next.js, or custom implementations

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

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

  1. Navigate to Reports > Realtime in GA4
  2. Visit your Storyblok-powered site
  3. 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:


Additional Resources