Installing Google Analytics 4 on Sitecore | Blue Frog Docs

Installing Google Analytics 4 on Sitecore

Complete guide to implementing GA4 on Sitecore XP, XM, SXA, and JSS using MVC, Razor, and headless approaches

Installing Google Analytics 4 on Sitecore

General GA4 Concepts: See Google Analytics for universal GA4 concepts and features.

This guide covers Sitecore-specific methods for implementing Google Analytics 4 (GA4), from traditional MVC to headless JSS implementations, with support for Sitecore XP, XM, and SXA.

Prerequisites

Before installing GA4 on Sitecore:

  1. Create a GA4 Property in Google Analytics

    • Sign in to analytics.google.com
    • Create a new GA4 property
    • Copy your Measurement ID (format: G-XXXXXXXXXX)
  2. Verify Sitecore Version

    • Sitecore XP/XM 10.x (recommended)
    • Sitecore 9.x (supported)
    • Sitecore JSS 21.x+ (for headless)
  3. Backend Access Requirements

    • Content Editor access
    • Visual Studio / development environment
    • Deploy permissions to Sitecore instance
  4. Development Environment

    • .NET Framework 4.8+ or .NET Core 3.1+
    • Visual Studio 2019/2022
    • Sitecore TDS, Unicorn, or Sitecore CLI for deployments

Method 1: Layout File Integration (MVC)

Best for: Quick setup, standard Sitecore MVC implementations

Basic Layout Implementation

Edit your main layout file (typically /Views/Shared/_Layout.cshtml):

@using Sitecore.Mvc
@using Sitecore.Mvc.Analytics.Extensions

<!DOCTYPE html>
<html lang="@Sitecore.Context.Language.Name">
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>@Html.Sitecore().Field("Title", new { DisableWebEdit = true })</title>

    @Html.Sitecore().Placeholder("head")

    @* Only load analytics in normal/preview mode, not Experience Editor *@
    @if (!Sitecore.Context.PageMode.IsExperienceEditorEditing)
    {
        <!-- Google Analytics 4 -->
        <script async src="https://www.googletagmanager.com/gtag/js?id=G-XXXXXXXXXX"></script>
        <script>
            window.dataLayer = window.dataLayer || [];
            function gtag(){dataLayer.push(arguments);}
            gtag('js', new Date());
            gtag('config', 'G-XXXXXXXXXX', {
                'anonymize_ip': true,
                'cookie_flags': 'SameSite=None;Secure'
            });
        </script>
    }
</head>
<body>
    @Html.Sitecore().Placeholder("main")
    @RenderBody()
</body>
</html>

Multi-Site Configuration

For Sitecore multi-site setups, configure per-site tracking IDs:

1. Add to site definition in web.config or patch file:

<!-- App_Config/Include/Sites/Sites.config -->
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
  <sitecore>
    <sites>
      <site name="website1"
            hostName="www.site1.com"
            database="web"
            googleAnalyticsId="G-XXXXXXXXX1" />

      <site name="website2"
            hostName="www.site2.com"
            database="web"
            googleAnalyticsId="G-XXXXXXXXX2" />
    </sites>
  </sitecore>
</configuration>

2. Access in Layout:

@{
    var gaId = Sitecore.Context.Site.Properties["googleAnalyticsId"];
}

@if (!string.IsNullOrEmpty(gaId) && !Sitecore.Context.PageMode.IsExperienceEditorEditing)
{
    <script async src="https://www.googletagmanager.com/gtag/js?id=@gaId"></script>
    <script>
        window.dataLayer = window.dataLayer || [];
        function gtag(){dataLayer.push(arguments);}
        gtag('js', new Date());
        gtag('config', '@gaId', {
            'anonymize_ip': true
        });
    </script>
}

Configuration-Based Approach

1. Add to appSettings in Web.config:

<configuration>
  <appSettings>
    <add key="GoogleAnalytics.MeasurementId" value="G-XXXXXXXXXX" />
    <add key="GoogleAnalytics.Enabled" value="true" />
  </appSettings>
</configuration>

2. Create helper class:

