--- title: Analytics event tracking with Hydrogen description: Learn how to set up analytics with Hydrogen and Oxygen, and transmit metrics events to Shopify analytics. source_url: html: https://shopify.dev/docs/storefronts/headless/hydrogen/analytics/tracking?framework=hydrogen&extension=javascript md: https://shopify.dev/docs/storefronts/headless/hydrogen/analytics/tracking.md?framework=hydrogen&extension=javascript --- # Analytics event tracking with Hydrogen Learn how to implement analytics events in your Hydrogen project and send tracked events to Shopify analytics and, optionally, third-party services. Tip Hydrogen includes analytics by default as of version 2024.4.3. You only need to do these tasks if you're upgrading an older version. ## Requirements [Hydrogen channel installed](https://apps.shopify.com/hydrogen) [Complete “Getting started with Hydrogen and Oxygen”](https://shopify.dev/docs/storefronts/headless/hydrogen/getting-started) [Configure Customer Privacy API settings](https://shopify.dev/docs/storefronts/headless/hydrogen/analytics/consent) ## Project ![](https://shopify.dev/images/logos/Hydrogen.svg)![](https://shopify.dev/images/logos/Hydrogen-dark.svg) Hydrogen ![](https://shopify.dev/images/logos/JS.svg)![](https://shopify.dev/images/logos/JS-dark.svg) JavaScript ## Update root.jsx To start sending analytics events, you need to set up the `Analytics` component in your Hydrogen project's root route. Import the `Analytics` component and the `getShopAnalytics` utility from Hydrogen. The `Analytics` component automatically checks for [consent](https://shopify.dev/docs/storefronts/headless/hydrogen/analytics/consent) before collecting any event data. ## /app/root.jsx ```jsx import { useNonce getShopAnalytics, Analytics, } from '@shopify/hydrogen'; import { Links, Meta, Outlet, Scripts, useRouteError, useRouteLoaderData, ScrollRestoration, isRouteErrorResponse, } from '@remix-run/react'; import favicon from '~/assets/favicon.svg'; import resetStyles from '~/styles/reset.css?url'; import appStyles from '~/styles/app.css?url'; import {PageLayout} from '~/components/PageLayout'; import {FOOTER_QUERY, HEADER_QUERY} from '~/lib/fragments'; import {ThirdPartyAnalyticsIntegration} from '~/components/ThirdPartyAnalyticsIntegration'; /** * This is important to avoid re-fetching root queries on sub-navigations * @type {ShouldRevalidateFunction} */ export const shouldRevalidate = ({ formMethod, currentUrl, nextUrl, }) => { // revalidate when a mutation is performed e.g add to cart, login... if (formMethod && formMethod !== 'GET') return true; // revalidate when manually revalidating via useRevalidator ``` Caution If you haven’t [configured consent](https://shopify.dev/docs/storefronts/headless/hydrogen/analytics/consent) through the Customer Privacy API, then analytics events won't fire and no data is tracked. Update the root loader function to destructure the `env` object from the Hydrogen context. ## /app/root.jsx ```jsx import { useNonce getShopAnalytics, Analytics, } from '@shopify/hydrogen'; import { Links, Meta, Outlet, Scripts, useRouteError, useRouteLoaderData, ScrollRestoration, isRouteErrorResponse, } from '@remix-run/react'; import favicon from '~/assets/favicon.svg'; import resetStyles from '~/styles/reset.css?url'; import appStyles from '~/styles/app.css?url'; import {PageLayout} from '~/components/PageLayout'; import {FOOTER_QUERY, HEADER_QUERY} from '~/lib/fragments'; import {ThirdPartyAnalyticsIntegration} from '~/components/ThirdPartyAnalyticsIntegration'; /** * This is important to avoid re-fetching root queries on sub-navigations * @type {ShouldRevalidateFunction} */ export const shouldRevalidate = ({ formMethod, currentUrl, nextUrl, }) => { // revalidate when a mutation is performed e.g add to cart, login... if (formMethod && formMethod !== 'GET') return true; // revalidate when manually revalidating via useRevalidator ``` Return the `shop` object by calling the `getShopAnalytics` utility. This function automatically collects the credentials required to send analytics events to Shopify. [![](https://shopify.dev/images/logos/Hydrogen.svg)![](https://shopify.dev/images/logos/Hydrogen-dark.svg)](https://shopify.dev/docs/api/hydrogen/current/utilities/getshopanalytics) [get​Shop​Analytics](https://shopify.dev/docs/api/hydrogen/current/utilities/getshopanalytics) ## /app/root.jsx ```jsx import { useNonce getShopAnalytics, Analytics, } from '@shopify/hydrogen'; import { Links, Meta, Outlet, Scripts, useRouteError, useRouteLoaderData, ScrollRestoration, isRouteErrorResponse, } from '@remix-run/react'; import favicon from '~/assets/favicon.svg'; import resetStyles from '~/styles/reset.css?url'; import appStyles from '~/styles/app.css?url'; import {PageLayout} from '~/components/PageLayout'; import {FOOTER_QUERY, HEADER_QUERY} from '~/lib/fragments'; import {ThirdPartyAnalyticsIntegration} from '~/components/ThirdPartyAnalyticsIntegration'; /** * This is important to avoid re-fetching root queries on sub-navigations * @type {ShouldRevalidateFunction} */ export const shouldRevalidate = ({ formMethod, currentUrl, nextUrl, }) => { // revalidate when a mutation is performed e.g add to cart, login... if (formMethod && formMethod !== 'GET') return true; // revalidate when manually revalidating via useRevalidator ``` Return the `consent` object with its required values. If you're using Shopify's cookie banner, then make sure `withPrivacyBanner` is set to `true`. ## /app/root.jsx ```jsx import { useNonce getShopAnalytics, Analytics, } from '@shopify/hydrogen'; import { Links, Meta, Outlet, Scripts, useRouteError, useRouteLoaderData, ScrollRestoration, isRouteErrorResponse, } from '@remix-run/react'; import favicon from '~/assets/favicon.svg'; import resetStyles from '~/styles/reset.css?url'; import appStyles from '~/styles/app.css?url'; import {PageLayout} from '~/components/PageLayout'; import {FOOTER_QUERY, HEADER_QUERY} from '~/lib/fragments'; import {ThirdPartyAnalyticsIntegration} from '~/components/ThirdPartyAnalyticsIntegration'; /** * This is important to avoid re-fetching root queries on sub-navigations * @type {ShouldRevalidateFunction} */ export const shouldRevalidate = ({ formMethod, currentUrl, nextUrl, }) => { // revalidate when a mutation is performed e.g add to cart, login... if (formMethod && formMethod !== 'GET') return true; // revalidate when manually revalidating via useRevalidator ``` Update the root component to wrap your Hydrogen app with the Analytics provider component. ## /app/root.jsx ```jsx import { useNonce getShopAnalytics, Analytics, } from '@shopify/hydrogen'; import { Links, Meta, Outlet, Scripts, useRouteError, useRouteLoaderData, ScrollRestoration, isRouteErrorResponse, } from '@remix-run/react'; import favicon from '~/assets/favicon.svg'; import resetStyles from '~/styles/reset.css?url'; import appStyles from '~/styles/app.css?url'; import {PageLayout} from '~/components/PageLayout'; import {FOOTER_QUERY, HEADER_QUERY} from '~/lib/fragments'; import {ThirdPartyAnalyticsIntegration} from '~/components/ThirdPartyAnalyticsIntegration'; /** * This is important to avoid re-fetching root queries on sub-navigations * @type {ShouldRevalidateFunction} */ export const shouldRevalidate = ({ formMethod, currentUrl, nextUrl, }) => { // revalidate when a mutation is performed e.g add to cart, login... if (formMethod && formMethod !== 'GET') return true; // revalidate when manually revalidating via useRevalidator ``` At this point, your app is set up to send analytics events to Shopify, but there aren't any events configured. In the next step, we'll add events to your storefront routes. ## Update routes with analytics subcomponents To track page views, you need to add pageview components to each route you want to track. In your product details page route, import the `Analytics` component from Hydrogen. [![](https://shopify.dev/images/logos/Hydrogen.svg)![](https://shopify.dev/images/logos/Hydrogen-dark.svg)](https://shopify.dev/docs/api/hydrogen/current/components/analytics/analytics-provider) [Analytics.Provider](https://shopify.dev/docs/api/hydrogen/current/components/analytics/analytics-provider) ## /app/routes/products.$handle.jsx ```jsx import {defer} from '@shopify/remix-oxygen'; import {useLoaderData} from '@remix-run/react'; import { getSelectedProductOptions, Analytics, useOptimisticVariant, getProductOptions, getAdjacentAndFirstAvailableVariants, useSelectedOptionInUrlParam, } from '@shopify/hydrogen'; import {getVariantUrl} from '~/lib/variants'; import {ProductPrice} from '~/components/ProductPrice'; import {ProductImage} from '~/components/ProductImage'; import {ProductForm} from '~/components/ProductForm'; /** * @type {MetaFunction} */ export const meta = ({data}) => { return [{title: `Hydrogen | ${data?.product.title ?? ''}`}]; }; /** * @param {LoaderFunctionArgs} args */ export async function loader(args) { // Start fetching non-critical data without blocking time to first byte const deferredData = loadDeferredData(args); // Await the critical data required to render initial state of the page const criticalData = await loadCriticalData(args); return {...deferredData, ...criticalData}; } /** ``` Add the `Analytics.ProductView` component to your nested route. This component sends a pageview event to Shopify when the route is loaded. The `ProductView` subcomponent takes a `data` prop with the product data to send to Shopify. [![](https://shopify.dev/images/logos/Hydrogen.svg)![](https://shopify.dev/images/logos/Hydrogen-dark.svg)](https://shopify.dev/docs/api/hydrogen/current/components/analytics/analytics-productview) [Analytics.Product​View](https://shopify.dev/docs/api/hydrogen/current/components/analytics/analytics-productview) ## /app/routes/products.$handle.jsx ```jsx import {defer} from '@shopify/remix-oxygen'; import {useLoaderData} from '@remix-run/react'; import { getSelectedProductOptions, Analytics, useOptimisticVariant, getProductOptions, getAdjacentAndFirstAvailableVariants, useSelectedOptionInUrlParam, } from '@shopify/hydrogen'; import {getVariantUrl} from '~/lib/variants'; import {ProductPrice} from '~/components/ProductPrice'; import {ProductImage} from '~/components/ProductImage'; import {ProductForm} from '~/components/ProductForm'; /** * @type {MetaFunction} */ export const meta = ({data}) => { return [{title: `Hydrogen | ${data?.product.title ?? ''}`}]; }; /** * @param {LoaderFunctionArgs} args */ export async function loader(args) { // Start fetching non-critical data without blocking time to first byte const deferredData = loadDeferredData(args); // Await the critical data required to render initial state of the page const criticalData = await loadCriticalData(args); return {...deferredData, ...criticalData}; } /** ``` Repeat this pattern for each route you want to track. Different routes have different preset analytics subcomponents, and require different data payloads. Follow the same pattern for collections routes... [![](https://shopify.dev/images/logos/Hydrogen.svg)![](https://shopify.dev/images/logos/Hydrogen-dark.svg)](https://shopify.dev/docs/api/hydrogen/current/components/analytics/analytics-collectionview) [Analytics.Collection​View](https://shopify.dev/docs/api/hydrogen/current/components/analytics/analytics-collectionview) ## /app/routes/collections.$handle.jsx ```jsx import {defer, redirect} from '@shopify/remix-oxygen'; import {useLoaderData, Link} from '@remix-run/react'; import { getPaginationVariables, Image, Money, Analytics, } from '@shopify/hydrogen'; import {useVariantUrl} from '~/lib/variants'; import {PaginatedResourceSection} from '~/components/PaginatedResourceSection'; /** * @type {MetaFunction} */ export const meta = ({data}) => { return [{title: `Hydrogen | ${data?.collection.title ?? ''} Collection`}]; }; /** * @param {LoaderFunctionArgs} args */ export async function loader(args) { // Start fetching non-critical data without blocking time to first byte const deferredData = loadDeferredData(args); // Await the critical data required to render initial state of the page const criticalData = await loadCriticalData(args); return {...deferredData, ...criticalData}; } /** * Load data necessary for rendering content above the fold. This is the critical data * needed to render the page. If it's unavailable, the whole page should 400 or 500 error. * @param {LoaderFunctionArgs} */ ``` ...the search results route... [![](https://shopify.dev/images/logos/Hydrogen.svg)![](https://shopify.dev/images/logos/Hydrogen-dark.svg)](https://shopify.dev/docs/api/hydrogen/current/components/analytics/analytics-searchview) [Analytics.Search​View](https://shopify.dev/docs/api/hydrogen/current/components/analytics/analytics-searchview) ## /app/routes/search.jsx ```jsx import {useLoaderData} from '@remix-run/react'; import {getPaginationVariables, Analytics} from '@shopify/hydrogen'; import {SearchForm} from '~/components/SearchForm'; import {SearchResults} from '~/components/SearchResults'; import {getEmptyPredictiveSearchResult} from '~/lib/search'; /** * @type {MetaFunction} */ export const meta = () => { return [{title: `Hydrogen | Search`}]; }; /** * @param {LoaderFunctionArgs} */ export async function loader({request, context}) { const url = new URL(request.url); const isPredictive = url.searchParams.has('predictive'); const searchPromise = isPredictive ? predictiveSearch({request, context}) : regularSearch({request, context}); searchPromise.catch((error) => { console.error(error); return {term: '', result: null, error: error.message}; }); return await searchPromise; } /** * Renders the /search route */ export default function SearchPage() { /** @type {LoaderReturnData} */ ``` ...and the cart route. [![](https://shopify.dev/images/logos/Hydrogen.svg)![](https://shopify.dev/images/logos/Hydrogen-dark.svg)](https://shopify.dev/docs/api/hydrogen/current/components/analytics/analytics-cartview) [Analytics.Cart​View](https://shopify.dev/docs/api/hydrogen/current/components/analytics/analytics-cartview) ## /app/routes/cart.jsx ```jsx import {useLoaderData} from '@remix-run/react'; import {CartForm, Analytics} from '@shopify/hydrogen'; import {data} from '@shopify/remix-oxygen'; import {CartMain} from '~/components/CartMain'; /** * @type {MetaFunction} */ export const meta = () => { return [{title: `Hydrogen | Cart`}]; }; /** * @type {HeadersFunction} */ export const headers = ({actionHeaders}) => actionHeaders; /** * @param {ActionFunctionArgs} */ export async function action({request, context}) { const {cart} = context; const formData = await request.formData(); const {action, inputs} = CartForm.getFormInput(formData); if (!action) { throw new Error('No action provided'); } let status = 200; let result; switch (action) { case CartForm.ACTIONS.LinesAdd: result = await cart.addLines(inputs.lines); break; case CartForm.ACTIONS.LinesUpdate: result = await cart.updateLines(inputs.lines); break; case CartForm.ACTIONS.LinesRemove: result = await cart.removeLines(inputs.lineIds); break; case CartForm.ACTIONS.DiscountCodesUpdate: { const formDiscountCode = inputs.discountCode; // User inputted discount code const discountCodes = formDiscountCode ? [formDiscountCode] : []; // Combine discount codes already applied on cart discountCodes.push(...inputs.discountCodes); result = await cart.updateDiscountCodes(discountCodes); break; } case CartForm.ACTIONS.GiftCardCodesUpdate: { const formGiftCardCode = inputs.giftCardCode; // User inputted gift card code const giftCardCodes = formGiftCardCode ? [formGiftCardCode] : []; // Combine gift card codes already applied on cart giftCardCodes.push(...inputs.giftCardCodes); result = await cart.updateGiftCardCodes(giftCardCodes); break; } case CartForm.ACTIONS.BuyerIdentityUpdate: { result = await cart.updateBuyerIdentity({ ...inputs.buyerIdentity, }); break; } default: throw new Error(`${action} cart action is not defined`); } const cartId = result?.cart?.id; const headers = cartId ? cart.setCartId(result.cart.id) : new Headers(); const {cart: cartResult, errors, warnings} = result; const redirectTo = formData.get('redirectTo') ?? null; if (typeof redirectTo === 'string') { status = 303; headers.set('Location', redirectTo); } return data( { cart: cartResult, errors, warnings, analytics: { cartId, }, }, {status, headers}, ); } /** * @param {LoaderFunctionArgs} args */ export async function loader({context}) { const {cart} = context; return await cart.get(); } export default function Cart() { /** @type {LoaderFunctionArgs} */ const cart = useLoaderData(); return (

