Prismic's component-based architecture with Slices and preview functionality requires custom event tracking implementation. This guide shows how to track Prismic-specific interactions with GA4.
Prismic Event Categories
Slice Events
- Slice View - Individual Slice rendered on page
- Slice Interaction - User interacts with Slice component
- Slice CTA Click - Call-to-action button in Slice clicked
- Slice Form Submit - Form within Slice submitted
Preview Events
- Preview Mode Accessed - Editor enters preview mode
- Preview Document Viewed - Specific document previewed
- Preview Exit - User exits preview mode
Content Events
- Document View - Prismic document rendered
- Link Click - Internal Prismic link clicked
- External Link Click - External link in content clicked
- Media View - Image or media from Prismic loaded
Event Tracking Implementation
Method 1: Tracking Slice Interactions (Next.js)
Create a wrapper component for tracking Slices:
// components/TrackedSlice.js
'use client';
import { useEffect, useRef } from 'react';
export function TrackedSlice({ slice, children }) {
const sliceRef = useRef(null);
const hasTracked = useRef(false);
useEffect(() => {
if (!sliceRef.current || hasTracked.current) return;
const observer = new IntersectionObserver(
(entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting && !hasTracked.current) {
hasTracked.current = true;
if (typeof window.gtag !== 'undefined') {
window.gtag('event', 'view_item', {
event_category: 'slice',
event_label: slice.slice_type,
slice_type: slice.slice_type,
slice_variation: slice.variation,
slice_id: slice.id,
});
}
}
});
},
{ threshold: 0.5 }
);
observer.observe(sliceRef.current);
return () => observer.disconnect();
}, [slice]);
return <div ref={sliceRef}>{children}</div>;
}
Usage in Slice components:
// slices/HeroSlice.js
import { TrackedSlice } from '@/components/TrackedSlice';
export default function HeroSlice({ slice }) {
return (
<TrackedSlice slice={slice}>
<section className="hero">
<h1>{slice.primary.title}</h1>
<p>{slice.primary.description}</p>
<button => {
if (typeof window.gtag !== 'undefined') {
window.gtag('event', 'click', {
event_category: 'slice_cta',
event_label: 'hero_cta',
slice_type: slice.slice_type,
cta_text: slice.primary.cta_text,
});
}
}}
>
{slice.primary.cta_text}
</button>
</section>
</TrackedSlice>
);
}
Method 2: Tracking Preview Mode
Create a preview tracking component:
// components/PrismicPreviewTracking.js
'use client';
import { useEffect } from 'react';
import { usePathname, useSearchParams } from 'next/navigation';
export function PrismicPreviewTracking() {
const pathname = usePathname();
const searchParams = useSearchParams();
const isPreview = searchParams.get('preview') === 'true';
useEffect(() => {
if (isPreview && typeof window.gtag !== 'undefined') {
// Set preview mode as user property
window.gtag('set', 'user_properties', {
preview_mode: 'active',
});
// Track preview access
window.gtag('event', 'prismic_preview_accessed', {
event_category: 'preview',
event_label: 'Preview Mode Active',
page_path: pathname,
});
}
}, [isPreview, pathname]);
// Track preview exit
useEffect(() => {
if (!isPreview && typeof window.gtag !== 'undefined') {
window.gtag('set', 'user_properties', {
preview_mode: 'inactive',
});
}
}, [isPreview]);
return null;
}
Add to layout:
// app/layout.js
import { PrismicPreviewTracking } from '@/components/PrismicPreviewTracking';
export default function RootLayout({ children }) {
return (
<html>
<body>
{children}
<PrismicPreviewTracking />
</body>
</html>
);
}
Method 3: Tracking Prismic Document Views
Track when Prismic documents are viewed:
// In your page component
import { useEffect } from 'react';
export default function PrismicPage({ document }) {
useEffect(() => {
if (document && typeof window.gtag !== 'undefined') {
window.gtag('event', 'view_document', {
event_category: 'prismic_content',
event_label: document.type,
document_id: document.id,
document_type: document.type,
document_uid: document.uid,
document_tags: document.tags.join(','),
last_published: document.last_publication_date,
language: document.lang,
});
}
}, [document]);
return (
<div>
{/* Render document content */}
</div>
);
}
Method 4: Tracking Prismic Link Clicks
Create a custom Link component:
// components/PrismicLink.js
'use client';
import { PrismicNextLink } from '@prismicio/next';
export function TrackedPrismicLink({ field, children, ...props }) {
const handleClick = () => {
if (typeof window.gtag !== 'undefined') {
const isExternal = field.link_type === 'Web';
window.gtag('event', 'click', {
event_category: isExternal ? 'outbound_link' : 'internal_link',
event_label: field.url || field.uid,
link_type: field.link_type,
link_url: field.url,
link_uid: field.uid,
});
}
};
return (
<PrismicNextLink field={field} {...props}>
{children}
</PrismicNextLink>
);
}
Usage:
import { TrackedPrismicLink } from '@/components/PrismicLink';
export function MyComponent({ slice }) {
return (
<TrackedPrismicLink field={slice.primary.link}>
{slice.primary.link_text}
</TrackedPrismicLink>
);
}
Method 5: Tracking Slice Form Submissions
For forms within Slices:
// slices/ContactFormSlice.js
'use client';
import { useState } from 'react';
export default function ContactFormSlice({ slice }) {
const [formData, setFormData] = useState({});
const handleSubmit = async (e) => {
e.preventDefault();
// Track form submission
if (typeof window.gtag !== 'undefined') {
window.gtag('event', 'generate_lead', {
event_category: 'form',
event_label: 'contact_form_submit',
slice_type: slice.slice_type,
form_name: slice.primary.form_name,
});
}
// Submit form data
// ... form submission logic
};
return (
<form
<input
type="email" => setFormData({ ...formData, email: e.target.value })}
required
/>
<button type="submit">Submit</button>
</form>
);
}
Method 6: Tracking Media Interactions (Gatsby)
For Gatsby with Prismic:
// components/PrismicImage.js
import { GatsbyImage, getImage } from 'gatsby-plugin-image';
import { useEffect, useRef } from 'react';
export function TrackedPrismicImage({ image, alt }) {
const imageRef = useRef(null);
const hasTracked = useRef(false);
useEffect(() => {
if (!imageRef.current || hasTracked.current) return;
const observer = new IntersectionObserver(
(entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting && !hasTracked.current) {
hasTracked.current = true;
if (typeof window.gtag !== 'undefined') {
window.gtag('event', 'image_view', {
event_category: 'media',
event_label: 'prismic_image_loaded',
image_url: image.url,
image_dimensions: `${image.dimensions.width}x${image.dimensions.height}`,
});
}
}
});
},
{ threshold: 0.1 }
);
observer.observe(imageRef.current);
return () => observer.disconnect();
}, [image]);
const imageData = getImage(image);
return (
<div ref={imageRef}>
<GatsbyImage image={imageData} alt={alt} />
</div>
);
}
Gatsby-Specific Event Tracking
Track Page Transitions
In gatsby-browser.js:
export const location, prevLocation }) => {
if (typeof window.gtag !== 'undefined') {
window.gtag('config', 'G-XXXXXXXXXX', {
page_path: location.pathname + location.search,
});
// Track if coming from Prismic preview
if (location.search.includes('preview=true')) {
window.gtag('event', 'preview_page_view', {
event_category: 'preview',
event_label: 'Preview Page View',
page_path: location.pathname,
});
}
}
};
Track Prismic Build Time
// gatsby-node.js
exports.onPostBuild = ({ reporter }) => {
reporter.info('Prismic build completed');
// Send build event to GA4 (server-side with Measurement Protocol)
// This requires additional setup with GA4 Measurement Protocol API
};
Advanced Tracking Patterns
Slice Zone Tracking
Track entire Slice Zone rendering:
// components/TrackedSliceZone.js
'use client';
import { SliceZone } from '@prismicio/react';
import { useEffect } from 'react';
import { components } from '@/slices';
export function TrackedSliceZone({ slices }) {
useEffect(() => {
if (slices && slices.length > 0 && typeof window.gtag !== 'undefined') {
// Track Slice Zone composition
window.gtag('event', 'slice_zone_loaded', {
event_category: 'content',
event_label: 'Slice Zone Rendered',
slice_count: slices.length,
slice_types: slices.map(s => s.slice_type).join(','),
});
}
}, [slices]);
return <SliceZone slices={slices} components={components} />;
}
User Journey Tracking
Track content discovery path:
// hooks/usePrismicJourney.js
'use client';
import { useEffect, useRef } from 'react';
import { usePathname } from 'next/navigation';
export function usePrismicJourney(document) {
const pathname = usePathname();
const journeyRef = useRef([]);
useEffect(() => {
if (document) {
journeyRef.current.push({
type: document.type,
uid: document.uid,
timestamp: Date.now(),
});
if (typeof window.gtag !== 'undefined') {
window.gtag('event', 'content_journey', {
event_category: 'engagement',
event_label: 'Content Path',
journey_depth: journeyRef.current.length,
content_types: journeyRef.current.map(d => d.type).join(' > '),
});
}
}
}, [document]);
}
Rich Text Interaction Tracking
Track interactions with Prismic Rich Text fields:
// components/TrackedRichText.js
'use client';
import { PrismicRichText } from '@prismicio/react';
import { useRef, useEffect } from 'react';
export function TrackedRichText({ field }) {
const contentRef = useRef(null);
useEffect(() => {
if (!contentRef.current) return;
const handleLinkClick = (e) => {
const link = e.target.closest('a');
if (link && typeof window.gtag !== 'undefined') {
window.gtag('event', 'click', {
event_category: 'rich_text_link',
event_label: link.href,
link_text: link.textContent,
});
}
};
contentRef.current.addEventListener('click', handleLinkClick);
return () => {
contentRef.current?.removeEventListener('click', handleLinkClick);
};
}, []);
return (
<div ref={contentRef}>
<PrismicRichText field={field} />
</div>
);
}
Recommended GA4 Custom Events
Create in GA4 Console
Navigate to GA4 → Configure → Events → Create Event:
slice_interaction
- Event name:
click - Match condition:
event_category = slice_cta
- Event name:
preview_usage
- Event name:
prismic_preview_accessed - Parameter:
event_category = preview
- Event name:
document_engagement
- Event name:
view_document - Parameter:
document_typeexists
- Event name:
high_engagement
- Event name:
slice_zone_loaded - Match condition:
slice_count >= 5
- Event name:
Custom Dimensions
Set up in GA4 → Configure → Custom Definitions:
| Dimension Name | Parameter | Scope |
|---|---|---|
| Slice Type | slice_type | Event |
| Slice Variation | slice_variation | Event |
| Document Type | document_type | Event |
| Document UID | document_uid | Event |
| Preview Mode | preview_mode | User |
| Language | language | Event |
Performance Considerations
Throttle Scroll Tracking
Prevent excessive events:
function useThrottledTracking(callback, delay = 1000) {
const timeoutRef = useRef(null);
const lastCallRef = useRef(0);
return (...args) => {
const now = Date.now();
if (now - lastCallRef.current < delay) return;
clearTimeout(timeoutRef.current);
timeoutRef.current = setTimeout(() => {
callback(...args);
lastCallRef.current = now;
}, 100);
};
}
Batch Events
Consolidate multiple events:
const eventQueue = [];
const BATCH_SIZE = 5;
function queueEvent(eventName, params) {
eventQueue.push({ name: eventName, params });
if (eventQueue.length >= BATCH_SIZE) {
flushEvents();
}
}
function flushEvents() {
if (typeof window.gtag !== 'undefined') {
eventQueue.forEach(({ name, params }) => {
window.gtag('event', name, params);
});
}
eventQueue.length = 0;
}
// Flush on page unload
window.addEventListener('beforeunload', flushEvents);
Testing Event Tracking
1. DebugView
Enable debug mode:
gtag('config', 'G-XXXXXXXXXX', {
'debug_mode': true
});
Check GA4 → Admin → DebugView to see events in real-time.
2. Browser Console
// Log all dataLayer pushes
window.dataLayer.push = new Proxy(window.dataLayer.push, {
apply(target, thisArg, args) {
console.log('GA4 Event:', args);
return target.apply(thisArg, args);
}
});
3. Google Tag Assistant
Install Tag Assistant and validate events fire correctly.
Common Issues
Events Not Firing in Preview
Cause: Preview mode may block scripts
Solution: Ensure gtag loads in preview:
// Don't exclude preview from analytics
if (isPreview || typeof window.gtag !== 'undefined') {
window.gtag('event', 'preview_accessed');
}
Duplicate Slice Events
Cause: Slice re-renders trigger multiple events
Solution: Use hasTracked ref (see TrackedSlice example above)
Events Fire Too Frequently
Cause: No throttling on scroll/interaction events
Solution: Implement throttling (see Performance section)
Next Steps
- Set up Prismic GTM Data Layer for advanced tracking
- Configure Meta Pixel for Prismic
- Troubleshoot Events Not Firing