// /Models/Analytics/GoogleAnalyticsConfig.cs
namespace YourProject.Models.Analytics
{
    public static class GoogleAnalyticsConfig
    {
        public static string MeasurementId =>
            System.Configuration.ConfigurationManager.AppSettings["GoogleAnalytics.MeasurementId"];

        public static bool Enabled =>
            bool.Parse(System.Configuration.ConfigurationManager.AppSettings["GoogleAnalytics.Enabled"] ?? "false");

        public static bool ShouldRender =>
            Enabled &&
            !Sitecore.Context.PageMode.IsExperienceEditorEditing &&
            !string.IsNullOrEmpty(MeasurementId);
    }
}

3. Use in Layout:

@using YourProject.Models.Analytics

@if (GoogleAnalyticsConfig.ShouldRender)
{
    <script async src="https://www.googletagmanager.com/gtag/js?id=@GoogleAnalyticsConfig.MeasurementId"></script>
    <script>
        window.dataLayer = window.dataLayer || [];
        function gtag(){dataLayer.push(arguments);}
        gtag('js', new Date());
        gtag('config', '@GoogleAnalyticsConfig.MeasurementId', {
            'anonymize_ip': true
        });
    </script>
}

Method 2: View Rendering Component

Best for: Reusable component, managed through Sitecore Content Editor

Step 1: Create Rendering Item

Content Editor:

  1. Navigate to /sitecore/layout/Renderings/Feature/Analytics
  2. Insert new View Rendering
  3. Name: "Google Analytics"
  4. Path: /Views/Analytics/GoogleAnalytics.cshtml

Step 2: Create Data Template

Template Definition:

/sitecore/templates/Feature/Analytics/Google Analytics Settings
├── Fields
│   ├── Measurement ID (Single-Line Text)
│   ├── Enable Tracking (Checkbox)
│   ├── Anonymize IP (Checkbox)
│   └── Enhanced Measurement (Checkbox)

Template in TDS or Unicorn:

# /serialization/Templates/Analytics/GoogleAnalyticsSettings.yml
---
ID: "{12345678-1234-1234-1234-123456789012}"
Path: /sitecore/templates/Feature/Analytics/Google Analytics Settings
Template: /sitecore/templates/System/Templates/Template
Fields:
- ID: "measurement-id"
  Name: "Measurement ID"
  Type: "Single-Line Text"
- ID: "enable-tracking"
  Name: "Enable Tracking"
  Type: "Checkbox"
  Value: "1"
- ID: "anonymize-ip"
  Name: "Anonymize IP"
  Type: "Checkbox"
  Value: "1"

Step 3: Create Settings Item

Content Editor:

  1. Navigate to /sitecore/content/[Your Site]/Settings
  2. Insert new item from Google Analytics Settings template
  3. Fill in Measurement ID and options

Step 4: Create View

@* /Views/Analytics/GoogleAnalytics.cshtml *@
@using Sitecore.Mvc
@using Sitecore.Data.Items

@{
    // Get settings from Sitecore
    var settingsPath = "/sitecore/content/" + Sitecore.Context.Site.Name + "/Settings/Google Analytics Settings";
    var settingsItem = Sitecore.Context.Database.GetItem(settingsPath);

    if (settingsItem != null)
    {
        var measurementId = settingsItem["Measurement ID"];
        var enableTracking = settingsItem["Enable Tracking"] == "1";
        var anonymizeIp = settingsItem["Anonymize IP"] == "1";

        if (enableTracking && !string.IsNullOrEmpty(measurementId) && !Sitecore.Context.PageMode.IsExperienceEditorEditing)
        {
            <!-- Google Analytics 4 -->
            <script async src="https://www.googletagmanager.com/gtag/js?id=@measurementId"></script>
            <script>
                window.dataLayer = window.dataLayer || [];
                function gtag(){dataLayer.push(arguments);}
                gtag('js', new Date());

                gtag('config', '@measurementId', {
                    @if (anonymizeIp)
                    {
                        <text>'anonymize_ip': true,</text>
                    }
                    'cookie_flags': 'SameSite=None;Secure'
                });
            </script>
        }
    }
}

Step 5: Add to Layout

Add the rendering to your page's presentation details or layout definition.

