A comprehensive data layer enables GTM to access Prismic-specific information like document types, Slices, custom fields, and content metadata. This guide shows how to build a complete Prismic data layer.
Prismic Data Layer Overview
The data layer is a JavaScript object that stores information about Prismic documents, Slices, and user interactions. GTM tags access this data via variables.
Prismic Data Categories
- Document Data - Document ID, type, UID, tags, publication dates
- Slice Data - Slice types, variations, positions in Slice Zone
- Custom Type Data - Custom field values and metadata
- Content Data - Titles, descriptions, rich text content metadata
- Relationship Data - Linked documents and content relationships
- Preview Data - Preview mode status and session information
Basic Data Layer Implementation
Method 1: Next.js App Router
Create components/PrismicDataLayer.js:
'use client';
import { useEffect } from 'react';
import { usePathname } from 'next/navigation';
export function PrismicDataLayer({ document, slices }) {
const pathname = usePathname();
useEffect(() => {
if (!document || typeof window === 'undefined') return;
window.dataLayer = window.dataLayer || [];
// Push comprehensive Prismic data
window.dataLayer.push({
event: 'prismic_data_ready',
// Page metadata
page: {
path: pathname,
title: document.data?.meta_title || document.data?.title,
description: document.data?.meta_description,
},
// Prismic document data
prismic: {
// Core document info
id: document.id,
uid: document.uid,
type: document.type,
lang: document.lang,
// Publication metadata
firstPublicationDate: document.first_publication_date,
lastPublicationDate: document.last_publication_date,
// Tags and categorization
tags: document.tags || [],
// Alternate languages
alternateLanguages: document.alternate_languages?.map(lang => ({
id: lang.id,
uid: lang.uid,
type: lang.type,
lang: lang.lang,
})) || [],
// Slice Zone information
slices: slices ? {
count: slices.length,
types: slices.map(s => s.slice_type),
variations: slices.map(s => s.variation),
composition: slices.map((s, i) => ({
position: i + 1,
type: s.slice_type,
variation: s.variation,
})),
} : null,
},
});
}, [document, slices, pathname]);
return null;
}
Usage in page component:
// app/[uid]/page.js
import { PrismicDataLayer } from '@/components/PrismicDataLayer';
import { SliceZone } from '@prismicio/react';
import { components } from '@/slices';
export default async function Page({ params }) {
const client = createClient();
const document = await client.getByUID('page', params.uid);
return (
<>
<PrismicDataLayer document={document} slices={document.data.slices} />
<h1>{document.data.title}</h1>
<SliceZone slices={document.data.slices} components={components} />
</>
);
}
Method 2: Next.js Pages Router
Create components/PrismicDataLayer.js:
import { useEffect } from 'react';
import { useRouter } from 'next/router';
export function PrismicDataLayer({ document, slices }) {
const router = useRouter();
useEffect(() => {
if (!document) return;
window.dataLayer = window.dataLayer || [];
window.dataLayer.push({
event: 'prismic_data_ready',
page: {
path: router.asPath,
locale: router.locale,
},
prismic: {
id: document.id,
uid: document.uid,
type: document.type,
lang: document.lang,
tags: document.tags || [],
sliceCount: slices?.length || 0,
sliceTypes: slices?.map(s => s.slice_type).join(',') || '',
},
});
}, [document, slices, router]);
return null;
}
Method 3: Gatsby
In gatsby-browser.js:
export const location, prevLocation }) => {
// Gatsby automatically provides page context
// Data layer will be pushed from page template
};
In your page template:
// src/templates/page.js
import { useEffect } from 'react';
export default function PageTemplate({ data }) {
const { prismicPage } = data;
useEffect(() => {
if (typeof window !== 'undefined') {
window.dataLayer = window.dataLayer || [];
window.dataLayer.push({
event: 'prismic_data_ready',
prismic: {
id: prismicPage.prismicId,
uid: prismicPage.uid,
type: prismicPage.type,
lang: prismicPage.lang,
tags: prismicPage.tags || [],
slices: {
count: prismicPage.data.body?.length || 0,
types: prismicPage.data.body?.map(s => s.slice_type) || [],
},
},
});
}
}, [prismicPage]);
return (
<div>
{/* Render page */}
</div>
);
}
export const query = graphql`
query PageQuery($uid: String!) {
prismicPage(uid: { eq: $uid }) {
prismicId
uid
type
lang
tags
data {
title {
text
}
body {
... on PrismicSliceType {
slice_type
slice_label
}
}
}
}
}
`;
Advanced Data Layer Patterns
Custom Type Fields in Data Layer
Extract custom field data:
'use client';
import { useEffect } from 'react';
export function CustomTypeDataLayer({ document }) {
useEffect(() => {
if (!document) return;
const customData = {};
// Extract specific custom fields
if (document.data.category) {
customData.category = document.data.category;
}
if (document.data.author) {
customData.author = {
id: document.data.author.id,
type: document.data.author.type,
uid: document.data.author.uid,
};
}
if (document.data.featured_image) {
customData.hasFeaturedImage = true;
customData.featuredImageUrl = document.data.featured_image.url;
}
window.dataLayer = window.dataLayer || [];
window.dataLayer.push({
event: 'prismic_custom_data',
prismicCustom: customData,
});
}, [document]);
return null;
}
Slice-Specific Data Layer
Track individual Slice data:
// components/TrackedSlice.js
'use client';
import { useEffect, useRef } from 'react';
export function TrackedSlice({ slice, index, 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;
window.dataLayer = window.dataLayer || [];
window.dataLayer.push({
event: 'slice_viewed',
slice: {
type: slice.slice_type,
variation: slice.variation,
position: index + 1,
id: slice.id,
// Extract primary fields
primaryData: Object.keys(slice.primary || {}).reduce((acc, key) => {
const value = slice.primary[key];
// Only include simple values, not complex objects
if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') {
acc[key] = value;
}
return acc;
}, {}),
},
});
}
});
},
{ threshold: 0.5 }
);
observer.observe(sliceRef.current);
return () => observer.disconnect();
}, [slice, index]);
return <div ref={sliceRef}>{children}</div>;
}
Usage in Slice Zone:
import { TrackedSlice } from '@/components/TrackedSlice';
export default function MySlice({ slice, index }) {
return (
<TrackedSlice slice={slice} index={index}>
<section>
{/* Slice content */}
</section>
</TrackedSlice>
);
}
Preview Mode Data Layer
// components/PrismicPreviewDataLayer.js
'use client';
import { useEffect } from 'react';
import { useSearchParams } from 'next/navigation';
export function PrismicPreviewDataLayer() {
const searchParams = useSearchParams();
const isPreview = searchParams.get('preview') === 'true';
useEffect(() => {
if (typeof window === 'undefined') return;
window.dataLayer = window.dataLayer || [];
window.dataLayer.push({
event: 'preview_mode_status',
preview: {
active: isPreview,
timestamp: new Date().toISOString(),
},
});
}, [isPreview]);
return null;
}
Content Relationship Data Layer
Track linked documents:
export function RelationshipDataLayer({ document }) {
useEffect(() => {
if (!document) return;
// Extract relationship fields
const relationships = {};
Object.keys(document.data).forEach(key => {
const field = document.data[key];
// Check if field is a relationship
if (field && field.id && field.type) {
relationships[key] = {
id: field.id,
type: field.type,
uid: field.uid,
lang: field.lang,
};
}
});
if (Object.keys(relationships).length > 0) {
window.dataLayer = window.dataLayer || [];
window.dataLayer.push({
event: 'prismic_relationships',
relationships,
});
}
}, [document]);
return null;
}
GTM Variable Configuration
Create Data Layer Variables in GTM
Navigate to Variables → New → User-Defined Variables:
Document Variables
| Variable Name | Type | Data Layer Variable Name |
|---|---|---|
| Prismic Document ID | Data Layer Variable | prismic.id |
| Prismic Document Type | Data Layer Variable | prismic.type |
| Prismic Document UID | Data Layer Variable | prismic.uid |
| Prismic Language | Data Layer Variable | prismic.lang |
| Prismic Tags | Data Layer Variable | prismic.tags |
| First Publication Date | Data Layer Variable | prismic.firstPublicationDate |
| Last Publication Date | Data Layer Variable | prismic.lastPublicationDate |
Slice Variables
| Variable Name | Type | Data Layer Variable Name |
|---|---|---|
| Slice Count | Data Layer Variable | prismic.slices.count |
| Slice Types | Data Layer Variable | prismic.slices.types |
| Slice Variations | Data Layer Variable | prismic.slices.variations |
Custom Variables
| Variable Name | Type | Data Layer Variable Name |
|---|---|---|
| Content Category | Data Layer Variable | prismicCustom.category |
| Has Featured Image | Data Layer Variable | prismicCustom.hasFeaturedImage |
| Author ID | Data Layer Variable | prismicCustom.author.id |
GTM Trigger Configuration
Document Type Triggers
Trigger: Blog Posts Only
- Type: Custom Event
- Event name:
prismic_data_ready - This trigger fires on: Some Custom Events
- Condition:
prismic.typeequalsblog_post
Trigger: Landing Pages
- Type: Custom Event
- Event name:
prismic_data_ready - Condition:
prismic.typeequalslanding_page
Slice-Based Triggers
Trigger: Hero Slice Viewed
- Type: Custom Event
- Event name:
slice_viewed - Condition:
slice.typeequalshero
Trigger: CTA Slice Viewed
- Type: Custom Event
- Event name:
slice_viewed - Condition:
slice.typeequalscall_to_action
Preview Mode Trigger
Trigger: Preview Active
- Type: Custom Event
- Event name:
preview_mode_status - Condition:
preview.activeequalstrue
Using Data Layer in GTM Tags
GA4 Configuration with Prismic Data
Tag: GA4 Configuration
- Tag Type: Google Analytics: GA4 Configuration
- Measurement ID:
G-XXXXXXXXXX - Configuration Parameters:
document_type:\{\{Prismic Document Type\}\}document_uid:\{\{Prismic Document UID\}\}content_language:\{\{Prismic Language\}\}slice_count:\{\{Slice Count\}\}
Custom HTML Tag with Prismic Data
<!-- Custom HTML Tag Example -->
<script>
console.log('Prismic Document:', {
id: {{Prismic Document ID}},
type: {{Prismic Document Type}},
uid: {{Prismic Document UID}},
sliceCount: {{Slice Count}},
});
// Send to analytics
if (typeof gtag !== 'undefined') {
gtag('event', 'prismic_page_view', {
document_type: {{Prismic Document Type}},
document_uid: {{Prismic Document UID}},
slice_count: {{Slice Count}},
});
}
</script>
Advanced Use Cases
Track Content Update Frequency
export function UpdateFrequencyDataLayer({ document }) {
useEffect(() => {
if (!document) return;
const firstPub = new Date(document.first_publication_date);
const lastPub = new Date(document.last_publication_date);
const daysSinceFirst = Math.floor((Date.now() - firstPub) / (1000 * 60 * 60 * 24));
const daysSinceUpdate = Math.floor((Date.now() - lastPub) / (1000 * 60 * 60 * 24));
window.dataLayer = window.dataLayer || [];
window.dataLayer.push({
event: 'content_freshness',
content: {
ageInDays: daysSinceFirst,
daysSinceUpdate,
wasUpdated: firstPub.getTime() !== lastPub.getTime(),
},
});
}, [document]);
return null;
}
Track Slice Zone Engagement Patterns
export function SliceEngagementDataLayer({ slices }) {
useEffect(() => {
if (!slices || slices.length === 0) return;
// Analyze Slice composition
const sliceAnalysis = {
totalSlices: slices.length,
uniqueTypes: [...new Set(slices.map(s => s.slice_type))].length,
hasHero: slices.some(s => s.slice_type === 'hero'),
hasCTA: slices.some(s => s.slice_type === 'call_to_action'),
hasForm: slices.some(s => s.slice_type === 'contact_form'),
composition: slices.map(s => s.slice_type).join(' > '),
};
window.dataLayer = window.dataLayer || [];
window.dataLayer.push({
event: 'slice_zone_analysis',
sliceAnalysis,
});
}, [slices]);
return null;
}
Testing and Debugging
Preview Data Layer in Console
// In browser console on your Prismic site
console.log(window.dataLayer);
// Expected output: Array with Prismic data objects
// [
// { event: 'gtm.js', ... },
// { event: 'prismic_data_ready', prismic: {...}, ... }
// ]
// Check specific values
console.log(window.dataLayer.find(item => item.event === 'prismic_data_ready'));
GTM Preview Mode
- In GTM, click Preview
- Enter your Prismic site URL
- Click Connect
- In Tag Assistant:
- Verify
prismic_data_readyevent fires - Check Data Layer tab
- Verify all Prismic variables populate
- Verify
Enable Data Layer Logging
// Add to your data layer component for debugging
if (process.env.NODE_ENV === 'development') {
console.log('Pushing to dataLayer:', {
event: 'prismic_data_ready',
prismic: { /* data */ },
});
}
window.dataLayer.push({ /* data */ });
Performance Considerations
Minimize Data Layer Size
Only include necessary data:
// Bad: Too much data
window.dataLayer.push({
prismic: {
fullDocument: document, // Don't include entire document
richTextContent: document.data.content, // Don't include full rich text
},
});
// Good: Only metadata
window.dataLayer.push({
prismic: {
id: document.id,
type: document.type,
uid: document.uid,
sliceCount: document.data.slices?.length,
},
});
Lazy Load Non-Critical Data
// Push critical data immediately
window.dataLayer.push({
event: 'prismic_data_ready',
prismic: { id, type, uid },
});
// Lazy load detailed data after page load
window.addEventListener('load', () => {
window.dataLayer.push({
event: 'prismic_detailed_data',
prismicDetailed: { /* additional data */ },
});
});
Common Issues
Data Layer Undefined
Cause: Data layer pushed before GTM loads
Solution: Initialize data layer before GTM:
// Always initialize dataLayer first
window.dataLayer = window.dataLayer || [];
// Then push data
window.dataLayer.push({ /* data */ });
Variables Not Populating in GTM
Cause: Event name mismatch or wrong variable path
Solution:
- Verify event name matches in GTM trigger
- Check data layer variable path matches object structure
- Use dot notation:
prismic.typenotprismic[type]
Slice Data Missing
Cause: Slices not passed to data layer component
Solution: Ensure slices are passed from page:
<PrismicDataLayer document={document} slices={document.data.slices} />