Prismic Data Layer for Google Tag Manager | OpsBlu Docs

Prismic Data Layer for Google Tag Manager

Configure GTM data layer with Prismic document metadata, Slice data, and custom type information for advanced tracking

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.type equals blog_post

Trigger: Landing Pages

  • Type: Custom Event
  • Event name: prismic_data_ready
  • Condition: prismic.type equals landing_page

Slice-Based Triggers

Trigger: Hero Slice Viewed

  • Type: Custom Event
  • Event name: slice_viewed
  • Condition: slice.type equals hero

Trigger: CTA Slice Viewed

  • Type: Custom Event
  • Event name: slice_viewed
  • Condition: slice.type equals call_to_action

Preview Mode Trigger

Trigger: Preview Active

  • Type: Custom Event
  • Event name: preview_mode_status
  • Condition: preview.active equals true

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

  1. In GTM, click Preview
  2. Enter your Prismic site URL
  3. Click Connect
  4. In Tag Assistant:
    • Verify prismic_data_ready event fires
    • Check Data Layer tab
    • Verify all Prismic variables populate

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.type not prismic[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} />

Next Steps