Method 3: Controller Rendering with Model

Best for: Complex logic, integration with Sitecore xDB

Step 1: Create Model

// /Models/Analytics/GoogleAnalyticsModel.cs
using Sitecore.Data.Items;

namespace YourProject.Models.Analytics
{
    public class GoogleAnalyticsModel
    {
        public string MeasurementId { get; set; }
        public bool EnableTracking { get; set; }
        public bool AnonymizeIp { get; set; }
        public bool EnhancedMeasurement { get; set; }
        public string UserId { get; set; } // Sitecore Contact ID
        public string Language { get; set; }
        public string PageType { get; set; }

        public bool ShouldRender =>
            EnableTracking &&
            !string.IsNullOrEmpty(MeasurementId) &&
            !Sitecore.Context.PageMode.IsExperienceEditorEditing;
    }
}

Step 2: Create Controller

// /Controllers/AnalyticsController.cs
using System.Web.Mvc;
using Sitecore.Analytics;
using Sitecore.Data.Items;
using YourProject.Models.Analytics;

namespace YourProject.Controllers
{
    public class AnalyticsController : Controller
    {
        public ActionResult GoogleAnalytics()
        {
            var model = new GoogleAnalyticsModel();

            // Get settings from Sitecore
            var settingsPath = $"/sitecore/content/{Sitecore.Context.Site.Name}/Settings/Google Analytics Settings";
            var settingsItem = Sitecore.Context.Database.GetItem(settingsPath);

            if (settingsItem != null)
            {
                model.MeasurementId = settingsItem["Measurement ID"];
                model.EnableTracking = settingsItem["Enable Tracking"] == "1";
                model.AnonymizeIp = settingsItem["Anonymize IP"] == "1";
                model.EnhancedMeasurement = settingsItem["Enhanced Measurement"] == "1";
            }

            // Get Sitecore context data
            model.Language = Sitecore.Context.Language.Name;
            model.PageType = Sitecore.Context.Item?.TemplateName;

            // Get Sitecore Contact ID (if xDB is enabled)
            if (Tracker.Current != null && Tracker.Current.Contact != null)
            {
                model.UserId = Tracker.Current.Contact.ContactId.ToString();
            }

            return View("/Views/Analytics/GoogleAnalytics.cshtml", model);
        }
    }
}

Step 3: Create Strongly-Typed View

@* /Views/Analytics/GoogleAnalytics.cshtml *@
@model YourProject.Models.Analytics.GoogleAnalyticsModel

@if (Model.ShouldRender)
{
    <!-- Google Analytics 4 -->
    <script async src="https://www.googletagmanager.com/gtag/js?id=@Model.MeasurementId"></script>
    <script>
        window.dataLayer = window.dataLayer || [];
        function gtag(){dataLayer.push(arguments);}
        gtag('js', new Date());

        gtag('config', '@Model.MeasurementId', {
            @if (Model.AnonymizeIp)
            {
                <text>'anonymize_ip': true,</text>
            }
            @if (!string.IsNullOrEmpty(Model.UserId))
            {
                <text>'user_id': '@Model.UserId',</text>
            }
            'language': '@Model.Language',
            'page_type': '@Model.PageType',
            'cookie_flags': 'SameSite=None;Secure'
        });
    </script>
}

Step 4: Register Controller Rendering

Content Editor:

  1. Navigate to /sitecore/layout/Renderings/Feature/Analytics
  2. Insert new Controller Rendering
  3. Controller: YourProject.Controllers.AnalyticsController, YourProject
  4. Controller Action: GoogleAnalytics

Method 4: Pipeline Processor (Site-Wide)

Best for: Enterprise implementations, site-wide tracking

Step 1: Create Pipeline Processor

// /Pipelines/HttpRequest/AddGoogleAnalytics.cs
using System.Web;
using Sitecore.Pipelines.HttpRequest;

