Strapi GA4 Event Tracking: Setup Guide | OpsBlu Docs

Strapi GA4 Event Tracking: Setup Guide

Track Strapi-specific events in GA4 including content views, user interactions, and custom content events.

Learn how to track events specific to Strapi-powered sites, including content views, user interactions, and custom events based on your Strapi content structure.

Important: Track in Frontend Framework

Remember that Strapi is headless, so all event tracking happens in your frontend framework (Next.js, Gatsby, Nuxt.js, etc.), not in Strapi itself.

Strapi Content Events

Content View Tracking

Track when users view content from Strapi.

Next.js Example

// app/articles/[slug]/page.tsx
'use client';

import { useEffect } from 'react';

export default function ArticlePage({ article }: { article: Article }) {
  useEffect(() => {
    // Track content view with Strapi metadata
    window.gtag('event', 'view_content', {
      content_type: article.attributes.__component || 'article',
      content_id: article.id,
      items: [{
        item_id: article.id,
        item_name: article.attributes.title,
        item_category: article.attributes.category?.data?.attributes?.name || 'uncategorized',
      }],
      // Strapi-specific metadata
      author: article.attributes.author?.data?.attributes?.name,
      published_date: article.attributes.publishedAt,
      locale: article.attributes.locale,
    });
  }, [article]);

  return (
    <article>
      <h1>{article.attributes.title}</h1>
      {/* Article content */}
    </article>
  );
}

Gatsby Example

// src/templates/article.js
import React, { useEffect } from 'react';
import { graphql } from 'gatsby';

const ArticleTemplate = ({ data }) => {
  const article = data.strapiArticle;

  useEffect(() => {
    if (typeof window !== 'undefined' && window.gtag) {
      window.gtag('event', 'view_content', {
        content_type: 'article',
        content_id: article.strapiId,
        items: [{
          item_id: article.strapiId,
          item_name: article.title,
          item_category: article.category?.name || 'uncategorized',
        }],
        author: article.author?.name,
        published_date: article.publishedAt,
      });
    }
  }, [article]);

  return (
    <article>
      <h1>{article.title}</h1>
      {/* Article content */}
    </article>
  );
};

export const query = graphql`
  query($slug: String!) {
    strapiArticle(slug: { eq: $slug }) {
      strapiId
      title
      publishedAt
      category {
        name
      }
      author {
        name
      }
    }
  }
`;

export default ArticleTemplate;

Nuxt Example

<!-- pages/articles/[slug].vue -->
<script setup>
const route = useRoute();
const { data: article } = await useFetch(`${useRuntimeConfig().public.strapiUrl}/api/articles/${route.params.slug}?populate=*`);

onMounted(() => {
  if (process.client && window.gtag && article.value) {
    window.gtag('event', 'view_content', {
      content_type: 'article',
      content_id: article.value.data.id,
      items: [{
        item_id: article.value.data.id,
        item_name: article.value.data.attributes.title,
        item_category: article.value.data.attributes.category?.data?.attributes?.name || 'uncategorized',
      }],
      author: article.value.data.attributes.author?.data?.attributes?.name,
      published_date: article.value.data.attributes.publishedAt,
      locale: article.value.data.attributes.locale,
    });
  }
});
</script>

<template>
  <article v-if="article">
    <h1>{{ article.data.attributes.title }}</h1>
    <!-- Article content -->
  </article>
</template>

Collection View Tracking

Track when users view collection pages (e.g., all blog posts, products).

// Track collection view
window.gtag('event', 'view_item_list', {
  item_list_id: 'articles',
  item_list_name: 'All Articles',
  items: articles.map((article, index) => ({
    item_id: article.id,
    item_name: article.attributes.title,
    item_category: article.attributes.category?.data?.attributes?.name,
    index: index,
  })),
});

Search Tracking

Track Strapi content search:

// When user searches Strapi content
function handleSearch(query, results) {
  window.gtag('event', 'search', {
    search_term: query,
    content_type: 'strapi_search',
    results_count: results.length,
  });
}

Category/Filter Tracking

Track when users filter or navigate categories:

// Track category selection
window.gtag('event', 'select_content', {
  content_type: 'category',
  item_id: category.id,
  item_name: category.attributes.name,
});

// Track filter application
window.gtag('event', 'filter_content', {
  filter_type: 'category',
  filter_value: selectedCategory,
  results_count: filteredArticles.length,
});

User Interaction Events

Content Engagement

Track scroll depth to measure content engagement:

// lib/analytics.js
export function trackScrollDepth() {
  let maxScroll = 0;
  const milestones = [25, 50, 75, 90, 100];

  const handleScroll = () => {
    const scrollPercent = Math.round(
      (window.scrollY / (document.documentElement.scrollHeight - window.innerHeight)) * 100
    );

    if (scrollPercent > maxScroll) {
      maxScroll = scrollPercent;

      milestones.forEach(milestone => {
        if (scrollPercent >= milestone && !window[`milestone_${milestone}`]) {
          window[`milestone_${milestone}`] = true;

          window.gtag('event', 'scroll_depth', {
            percent_scrolled: milestone,
            page_location: window.location.pathname,
          });
        }
      });
    }
  };

  window.addEventListener('scroll', handleScroll, { passive: true });

  return () => window.removeEventListener('scroll', handleScroll);
}

// Use in component
useEffect(() => {
  const cleanup = trackScrollDepth();
  return cleanup;
}, []);

Social Sharing

Track when users share Strapi content:

// Share button click handler
function handleShare(platform, article) {
  window.gtag('event', 'share', {
    method: platform, // 'twitter', 'facebook', 'linkedin', etc.
    content_type: 'article',
    item_id: article.id,
    content_title: article.attributes.title,
  });
}

Newsletter Signup

Track newsletter subscriptions:

// Newsletter form submission
function handleNewsletterSubmit(email) {
  window.gtag('event', 'sign_up', {
    method: 'newsletter',
    location: window.location.pathname,
  });
}

Download Tracking

Track when users download files from Strapi:

// Track file downloads
function trackDownload(file) {
  window.gtag('event', 'file_download', {
    file_name: file.attributes.name,
    file_extension: file.attributes.ext,
    file_size: file.attributes.size,
    link_url: file.attributes.url,
  });
}

// Example: Download button
<button => {
  trackDownload(strapiFile);
  window.open(strapiFile.attributes.url, '_blank');
}}>
  Download PDF
</button>

Video/Media Tracking

Track video engagement from Strapi media library:

// Video play tracking
function handleVideoPlay(video) {
  window.gtag('event', 'video_start', {
    video_title: video.attributes.name,
    video_url: video.attributes.url,
    video_provider: 'strapi_upload',
  });
}

// Video complete tracking
function handleVideoComplete(video, duration) {
  window.gtag('event', 'video_complete', {
    video_title: video.attributes.name,
    video_duration: duration,
  });
}

Dynamic Zone Tracking

Track interactions with Strapi Dynamic Zones:

// Track which components users interact with
function trackDynamicZoneInteraction(component) {
  window.gtag('event', 'component_interaction', {
    component_type: component.__component,
    component_id: component.id,
    page_location: window.location.pathname,
  });
}

// Example: Image gallery component
{article.attributes.content?.map((component) => {
  if (component.__component === 'content.image-gallery') {
    return (
      <ImageGallery
        images={component.images} => trackDynamicZoneInteraction(component)}
      />
    );
  }
})}

Strapi Relations Tracking

Track navigation through related content:

// Track related article clicks
function trackRelatedArticleClick(currentArticle, relatedArticle) {
  window.gtag('event', 'select_content', {
    content_type: 'related_article',
    promotion_id: currentArticle.id,
    promotion_name: currentArticle.attributes.title,
    items: [{
      item_id: relatedArticle.id,
      item_name: relatedArticle.attributes.title,
    }],
  });
}

Multi-Language Tracking (Strapi i18n)

Track language preferences and content in different locales:

// Track language selection
function trackLanguageChange(locale) {
  window.gtag('event', 'language_change', {
    previous_language: currentLocale,
    new_language: locale,
  });

  // Update GA4 user properties
  window.gtag('set', 'user_properties', {
    preferred_language: locale,
  });
}

// Track content in specific locale
window.gtag('event', 'view_content', {
  content_type: 'article',
  content_id: article.id,
  content_locale: article.attributes.locale,
  content_title: article.attributes.title,
});

Custom Content Type Events

Track custom Strapi content types:

// Example: Product from Strapi
function trackProductView(product) {
  window.gtag('event', 'view_item', {
    currency: 'USD',
    value: product.attributes.price,
    items: [{
      item_id: product.id,
      item_name: product.attributes.name,
      item_brand: product.attributes.brand?.data?.attributes?.name,
      item_category: product.attributes.category?.data?.attributes?.name,
      price: product.attributes.price,
    }],
  });
}

// Example: Event from Strapi
function trackEventRegistration(event) {
  window.gtag('event', 'event_registration', {
    event_id: event.id,
    event_name: event.attributes.title,
    event_date: event.attributes.date,
    event_location: event.attributes.location,
  });
}