Cart

); } /** @template T @typedef {import('@remix-run/react').MetaFunction} MetaFunction */ /** @template T @typedef {import('@remix-run/react').HeadersFunction} HeadersFunction */ /** @typedef {import('@shopify/hydrogen').CartQueryDataReturn} CartQueryDataReturn */ /** @typedef {import('@shopify/remix-oxygen').LoaderFunctionArgs} LoaderFunctionArgs */ /** @typedef {import('@shopify/remix-oxygen').SerializeFrom} ActionReturnData */ /** @typedef {import('@shopify/remix-oxygen').SerializeFrom} LoaderReturnData */ ``` If your cart is a side panel, you can publish the `cart_viewed` event with `useAnalytics`. ## /app/components/Header.jsx ```jsx import {Suspense} from 'react'; import {Await, NavLink, useAsyncValue} from '@remix-run/react'; import {useAnalytics, useOptimisticCart} from '@shopify/hydrogen'; import {useAside} from '~/components/Aside'; /** * @param {HeaderProps} */ export function Header({header, isLoggedIn, cart, publicStoreDomain}) { const {shop, menu} = header; return (
{shop.name}
); } /** * @param {{ * menu: HeaderProps['header']['menu']; * primaryDomainUrl: HeaderProps['header']['shop']['primaryDomain']['url']; * viewport: Viewport; * publicStoreDomain: HeaderProps['publicStoreDomain']; * }} */ export function HeaderMenu({ menu, ``` If you get the following error message in your browser console, make sure your cart query includes the`updatedAt` field. ``[h2:error:CartAnalytics] Can't set up cart analytics events because the `cart.updatedAt` value is missing from your GraphQL cart query. In standard Hydrogen projects, the cart query is contained in `app/lib/fragments.js`. Make sure it includes `cart.updatedAt`.`` ## /app/lib/fragments.js ```javascript // NOTE: https://shopify.dev/docs/api/storefront/latest/queries/cart export const CART_QUERY_FRAGMENT = `#graphql fragment Money on MoneyV2 { currencyCode amount } fragment CartLine on CartLine { id quantity attributes { key value } cost { totalAmount { ...Money } amountPerQuantity { ...Money } compareAtAmountPerQuantity { ...Money } } merchandise { ... on ProductVariant { id availableForSale compareAtPrice { ...Money } price { ...Money } requiresShipping title ``` ## Optional: Implement custom analytics with third-party services Using the Analytics provider component, you can create your own subcomponents to send events to third-party analytics services, in addition to Shopify. In your custom component file, import the `useAnalytics` hook from Hydrogen. ## /app/components/ThirdPartyAnalyticsIntegration.jsx ```jsx import {useAnalytics} from '@shopify/hydrogen'; import {useEffect} from 'react'; export function ThirdPartyAnalyticsIntegration() { const {subscribe} = useAnalytics(); // Register this analytics integration - this will prevent any analytics events // from being sent until this integration is ready const {ready} = register('Third Party Analytics Integration'); useEffect(() => { // Standard events subscribe('page_viewed', (data) => { console.log('ThirdPartyAnalyticsIntegration - Page viewed:', data); }); subscribe('product_viewed', (data) => { console.log('ThirdPartyAnalyticsIntegration - Product viewed:', data); }); subscribe('collection_viewed', (data) => { console.log('ThirdPartyAnalyticsIntegration - Collection viewed:', data); }); subscribe('cart_viewed', (data) => { console.log('ThirdPartyAnalyticsIntegration - Cart viewed:', data); }); subscribe('cart_updated', (data) => { console.log('ThirdPartyAnalyticsIntegration - Cart updated:', data); }); // Custom events subscribe('custom_checkbox_toggled', (data) => { console.log('ThirdPartyAnalyticsIntegration - Custom checkbox toggled:', data); }); // Mark this analytics integration as ready as soon as it's done setting up ready(); }, []); return null; } ``` Export your custom component so it can be used in your Hydrogen routes. ## /app/components/ThirdPartyAnalyticsIntegration.jsx ```jsx import {useAnalytics} from '@shopify/hydrogen'; import {useEffect} from 'react'; export function ThirdPartyAnalyticsIntegration() { const {subscribe} = useAnalytics(); // Register this analytics integration - this will prevent any analytics events // from being sent until this integration is ready const {ready} = register('Third Party Analytics Integration'); useEffect(() => { // Standard events subscribe('page_viewed', (data) => { console.log('ThirdPartyAnalyticsIntegration - Page viewed:', data); }); subscribe('product_viewed', (data) => { console.log('ThirdPartyAnalyticsIntegration - Product viewed:', data); }); subscribe('collection_viewed', (data) => { console.log('ThirdPartyAnalyticsIntegration - Collection viewed:', data); }); subscribe('cart_viewed', (data) => { console.log('ThirdPartyAnalyticsIntegration - Cart viewed:', data); }); subscribe('cart_updated', (data) => { console.log('ThirdPartyAnalyticsIntegration - Cart updated:', data); }); // Custom events subscribe('custom_checkbox_toggled', (data) => { console.log('ThirdPartyAnalyticsIntegration - Custom checkbox toggled:', data); }); // Mark this analytics integration as ready as soon as it's done setting up ready(); }, []); return null; } ``` The `subscribe` function provided by `useAnalytics` listens for standard analytics events from your routes, and provides a callback function with the corresponding data. [Web Pixels API standard events](https://shopify.dev/docs/api/web-pixels-api/standard-events) ## /app/components/ThirdPartyAnalyticsIntegration.jsx ```jsx import {useAnalytics} from '@shopify/hydrogen'; import {useEffect} from 'react'; export function ThirdPartyAnalyticsIntegration() { const {subscribe} = useAnalytics(); // Register this analytics integration - this will prevent any analytics events // from being sent until this integration is ready const {ready} = register('Third Party Analytics Integration'); useEffect(() => { // Standard events subscribe('page_viewed', (data) => { console.log('ThirdPartyAnalyticsIntegration - Page viewed:', data); }); subscribe('product_viewed', (data) => { console.log('ThirdPartyAnalyticsIntegration - Product viewed:', data); }); subscribe('collection_viewed', (data) => { console.log('ThirdPartyAnalyticsIntegration - Collection viewed:', data); }); subscribe('cart_viewed', (data) => { console.log('ThirdPartyAnalyticsIntegration - Cart viewed:', data); }); subscribe('cart_updated', (data) => { console.log('ThirdPartyAnalyticsIntegration - Cart updated:', data); }); // Custom events subscribe('custom_checkbox_toggled', (data) => { console.log('ThirdPartyAnalyticsIntegration - Custom checkbox toggled:', data); }); // Mark this analytics integration as ready as soon as it's done setting up ready(); }, []); return null; } ``` Register the analytics integration with ``. This will prevent any analytics events from being sent until this integration is ready. ## /app/components/ThirdPartyAnalyticsIntegration.jsx ```jsx import {useAnalytics} from '@shopify/hydrogen'; import {useEffect} from 'react'; export function ThirdPartyAnalyticsIntegration() { const {subscribe} = useAnalytics(); // Register this analytics integration - this will prevent any analytics events // from being sent until this integration is ready const {ready} = register('Third Party Analytics Integration'); useEffect(() => { // Standard events subscribe('page_viewed', (data) => { console.log('ThirdPartyAnalyticsIntegration - Page viewed:', data); }); subscribe('product_viewed', (data) => { console.log('ThirdPartyAnalyticsIntegration - Product viewed:', data); }); subscribe('collection_viewed', (data) => { console.log('ThirdPartyAnalyticsIntegration - Collection viewed:', data); }); subscribe('cart_viewed', (data) => { console.log('ThirdPartyAnalyticsIntegration - Cart viewed:', data); }); subscribe('cart_updated', (data) => { console.log('ThirdPartyAnalyticsIntegration - Cart updated:', data); }); // Custom events subscribe('custom_checkbox_toggled', (data) => { console.log('ThirdPartyAnalyticsIntegration - Custom checkbox toggled:', data); }); // Mark this analytics integration as ready as soon as it's done setting up ready(); }, []); return null; } ``` For each event you want to track in your custom analytics component, use the subscribe function to listen for the event and send the analytics data to your third-party analytics platform. This example just logs the event data to the console. It's up to you to replace this with your own custom analytics logic, depending on the platform you're using. ## /app/components/ThirdPartyAnalyticsIntegration.jsx ```jsx import {useAnalytics} from '@shopify/hydrogen'; import {useEffect} from 'react'; export function ThirdPartyAnalyticsIntegration() { const {subscribe} = useAnalytics(); // Register this analytics integration - this will prevent any analytics events // from being sent until this integration is ready const {ready} = register('Third Party Analytics Integration'); useEffect(() => { // Standard events subscribe('page_viewed', (data) => { console.log('ThirdPartyAnalyticsIntegration - Page viewed:', data); }); subscribe('product_viewed', (data) => { console.log('ThirdPartyAnalyticsIntegration - Product viewed:', data); }); subscribe('collection_viewed', (data) => { console.log('ThirdPartyAnalyticsIntegration - Collection viewed:', data); }); subscribe('cart_viewed', (data) => { console.log('ThirdPartyAnalyticsIntegration - Cart viewed:', data); }); subscribe('cart_updated', (data) => { console.log('ThirdPartyAnalyticsIntegration - Cart updated:', data); }); // Custom events subscribe('custom_checkbox_toggled', (data) => { console.log('ThirdPartyAnalyticsIntegration - Custom checkbox toggled:', data); }); // Mark this analytics integration as ready as soon as it's done setting up ready(); }, []); return null; } ``` Notify `` that this analytics integration is ready to receive events. ## /app/components/ThirdPartyAnalyticsIntegration.jsx ```jsx import {useAnalytics} from '@shopify/hydrogen'; import {useEffect} from 'react'; export function ThirdPartyAnalyticsIntegration() { const {subscribe} = useAnalytics(); // Register this analytics integration - this will prevent any analytics events // from being sent until this integration is ready const {ready} = register('Third Party Analytics Integration'); useEffect(() => { // Standard events subscribe('page_viewed', (data) => { console.log('ThirdPartyAnalyticsIntegration - Page viewed:', data); }); subscribe('product_viewed', (data) => { console.log('ThirdPartyAnalyticsIntegration - Product viewed:', data); }); subscribe('collection_viewed', (data) => { console.log('ThirdPartyAnalyticsIntegration - Collection viewed:', data); }); subscribe('cart_viewed', (data) => { console.log('ThirdPartyAnalyticsIntegration - Cart viewed:', data); }); subscribe('cart_updated', (data) => { console.log('ThirdPartyAnalyticsIntegration - Cart updated:', data); }); // Custom events subscribe('custom_checkbox_toggled', (data) => { console.log('ThirdPartyAnalyticsIntegration - Custom checkbox toggled:', data); }); // Mark this analytics integration as ready as soon as it's done setting up ready(); }, []); return null; } ``` Once you've created your custom analytics component, import it into your root route. ## /app/root.jsx ```jsx import { useNonce getShopAnalytics, Analytics, } from '@shopify/hydrogen'; import { Links, Meta, Outlet, Scripts, useRouteError, useRouteLoaderData, ScrollRestoration, isRouteErrorResponse, } from '@remix-run/react'; import favicon from '~/assets/favicon.svg'; import resetStyles from '~/styles/reset.css?url'; import appStyles from '~/styles/app.css?url'; import {PageLayout} from '~/components/PageLayout'; import {FOOTER_QUERY, HEADER_QUERY} from '~/lib/fragments'; import {ThirdPartyAnalyticsIntegration} from '~/components/ThirdPartyAnalyticsIntegration'; /** * This is important to avoid re-fetching root queries on sub-navigations * @type {ShouldRevalidateFunction} */ export const shouldRevalidate = ({ formMethod, currentUrl, nextUrl, }) => { // revalidate when a mutation is performed e.g add to cart, login... if (formMethod && formMethod !== 'GET') return true; // revalidate when manually revalidating via useRevalidator ``` Add your custom component to your app's root layout. Your component needs to be a child of the `Analytics.Provider` component in order to subscribe to events and receive analytics data. ## /app/root.jsx ```jsx import { useNonce getShopAnalytics, Analytics, } from '@shopify/hydrogen'; import { Links, Meta, Outlet, Scripts, useRouteError, useRouteLoaderData, ScrollRestoration, isRouteErrorResponse, } from '@remix-run/react'; import favicon from '~/assets/favicon.svg'; import resetStyles from '~/styles/reset.css?url'; import appStyles from '~/styles/app.css?url'; import {PageLayout} from '~/components/PageLayout'; import {FOOTER_QUERY, HEADER_QUERY} from '~/lib/fragments'; import {ThirdPartyAnalyticsIntegration} from '~/components/ThirdPartyAnalyticsIntegration'; /** * This is important to avoid re-fetching root queries on sub-navigations * @type {ShouldRevalidateFunction} */ export const shouldRevalidate = ({ formMethod, currentUrl, nextUrl, }) => { // revalidate when a mutation is performed e.g add to cart, login... if (formMethod && formMethod !== 'GET') return true; // revalidate when manually revalidating via useRevalidator ``` ## /app/root.jsx ```jsx import { useNonce getShopAnalytics, Analytics, } from '@shopify/hydrogen'; import { Links, Meta, Outlet, Scripts, useRouteError, useRouteLoaderData, ScrollRestoration, isRouteErrorResponse, } from '@remix-run/react'; import favicon from '~/assets/favicon.svg'; import resetStyles from '~/styles/reset.css?url'; import appStyles from '~/styles/app.css?url'; import {PageLayout} from '~/components/PageLayout'; import {FOOTER_QUERY, HEADER_QUERY} from '~/lib/fragments'; import {ThirdPartyAnalyticsIntegration} from '~/components/ThirdPartyAnalyticsIntegration'; /** * This is important to avoid re-fetching root queries on sub-navigations * @type {ShouldRevalidateFunction} */ export const shouldRevalidate = ({ formMethod, currentUrl, nextUrl, }) => { // revalidate when a mutation is performed e.g add to cart, login... if (formMethod && formMethod !== 'GET') return true; // revalidate when manually revalidating via useRevalidator ``` ### Next steps [![](https://shopify.dev/images/icons/32/gear.png)![](https://shopify.dev/images/icons/32/gear-dark.png)](https://shopify.dev/docs/storefronts/headless/hydrogen/analytics/validation) [Validate and troubleshoot Hydrogen analytics](https://shopify.dev/docs/storefronts/headless/hydrogen/analytics/validation) [Test that your analytics are working and check for common errors](https://shopify.dev/docs/storefronts/headless/hydrogen/analytics/validation)