namespace YourProject.Pipelines.HttpRequest
{
    public class AddGoogleAnalytics : HttpRequestProcessor
    {
        public override void Process(HttpRequestArgs args)
        {
            // Skip if in Experience Editor
            if (Sitecore.Context.PageMode.IsExperienceEditorEditing)
                return;

            // Skip if no context item
            if (Sitecore.Context.Item == null)
                return;

            // Get measurement ID from site properties
            var measurementId = Sitecore.Context.Site.Properties["googleAnalyticsId"];

            if (string.IsNullOrEmpty(measurementId))
                return;

            // Add GA4 script to page header
            var script = $@"
                <script async src='https://www.googletagmanager.com/gtag/js?id={measurementId}'></script>
                <script>
                    window.dataLayer = window.dataLayer || [];
                    function gtag(){{dataLayer.push(arguments);}}
                    gtag('js', new Date());
                    gtag('config', '{measurementId}', {{
                        'anonymize_ip': true,
                        'cookie_flags': 'SameSite=None;Secure'
                    }});
                </script>";

            // Inject into response (requires additional configuration)
            // This is a simplified example
            args.Context.Items["GoogleAnalytics"] = script;
        }
    }
}

Step 2: Register Pipeline

<!-- App_Config/Include/Analytics/GoogleAnalytics.config -->
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
  <sitecore>
    <pipelines>
      <httpRequestBegin>
        <processor type="YourProject.Pipelines.HttpRequest.AddGoogleAnalytics, YourProject"
                   patch:after="processor[@type='Sitecore.Pipelines.HttpRequest.ItemResolver, Sitecore.Kernel']"/>
      </httpRequestBegin>
    </pipelines>
  </sitecore>
</configuration>

Method 5: SXA (Sitecore Experience Accelerator)

Best for: SXA-based sites

Create SXA Component

1. Create Rendering Variant:

Navigate to your SXA site's rendering variants and create:

/sitecore/content/[Your Site]/Presentation/Rendering Variants/Analytics
├── Google Analytics
│   ├── Script Tag (Variant Field: Script)

2. Configure Variant Script:

<script async src="https://www.googletagmanager.com/gtag/js?id=$googleAnalyticsId"></script>
<script>
    window.dataLayer = window.dataLayer || [];
    function gtag(){dataLayer.push(arguments);}
    gtag('js', new Date());
    gtag('config', '$googleAnalyticsId', {
        'anonymize_ip': true
    });
</script>

3. Add to Partial Design:

Add the Google Analytics component to your SXA partial design for site-wide inclusion.

SXA Site Settings Extension

1. Extend SXA Site Settings template:

# Add GA fields to Site Settings
Template: /sitecore/templates/Foundation/Experience Accelerator/Sites/Site
Fields:
- ID: "google-analytics-id"
  Name: "Google Analytics ID"
  Type: "Single-Line Text"
  Section: "Analytics"

2. Access in SXA Component:

@using Sitecore.XA.Foundation.SitecoreExtensions.Extensions

@{
    var siteSettings = Html.Sxa().SiteSettings;
    var gaId = siteSettings?.GetItem()["Google Analytics ID"];
}

@if (!string.IsNullOrEmpty(gaId))
{
    <script>
        gtag('config', '@gaId');
    </script>
}

Method 6: Headless/JSS Implementation

Best for: Sitecore JSS (React, Vue, Angular, Next.js)

Next.js Implementation

1. Create Analytics Component:

// /src/components/Analytics/GoogleAnalytics.tsx
import Script from 'next/script';
import { useSitecoreContext } from '@sitecore-jss/sitecore-jss-nextjs';

interface GoogleAnalyticsProps {
  measurementId: string;
}