// Example: Course from Strapi
function trackCourseEnrollment(course) {
  window.gtag('event', 'course_enrollment', {
    course_id: course.id,
    course_name: course.attributes.title,
    course_category: course.attributes.category?.data?.attributes?.name,
    instructor: course.attributes.instructor?.data?.attributes?.name,
  });
}

Server-Side Event Tracking

For Next.js API routes or server actions:

// app/api/track-conversion/route.ts
export async function POST(request: Request) {
  const { eventName, params } = await request.json();

  // Send to GA4 Measurement Protocol
  const measurementId = process.env.GA_MEASUREMENT_ID!;
  const apiSecret = process.env.GA_API_SECRET!;

  const response = await fetch(
    `https://www.google-analytics.com/mp/collect?measurement_id=${measurementId}&api_secret=${apiSecret}`,
    {
      method: 'POST',
      body: JSON.stringify({
        client_id: params.client_id,
        events: [{
          name: eventName,
          params: params,
        }],
      }),
    }
  );

  return Response.json({ success: response.ok });
}

Creating a Reusable Analytics Hook (React)

// hooks/useAnalytics.ts
import { useEffect } from 'react';

export function usePageView() {
  useEffect(() => {
    if (typeof window !== 'undefined' && window.gtag) {
      window.gtag('config', process.env.NEXT_PUBLIC_GA_ID!, {
        page_path: window.location.pathname,
      });
    }
  }, []);
}

export function useContentView(content: any) {
  useEffect(() => {
    if (typeof window !== 'undefined' && window.gtag && content) {
      window.gtag('event', 'view_content', {
        content_type: content.__component || 'article',
        content_id: content.id,
        items: [{
          item_id: content.id,
          item_name: content.attributes?.title || content.title,
        }],
      });
    }
  }, [content]);
}

export function useTrackEvent() {
  return (eventName: string, params?: any) => {
    if (typeof window !== 'undefined' && window.gtag) {
      window.gtag('event', eventName, params);
    }
  };
}

// Usage in components
function Article({ article }) {
  useContentView(article);
  const trackEvent = useTrackEvent();

  return (
    <article>
      <h1>{article.attributes.title}</h1>
      <button => trackEvent('share', { method: 'twitter' })}>
        Share
      </button>
    </article>
  );
}

Instead of direct gtag.js calls, use GTM data layer for better organization:

// Push to data layer
window.dataLayer = window.dataLayer || [];
window.dataLayer.push({
  event: 'view_content',
  contentType: article.__component,
  contentId: article.id,
  contentTitle: article.attributes.title,
  contentCategory: article.attributes.category?.data?.attributes?.name,
  author: article.attributes.author?.data?.attributes?.name,
  publishedDate: article.attributes.publishedAt,
  locale: article.attributes.locale,
});

See GTM Data Layer Structure for complete implementation.

Testing & Debugging

1. Use GA4 DebugView

Enable debug mode:

// In development
gtag('config', process.env.NEXT_PUBLIC_GA_ID!, {
  debug_mode: process.env.NODE_ENV === 'development',
});

2. Console Logging

Add logging for development:

const trackEvent = (eventName, params) => {
  if (process.env.NODE_ENV === 'development') {
    console.log('GA4 Event:', eventName, params);
  }

  if (typeof window !== 'undefined' && window.gtag) {
    window.gtag('event', eventName, params);
  }
};

3. Verify Event Parameters

Check in GA4 DebugView:

  • Event name follows GA4 conventions
  • Parameters are correctly formatted
  • Item arrays have required fields
  • Values are numeric (not strings)

4. Test Across Content Types

Test events for each Strapi content type:

  • Collection types (articles, products, etc.)
  • Single types (homepage, about, etc.)
  • Relations (categories, authors, etc.)
  • Dynamic zones
  • Multi-language content

Common Issues

Events Not Firing

Cause: SSR/SSG timing issues or missing window check.

Solution: Always use useEffect or onMounted and check for window:

useEffect(() => {
  if (typeof window !== 'undefined' && window.gtag) {
    // Track event
  }
}, []);

Missing Content Data

Cause: Strapi API response not populated.

Solution: Use populate parameter in API calls:

fetch(`${STRAPI_URL}/api/articles/${slug}?populate=*`)

Or populate specific fields:

fetch(`${STRAPI_URL}/api/articles/${slug}?populate[0]=category&populate[1]=author`)

Duplicate Events

Cause: Multiple useEffect dependencies or event listeners.

Solution: Properly manage dependencies:

// Only track once when content loads
useEffect(() => {
  trackContentView(article);
}, [article.id]); // Only re-track if ID changes

Next Steps

For general event tracking concepts, see GA4 Event Tracking Guide.