const GoogleAnalytics = ({ measurementId }: GoogleAnalyticsProps): JSX.Element | null => {
  const { sitecoreContext } = useSitecoreContext();

  // Don't render in Experience Editor
  if (sitecoreContext?.pageEditing) {
    return null;
  }

  if (!measurementId) {
    return null;
  }

  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(){window.dataLayer.push(arguments);}
          gtag('js', new Date());
          gtag('config', '${measurementId}', {
            'anonymize_ip': true,
            'cookie_flags': 'SameSite=None;Secure'
          });
        `}
      </Script>
    </>
  );
};

export default GoogleAnalytics;

2. Add to Layout:

// /src/Layout.tsx
import GoogleAnalytics from './components/Analytics/GoogleAnalytics';

const Layout = ({ layoutData }: LayoutProps): JSX.Element => {
  const GA_ID = process.env.NEXT_PUBLIC_GA_MEASUREMENT_ID || '';

  return (
    <>
      <GoogleAnalytics measurementId={GA_ID} />
      <div>
        {/* Your layout content */}
      </div>
    </>
  );
};

3. Environment Configuration:

# .env.local
NEXT_PUBLIC_GA_MEASUREMENT_ID=G-XXXXXXXXXX

React JSS Implementation

// /src/components/Analytics/GoogleAnalytics.js
import React, { useEffect } from 'react';
import { useSitecoreContext } from '@sitecore-jss/sitecore-jss-react';

const GoogleAnalytics = ({ fields }) => {
  const { sitecoreContext } = useSitecoreContext();
  const measurementId = fields?.measurementId?.value || process.env.REACT_APP_GA_ID;

  useEffect(() => {
    // Don't load in Experience Editor
    if (sitecoreContext.pageEditing || !measurementId) {
      return;
    }

    // Load GA4
    const script = document.createElement('script');
    script.src = `https://www.googletagmanager.com/gtag/js?id=${measurementId}`;
    script.async = true;
    document.head.appendChild(script);

    // Initialize GA4
    window.dataLayer = window.dataLayer || [];
    function gtag(){window.dataLayer.push(arguments);}
    gtag('js', new Date());
    gtag('config', measurementId, {
      'anonymize_ip': true,
      'language': sitecoreContext.language,
      'page_path': sitecoreContext.route?.itemPath
    });

  }, [measurementId, sitecoreContext]);

  return null; // No UI rendering
};

export default GoogleAnalytics;

JSS Component Definition

// /sitecore/definitions/components/GoogleAnalytics.sitecore.js
export default function GoogleAnalytics(manifest) {
  manifest.addComponent({
    name: 'GoogleAnalytics',
    displayName: 'Google Analytics',
    fields: [
      { name: 'measurementId', type: manifest.fieldTypes.singleLineText },
      { name: 'anonymizeIp', type: manifest.fieldTypes.checkbox },
    ],
  });
}

Integration with Sitecore xDB

Sync GA4 with Sitecore Analytics:

@using Sitecore.Analytics

@{
    string userId = null;
    string sessionId = null;

    if (Tracker.Current != null && Tracker.Current.IsActive)
    {
        var contact = Tracker.Current.Contact;
        if (contact != null)
        {
            userId = contact.ContactId.ToString("N");
        }

        var session = Tracker.Current.Session;
        if (session != null)
        {
            sessionId = session.SessionId.ToString("N");
        }
    }
}

<script>
    gtag('config', 'G-XXXXXXXXXX', {
        @if (!string.IsNullOrEmpty(userId))
        {
            <text>'user_id': '@userId',</text>
        }
        'custom_map': {
            'dimension1': 'sitecore_contact_id',
            'dimension2': 'sitecore_session_id'
        },
        'sitecore_contact_id': '@userId',
        'sitecore_session_id': '@sessionId'
    });
</script>

Data Layer Configuration for Sitecore XP/XM

Basic Data Layer Structure

Configure a Sitecore-specific data layer to capture CMS metadata:

@using Sitecore.Analytics
@using Sitecore.Data.Fields
@using Sitecore.Data.Items

@{
    var currentItem = Sitecore.Context.Item;
    var isPersonalized = Sitecore.Mvc.Analytics.Extensions.HtmlHelperExtensions.IsPersonalized(Html);
}

<script>
    window.dataLayer = window.dataLayer || [];
    dataLayer.push({
        'event': 'page_view',
        'page_data': {
            'item_id': '@currentItem.ID',
            'item_name': '@currentItem.Name',
            'template_id': '@currentItem.TemplateID',
            'template_name': '@currentItem.TemplateName',
            'item_path': '@currentItem.Paths.FullPath',
            'language': '@Sitecore.Context.Language.Name',
            'version': '@currentItem.Version.Number',
            'is_personalized': @isPersonalized.ToString().ToLower(),
            'site_name': '@Sitecore.Context.Site.Name',
            'database': '@Sitecore.Context.Database.Name'
        }
    });
</script>

Sitecore XP Personalization Data

Track which personalization rules are active:

@using Sitecore.Analytics.Model
@using Sitecore.Marketing.Definitions
@using Sitecore.Marketing.Definitions.Goals

@{
    var personalizations = new List<string>();

    if (Tracker.Current != null && Tracker.Current.IsActive)
    {
        var interaction = Tracker.Current.Interaction;
        if (interaction != null)
        {
            // Track active goals
            var goals = interaction.GetPages()
                .SelectMany(p => p.PageEvents)
                .Where(e => e.IsGoal)
                .Select(e => e.Name);

            // Track campaigns
            var campaignId = Tracker.Current.Interaction.CampaignId;
        }
    }
}

<script>
    dataLayer.push({
        'event': 'personalization_data',
        'personalization': {
            'is_personalized': @isPersonalized.ToString().ToLower(),
            'active_tests': [],  // Add A/B test data if applicable
            'profile_values': {}, // Add profile card values if needed
            'campaign_id': '@campaignId'
        }
    });
</script>

Content Metadata Tracking

Capture content author, creation date, and workflow state:

@{
    var currentItem = Sitecore.Context.Item;

    // Get content metadata
    var createdBy = currentItem.Statistics.CreatedBy;
    var createdDate = currentItem.Statistics.Created;
    var updatedBy = currentItem.Statistics.UpdatedBy;
    var updatedDate = currentItem.Statistics.Updated;

    // Get workflow state (if applicable)
    var workflowState = string.Empty;
    var workflowField = currentItem.Fields[Sitecore.FieldIDs.WorkflowState];
    if (workflowField != null && !string.IsNullOrEmpty(workflowField.Value))
    {
        var stateItem = Sitecore.Context.Database.GetItem(workflowField.Value);
        workflowState = stateItem?.Name ?? string.Empty;
    }
}

<script>
    dataLayer.push({
        'event': 'content_metadata',
        'content': {
            'created_by': '@createdBy',
            'created_date': '@createdDate.ToString("yyyy-MM-dd")',
            'updated_by': '@updatedBy',
            'updated_date': '@updatedDate.ToString("yyyy-MM-dd")',
            'workflow_state': '@workflowState',
            'item_version': '@currentItem.Version.Number'
        }
    });
</script>

Sitecore Commerce Integration

For Sitecore Commerce implementations:

@using Sitecore.Commerce.Entities.Carts
@using Sitecore.Commerce.Services.Carts

@{
    // Get current cart (simplified example)
    var cartService = new CartServiceProvider();
    var cart = cartService.GetCurrentCart();

    if (cart != null && cart.Lines.Any())
    {
        var cartValue = cart.Lines.Sum(l => l.Total.Amount);
        var itemCount = cart.Lines.Sum(l => l.Quantity);
    }
}

<script>
    @if (cart != null)
    {
        <text>
        dataLayer.push({
            'event': 'cart_data',
            'ecommerce': {
                'currency': '@cart.CurrencyCode',
                'value': @cartValue,
                'items': [
                    @foreach (var line in cart.Lines)
                    {
                        <text>{
                            'item_id': '@line.Product.ProductId',
                            'item_name': '@line.Product.DisplayName',
                            'quantity': @line.Quantity,
                            'price': @line.Product.ListPrice
                        },</text>
                    }
                ]
            }
        });
        </text>
    }
</script>

Multi-Site Data Layer

For multi-site Sitecore installations:

@{
    var site = Sitecore.Context.Site;
    var siteSettings = site.SiteInfo;
}

<script>
    dataLayer.push({
        'event': 'site_context',
        'site': {
            'name': '@site.Name',
            'hostname': '@site.HostName',
            'virtual_folder': '@site.VirtualFolder',
            'root_path': '@site.RootPath',
            'start_item': '@site.StartPath',
            'language': '@site.Language',
            'database': '@site.Database.Name'
        }
    });
</script>

Language and Localization

Track language and country for multi-language sites:

@{
    var language = Sitecore.Context.Language.Name;
    var country = Sitecore.Context.Language.CultureInfo.TwoLetterISOLanguageName;
    var displayName = Sitecore.Context.Language.CultureInfo.DisplayName;
}

<script>
    gtag('config', 'G-XXXXXXXXXX', {
        'language': '@language',
        'content_group': '@country',
        'custom_map': {
            'dimension3': 'sitecore_language'
        },
        'sitecore_language': '@displayName'
    });
</script>

Implement Google Consent Mode for GDPR compliance:

<script>
    window.dataLayer = window.dataLayer || [];
    function gtag(){dataLayer.push(arguments);}

    // Default consent state (denied)
    gtag('consent', 'default', {
        'analytics_storage': 'denied',
        'ad_storage': 'denied',
        'wait_for_update': 500
    });

    gtag('js', new Date());
    gtag('config', 'G-XXXXXXXXXX');
</script>
<script async src="https://www.googletagmanager.com/gtag/js?id=G-XXXXXXXXXX"></script>

<script>
    // After user consent
    document.addEventListener('cookieConsentGranted', function() {
        gtag('consent', 'update', {
            'analytics_storage': 'granted'
        });
    });
</script>

Environment-Specific Configuration

Use web.config transformations for different environments:

Web.Release.config:

<?xml version="1.0"?>
<configuration xmlns:xdt="http://schemas.microsoft.com/XML-Document-Transform">
  <appSettings>
    <add key="GoogleAnalytics.MeasurementId"
         value="G-PRODUCTION-ID"
         xdt:Transform="SetAttributes"
         xdt:Locator="Match(key)"/>
  </appSettings>
</configuration>

Web.Debug.config:

<?xml version="1.0"?>
<configuration xmlns:xdt="http://schemas.microsoft.com/XML-Document-Transform">
  <appSettings>
    <add key="GoogleAnalytics.MeasurementId"
         value="G-DEVELOPMENT-ID"
         xdt:Transform="SetAttributes"
         xdt:Locator="Match(key)"/>
  </appSettings>
</configuration>

Caching Considerations

Sitecore's output caching:

Cache-Safe Implementation:

@* This is cached with the page *@
@Html.Sitecore().Placeholder("main")

@* This runs client-side, safe with caching *@
<script>
    // Client-side dynamic data
    gtag('config', 'G-XXXXXXXXXX', {
        'page_path': window.location.pathname,
        'page_title': document.title
    });
</script>

Disable Caching for Dynamic Tracking:

<!-- In rendering definition -->
<rendering cacheable="false">
  <!-- Your GA rendering -->
</rendering>

Validation and Testing

1. Experience Editor Test

Verify tracking doesn't load in Experience Editor:

  • Open a page in Experience Editor
  • Check browser console - GA should not load
  • Verify no errors in console

2. Preview Mode Test

Test in Preview mode:

  • Use Review > Preview in Sitecore
  • Open browser DevTools > Network tab
  • Look for requests to googletagmanager.com/gtag/js
  • Check Real-Time reports in GA4

3. Published Site Test

Test on published site:

  • Clear Sitecore HTML cache
  • Visit published site as anonymous user
  • Verify in Google Analytics Real-Time reports

4. Multi-Site Testing

For multi-site:

  • Test each site domain
  • Verify correct tracking ID loads per site
  • Check Real-Time reports for each property

5. Sitecore-Specific Debugging Tools

Sitecore Debugger:

@* Add to layout for debugging *@
@if (Sitecore.Context.Diagnostics.Debugging)
{
    <div style="position: fixed; bottom: 0; right: 0; background: #000; color: #fff; padding: 10px; z-index: 9999;">
        <strong>Sitecore Debug Info:</strong><br/>
        Item: @Sitecore.Context.Item?.Name<br/>
        Template: @Sitecore.Context.Item?.TemplateName<br/>
        Database: @Sitecore.Context.Database?.Name<br/>
        Language: @Sitecore.Context.Language?.Name<br/>
        Site: @Sitecore.Context.Site?.Name<br/>
        Page Mode: @(Sitecore.Context.PageMode.IsExperienceEditorEditing ? "Experience Editor" : "Normal")<br/>
        xDB Tracking: @(Sitecore.Analytics.Tracker.Current?.IsActive.ToString() ?? "N/A")
    </div>
}

Enable Sitecore Debugging: In web.config:

<setting name="Sitecore.Diagnostics.EnableDebugging" value="true"/>

Check Sitecore Cache Stats: Navigate to /sitecore/admin/cache.aspx to verify:

  • HTML cache is enabled and working
  • View the size of cached renderings
  • Verify your GA rendering is being cached (if cacheable)

Sitecore Analytics Debugger:

@using Sitecore.Analytics

@if (Sitecore.Context.Diagnostics.Debugging && Tracker.Current != null)
{
    <div style="background: #ffc; padding: 10px; border: 1px solid #cc0;">
        <strong>xDB Debug Info:</strong><br/>
        Contact ID: @Tracker.Current.Contact?.ContactId<br/>
        Session ID: @Tracker.Current.Session?.SessionId<br/>
        Is Active: @Tracker.Current.IsActive<br/>
        Campaign ID: @Tracker.Current.Interaction?.CampaignId<br/>
        Goals Triggered: @Tracker.Current.Interaction?.GetPages().SelectMany(p => p.PageEvents).Count(e => e.IsGoal)
    </div>
}

6. Debugging Tools

Chrome DevTools:

// Check dataLayer (browser console)
console.log(window.dataLayer);

// Check gtag function
console.log(typeof gtag);

// Monitor all dataLayer pushes
const originalPush = window.dataLayer.push;
window.dataLayer.push = function() {
    console.log('dataLayer.push:', arguments);
    return originalPush.apply(this, arguments);
};

// Check if Sitecore Contact ID is being sent
console.log(window.dataLayer.find(item => item.sitecore_contact_id));

Google Tag Assistant:

  • Install Tag Assistant browser extension
  • Connect to your site
  • Verify GA4 tag fires correctly
  • Check for Sitecore-specific custom dimensions

GA4 DebugView: Enable debug mode:

<script>
    gtag('config', 'G-XXXXXXXXXX', {
        'debug_mode': true  // Only enable for testing
    });
</script>

Then check Admin > DebugView in GA4 to see real-time events with detailed parameters.

Common Sitecore-Specific Issues

Issue: Tracking Loads in Experience Editor

Cause: Missing Experience Editor check

Solution:

@if (!Sitecore.Context.PageMode.IsExperienceEditorEditing)
{
    @* GA code *@
}

Issue: Different Tracking IDs Needed Per Site

Cause: Multi-site setup

Solution: Use site properties or settings items per site (see Method 1 above)

Issue: Tracking Not Working After Publish

Cause: HTML cache

Solution:

# Clear HTML cache
# In Sitecore: Control Panel > Database > Clean up databases > HTML cache

Or programmatically:

Sitecore.Caching.CacheManager.ClearAllCaches();

Issue: Session/User ID Not Syncing with xDB

Cause: Tracker not initialized

Solution:

// Ensure xDB is tracking
if (Tracker.Current != null && !Tracker.Current.IsActive)
{
    Tracker.Current.StartTracking();
}

Performance Optimization

Preconnect to Google Domains

<link rel="preconnect" href="https://www.google-analytics.com">
<link rel="preconnect" href="https://www.googletagmanager.com">
<link rel="dns-prefetch" href="https://www.googletagmanager.com">

Delayed Loading

// Load GA4 after user interaction
let gaLoaded = false;
const loadGA = () => {
    if (gaLoaded) return;
    gaLoaded = true;

    const script = document.createElement('script');
    script.async = true;
    script.src = 'https://www.googletagmanager.com/gtag/js?id=G-XXXXXXXXXX';
    document.head.appendChild(script);

    window.dataLayer = window.dataLayer || [];
    function gtag(){dataLayer.push(arguments);}
    gtag('js', new Date());
    gtag('config', 'G-XXXXXXXXXX');
};

['mousedown', 'touchstart', 'scroll'].forEach(event => {
    window.addEventListener(event, loadGA, {once: true, passive: true});
});

// Fallback after 5s
setTimeout(loadGA, 5000);

Next Steps

// SYS.FOOTER