---
title: Partytown + Google Tag Manager in Hydrogen
description: >-
Add Partytown web worker integration for Google Tag Manager to improve
performance
source_url:
html: 'https://shopify.dev/docs/storefronts/headless/hydrogen/cookbook/partytown'
md: 'https://shopify.dev/docs/storefronts/headless/hydrogen/cookbook/partytown.md'
---
ExpandOn this page
* [Requirements](https://shopify.dev/docs/storefronts/headless/hydrogen/cookbook/partytown.md#requirements)
* [Ingredients](https://shopify.dev/docs/storefronts/headless/hydrogen/cookbook/partytown.md#ingredients)
* [Step 1: Ignore Partytown library files](https://shopify.dev/docs/storefronts/headless/hydrogen/cookbook/partytown.md#step-1-ignore-partytown-library-files)
* [Step 2: Create GTM web worker component](https://shopify.dev/docs/storefronts/headless/hydrogen/cookbook/partytown.md#step-2-create-gtm-web-worker-component)
* [Step 3: Document Partytown setup](https://shopify.dev/docs/storefronts/headless/hydrogen/cookbook/partytown.md#step-3-document-partytown-setup)
* [Step 4: Add CORS reverse proxy](https://shopify.dev/docs/storefronts/headless/hydrogen/cookbook/partytown.md#step-4-add-cors-reverse-proxy)
* [Step 5: Configure CSP headers](https://shopify.dev/docs/storefronts/headless/hydrogen/cookbook/partytown.md#step-5-configure-csp-headers)
* [Step 6: Add URL resolver for proxying](https://shopify.dev/docs/storefronts/headless/hydrogen/cookbook/partytown.md#step-6-add-url-resolver-for-proxying)
* [Step 7: Initialize Partytown and GTM](https://shopify.dev/docs/storefronts/headless/hydrogen/cookbook/partytown.md#step-7-initialize-partytown-and-gtm)
* [Step 8: Enable atomic mode](https://shopify.dev/docs/storefronts/headless/hydrogen/cookbook/partytown.md#step-8-enable-atomic-mode)
* [Step 9: Install Partytown](https://shopify.dev/docs/storefronts/headless/hydrogen/cookbook/partytown.md#step-9-install-partytown)
* [Step 10: Configure Vite for Partytown](https://shopify.dev/docs/storefronts/headless/hydrogen/cookbook/partytown.md#step-10-configure-vite-for-partytown)
* [Next steps](https://shopify.dev/docs/storefronts/headless/hydrogen/cookbook/partytown.md#next-steps)
# Partytown + Google Tag Manager in Hydrogen
This recipe integrates Partytown with your Hydrogen storefront to run Google Tag Manager and other third-party scripts in a web worker, keeping the main thread free for critical rendering tasks.
Key features:
* Moves GTM and analytics scripts off the main thread
* Improves Core Web Vitals scores
* Maintains full GTM functionality
* Includes CORS reverse proxy for third-party scripts
* CSP headers configured for GTM domains
Note
TypeScript users need to manually add GTM types to `env.d.ts`.
***
## Requirements
* Google Tag Manager container ID (remember to set your `GTM_CONTAINER_ID` or `GTM_ID` environment variable)
* Basic understanding of web workers and CSP
* Node.js 18.0.0 or higher
***
## Ingredients
*New files added to the template by this recipe.*
| File | Description |
| - | - |
| [app/components/PartytownGoogleTagManager.tsx](https://github.com/Shopify/hydrogen/blob/12374c8f03f82c6800000cf08e327c4db4c287bb/cookbook/recipes/partytown/ingredients/templates/skeleton/app/components/PartytownGoogleTagManager.tsx) | Component that loads GTM scripts in a web worker via Partytown |
| [app/routes/reverse-proxy.ts](https://github.com/Shopify/hydrogen/blob/12374c8f03f82c6800000cf08e327c4db4c287bb/cookbook/recipes/partytown/ingredients/templates/skeleton/app/routes/reverse-proxy.ts) | Reverse proxy route for third-party scripts requiring CORS headers |
| [app/utils/partytown/maybeProxyRequest.ts](https://github.com/Shopify/hydrogen/blob/12374c8f03f82c6800000cf08e327c4db4c287bb/cookbook/recipes/partytown/ingredients/templates/skeleton/app/utils/partytown/maybeProxyRequest.ts) | URL resolver to control which scripts should be reverse-proxied |
| [app/utils/partytown/partytownAtomicHeaders.ts](https://github.com/Shopify/hydrogen/blob/12374c8f03f82c6800000cf08e327c4db4c287bb/cookbook/recipes/partytown/ingredients/templates/skeleton/app/utils/partytown/partytownAtomicHeaders.ts) | Helper utility to enable Partytown atomic mode for better performance |
***
## Step 1: Ignore Partytown library files
Add `public/~partytown` to ignore Partytown library files.
#### File: [.gitignore](https://github.com/Shopify/hydrogen/blob/12374c8f03f82c6800000cf08e327c4db4c287bb/templates/skeleton/.gitignore) ([patch](https://github.com/Shopify/hydrogen/blob/12374c8f03f82c6800000cf08e327c4db4c287bb/cookbook/recipes/partytown/patches/.gitignore.0c7440.patch))
```diff
@@ -4,6 +4,7 @@ node_modules
/build
/dist
/public/build
+/public/~partytown
/.mf
.env
.shopify
```
***
## Step 2: Create GTM web worker component
Add a GTM component that loads scripts in a web worker.
#### File: [PartytownGoogleTagManager.tsx](https://github.com/Shopify/hydrogen/blob/12374c8f03f82c6800000cf08e327c4db4c287bb/cookbook/recipes/partytown/ingredients/templates/skeleton/app/components/PartytownGoogleTagManager.tsx)
## File
```tsx
import {useEffect, useRef} from 'react';
/**
* Component to add Google Tag Manager via Partytown
* @see https://partytown.builder.io/google-tag-manager
*/
export function PartytownGoogleTagManager(props: {
gtmContainerId: string | undefined;
dataLayerKey?: string;
}) {
const init = useRef(false);
const {gtmContainerId, dataLayerKey = 'dataLayer'} = props;
useEffect(() => {
if (init.current || !gtmContainerId) {
return;
}
const gtmScript = document.createElement('script');
const nonceScript = document.querySelector('[nonce]') as
| HTMLScriptElement
| undefined;
if (nonceScript?.nonce) {
gtmScript.setAttribute('nonce', nonceScript.nonce);
}
gtmScript.innerHTML = `
(function(w, d, s, l, i) {
w[l] = w[l] || [];
w[l].push({
'gtm.start': new Date().getTime(),
event: 'gtm.js'
});
var f = d.getElementsByTagName(s)[0],
j = d.createElement(s),
dl = l != 'dataLayer' ? '&l=' + l : '';
j.type = "text/partytown"
j.src =
'https://www.googletagmanager.com/gtm.js?id=' + i + dl + '&version=' + Date.now();
f.parentNode.insertBefore(j, f);
})(window, document, 'script', '${dataLayerKey}', '${gtmContainerId}');
`;
// Add the partytown GTM script to the body
document.body.appendChild(gtmScript);
init.current = true;
return () => {
document.body.removeChild(gtmScript);
};
}, [dataLayerKey, gtmContainerId]);
if (!gtmContainerId) {
return null;
}
return (
);
}
```
***
## Step 3: Document Partytown setup
Document Partytown setup and configuration instructions.
#### File: [README.md](https://github.com/Shopify/hydrogen/blob/12374c8f03f82c6800000cf08e327c4db4c287bb/templates/skeleton/README.md) ([patch](https://github.com/Shopify/hydrogen/blob/12374c8f03f82c6800000cf08e327c4db4c287bb/cookbook/recipes/partytown/patches/README.md.db10ed.patch))
## File
````diff
@@ -1,6 +1,6 @@
-# Hydrogen template: Skeleton
+# Hydrogen template: Skeleton + Partytown + Google Tag Manager
-Hydrogen is Shopify’s stack for headless commerce. Hydrogen is designed to dovetail with [Remix](https://remix.run/), Shopify’s full stack web framework. This template contains a **minimal setup** of components, queries and tooling to get started with Hydrogen.
+Hydrogen is Shopify's stack for headless commerce. Hydrogen is designed to dovetail with [Remix](https://remix.run/), Shopify's full stack web framework. This template contains a **minimal setup** of components, queries and tooling to get started with Hydrogen, enhanced with [Partytown](https://partytown.builder.io/) for performance-oriented lazy-loading of [Google Tag Manager](https://support.google.com/tagmanager).
[Check out Hydrogen docs](https://shopify.dev/custom-storefronts/hydrogen)
[Get familiar with Remix](https://remix.run/docs/en/v1)
@@ -17,12 +17,15 @@ Hydrogen is Shopify’s stack for headless commerce. Hydrogen is designed to dov
- GraphQL generator
- TypeScript and JavaScript flavors
- Minimal setup of components and routes
+- **Partytown** - Relocates resource intensive scripts off the main thread into a web worker
+- **Google Tag Manager** - Integration with CSP support
## Getting started
**Requirements:**
- Node.js version 18.0.0 or higher
+- [Google Tag Manager ID](https://support.google.com/tagmanager/answer/6103696?hl=en) (optional)
```bash
npm create @shopify/hydrogen@latest
@@ -40,6 +43,62 @@ npm run build
npm run dev
```
+## Partytown + Google Tag Manager Setup
+
+### Key files
+
+| File | Description |
+| --- | --- |
+| [`app/components/PartytownGoogleTagManager.tsx`](app/components/PartytownGoogleTagManager.tsx) | Component that loads GTM in a web worker via Partytown |
+| [`app/utils/partytown/maybeProxyRequest.ts`](app/utils/partytown/maybeProxyRequest.ts) | URL resolver for controlling which scripts should be reverse-proxied |
+| [`app/utils/partytown/partytownAtomicHeaders.ts`](app/utils/partytown/partytownAtomicHeaders.ts) | Headers utility for enabling Atomics mode |
+| [`app/routes/reverse-proxy.ts`](app/routes/reverse-proxy.ts) | Reverse proxy route for scripts requiring CORS headers |
+| [`app/root.tsx`](app/root.tsx) | Root layout with Partytown and GTM implementation |
+| [`app/entry.server.tsx`](app/entry.server.tsx) | Enhanced CSP configuration for GTM domains |
+
+### Configuration
+
+1. **Copy Partytown library files** (required for production):
+ ```bash
+ npm run partytown
+ ```
+
+2. **Set environment variables** in your `.env` file:
+ ```bash
+ GTM_CONTAINER_ID=GTM-XXXXXXX # Your Google Tag Manager container ID
+ # OR
+ GTM_ID=GTM-XXXXXXX
+ ```
+
+3. **TypeScript users**: Add the environment variable to your type definitions in `app/env.d.ts`:
+ ```typescript
+ interface Env extends HydrogenEnv {
+ GTM_CONTAINER_ID?: `GTM-${string}`;
+ GTM_ID?: `GTM-${string}`;
+ }
+ ```
+
+### How it works
+
+1. **Partytown** runs third-party scripts in a web worker, keeping the main thread free
+2. **GTM scripts** are loaded with `type="text/partytown"` to run in the worker
+3. **Reverse proxy** handles scripts that need CORS headers
+4. **CSP headers** are configured to allow GTM and Google Analytics domains
+
+### Performance benefits
+
+- Main thread remains responsive
+- Third-party scripts don't block critical rendering
+- Better Core Web Vitals scores
+- Improved user experience
+
## Setup for using Customer Account API (`/account` section)
Follow step 1 and 2 of
+
+## Resources
+
+- [Hydrogen documentation](https://shopify.dev/custom-storefronts/hydrogen)
+- [Partytown documentation](https://partytown.builder.io/)
+- [Google Tag Manager setup](https://support.google.com/tagmanager/answer/6103696)
+- [Introducing Partytown](https://dev.to/adamdbradley/introducing-partytown-run-third-party-scripts-from-a-web-worker-2cnp)
\ No newline at end of file
````
***
## Step 4: Add CORS reverse proxy
Reverse the proxy route for third-party scripts requiring CORS headers.
#### File: [reverse-proxy.ts](https://github.com/Shopify/hydrogen/blob/12374c8f03f82c6800000cf08e327c4db4c287bb/cookbook/recipes/partytown/ingredients/templates/skeleton/app/routes/reverse-proxy.ts)
## File
```ts
// Reverse proxies partytown libs that require CORS. Used by Partytown resolveUrl
//@see: https://developers.cloudflare.com/workers/examples/cors-header-proxy/
import type {Route} from './+types/reverse-proxy';
type HandleRequestResponHeaders = {
'Access-Control-Allow-Origin': string;
Vary: string;
'cache-control'?: string;
'content-type'?: string;
};
type CorsHeaders = {
'Access-Control-Allow-Origin': string;
'Access-Control-Allow-Methods': string;
'Access-Control-Max-Age': string;
'Access-Control-Allow-Headers'?: string;
};
const ALLOWED_PROXY_DOMAINS = new Set([
'https://cdn.jsdelivr.net',
'https://unpkg.com',
'https://google-analytics.com',
'https://www.googletagmanager.com',
'https://www.google-analytics.com',
// other domains you may want to allow to proxy to
]);
// Handle CORS preflight for POST requests
export async function action({request}: Route.ActionArgs) {
const url = new URL(request.url);
const isProxyReq = url.pathname.startsWith('/reverse-proxy');
if (!isProxyReq) {
return handleErrorResponse({
status: 405,
```
***
## Step 5: Configure CSP headers
Configure the CSP headers for GTM and Google Analytics domains.
#### File: [entry.server.tsx](https://github.com/Shopify/hydrogen/blob/12374c8f03f82c6800000cf08e327c4db4c287bb/templates/skeleton/app/entry.server.tsx) ([patch](https://github.com/Shopify/hydrogen/blob/12374c8f03f82c6800000cf08e327c4db4c287bb/cookbook/recipes/partytown/patches/entry.server.tsx.b35f11.patch))
```diff
@@ -19,6 +19,19 @@ export default async function handleRequest(
checkoutDomain: context.env.PUBLIC_CHECKOUT_DOMAIN,
storeDomain: context.env.PUBLIC_STORE_DOMAIN,
},
+ // @description Add CSP headers for Google Tag Manager and Analytics via Partytown
+ scriptSrc: [
+ "'self'",
+ 'cdn.shopify.com',
+ 'www.googletagmanager.com',
+ 'www.google-analytics.com',
+ 'localhost:*',
+ ],
+ connectSrc: [
+ "'self'",
+ 'www.googletagmanager.com',
+ 'www.google-analytics.com',
+ ],
});
const body = await renderToReadableStream(
```
***
## Step 6: Add URL resolver for proxying
Add a URL resolver to control which scripts should be reverse-proxied.
#### File: [maybeProxyRequest.ts](https://github.com/Shopify/hydrogen/blob/12374c8f03f82c6800000cf08e327c4db4c287bb/cookbook/recipes/partytown/ingredients/templates/skeleton/app/utils/partytown/maybeProxyRequest.ts)
## File
```ts
/**
* Partytown will call this function to resolve any URLs
* Many third-party scripts already provide the correct CORS headers, but not all do. For services that do not add the correct headers, then a reverse proxy to another domain must be used in order to provide the CORS headers.
* @param url - the URL to resolve
* @param location - the current location
* @param type - the type of request (script, image, etc)
* @returns URL or proxy URL
* @see https://partytown.builder.io/proxying-requests
*/
export function maybeProxyRequest(url: URL, location: Location, type: string) {
// Domains that already provide correct CORS headers
const nonProxyDomains: string[] = [];
// Don't proxy requests to certain domains
const bypassProxy = nonProxyDomains.some((domain) =>
url.host.includes(domain),
);
// Don't proxy requests that aren't scripts
if (type !== 'script' || bypassProxy) {
return url;
}
// If the url is already reverse proxied, don't proxy it again
if (url.href.includes('/reverse-proxy')) {
return url;
}
// Otherwise, proxy the url
const proxyUrl = new URL(`${location.origin}/reverse-proxy`);
proxyUrl.searchParams.append('apiUrl', url.href);
return proxyUrl;
}
```
***
## Step 7: Initialize Partytown and GTM
Initialize Partytown and GTM in the root layout.
#### File: [root.tsx](https://github.com/Shopify/hydrogen/blob/12374c8f03f82c6800000cf08e327c4db4c287bb/templates/skeleton/app/root.tsx) ([patch](https://github.com/Shopify/hydrogen/blob/12374c8f03f82c6800000cf08e327c4db4c287bb/cookbook/recipes/partytown/patches/root.tsx.5e9998.patch))
## File
```diff
@@ -1,4 +1,4 @@
-import {Analytics, getShopAnalytics, useNonce} from '@shopify/hydrogen';
+import {Analytics, getShopAnalytics, useNonce, Script} from '@shopify/hydrogen';
import {
Outlet,
useRouteError,
@@ -16,6 +16,10 @@ import {FOOTER_QUERY, HEADER_QUERY} from '~/lib/fragments';
import resetStyles from '~/styles/reset.css?url';
import appStyles from '~/styles/app.css?url';
import {PageLayout} from './components/PageLayout';
+// @description Import Partytown components for Google Tag Manager integration
+import {PartytownGoogleTagManager} from '~/components/PartytownGoogleTagManager';
+import {Partytown} from '@qwik.dev/partytown/react';
+import {maybeProxyRequest} from '~/utils/partytown/maybeProxyRequest';
export type RootLoader = typeof loader;
@@ -90,6 +94,10 @@ export async function loader(args: Route.LoaderArgs) {
country: args.context.storefront.i18n.country,
language: args.context.storefront.i18n.language,
},
+ // @description Pass GTM container ID from environment variables
+ gtmContainerId:
+ // @ts-ignore - GTM_ID and GTM_CONTAINER_ID are optional environment variables
+ args.context.env.GTM_ID || args.context.env.GTM_CONTAINER_ID,
};
}
@@ -163,6 +171,38 @@ export function Layout({children}: {children?: React.ReactNode}) {
);
}
+function PartyTownScripts({gtmContainerId}: {gtmContainerId: string}) {
+ const nonce = useNonce();
+ return (
+ <>
+ {/* @description Initialize Google Tag Manager data layer and Partytown web worker */}
+
+
+
+
+
+ >
+ );
+}
+
export default function App() {
const data = useRouteLoaderData('root');
@@ -177,6 +217,7 @@ export default function App() {
consent={data.consent}
>
+
```
***
## Step 8: Enable atomic mode
Add a helper utility to enable Partytown atomic mode for better performance.
#### File: [partytownAtomicHeaders.ts](https://github.com/Shopify/hydrogen/blob/12374c8f03f82c6800000cf08e327c4db4c287bb/cookbook/recipes/partytown/ingredients/templates/skeleton/app/utils/partytown/partytownAtomicHeaders.ts)
## File
```ts
/*
* Helper utility to enable PartyTown atomic mode
* @see: https://partytown.builder.io/atomics
*/
export function partytownAtomicHeaders() {
return {
'Cross-Origin-Embedder-Policy': 'credentialless',
'Cross-Origin-Opener-Policy': 'same-origin',
};
}
```
***
## Step 9: Install Partytown
Add a Partytown dependency and npm script for copying library files.
#### File: [package.json](https://github.com/Shopify/hydrogen/blob/12374c8f03f82c6800000cf08e327c4db4c287bb/templates/skeleton/package.json) ([patch](https://github.com/Shopify/hydrogen/blob/12374c8f03f82c6800000cf08e327c4db4c287bb/cookbook/recipes/partytown/patches/package.json.f30b0a.patch))
```diff
@@ -8,12 +8,14 @@
"build": "shopify hydrogen build --codegen",
"dev": "shopify hydrogen dev --codegen",
"preview": "shopify hydrogen preview --build",
- "lint": "eslint --no-error-on-unmatched-pattern .",
+ "lint": "eslint --no-error-on-unmatched-pattern --ignore-pattern 'public/~partytown/**' .",
"typecheck": "react-router typegen && tsc --noEmit",
- "codegen": "shopify hydrogen codegen && react-router typegen"
+ "codegen": "shopify hydrogen codegen && react-router typegen",
+ "partytown": "partytown copylib public/~partytown"
},
"prettier": "@shopify/prettier-config",
"dependencies": {
+ "@qwik.dev/partytown": "^0.11.2",
"@shopify/hydrogen": "2025.7.0",
"graphql": "^16.10.0",
"graphql-tag": "^2.12.6",
```
***
## Step 10: Configure Vite for Partytown
Configure Vite to exclude Partytown library from build optimization.
#### File: [vite.config.ts](https://github.com/Shopify/hydrogen/blob/12374c8f03f82c6800000cf08e327c4db4c287bb/templates/skeleton/vite.config.ts) ([patch](https://github.com/Shopify/hydrogen/blob/12374c8f03f82c6800000cf08e327c4db4c287bb/cookbook/recipes/partytown/patches/vite.config.ts.475b4c.patch))
```diff
@@ -23,7 +23,12 @@ export default defineConfig({
* Include 'example-dep' in the array below.
* @see https://vitejs.dev/config/dep-optimization-options
*/
- include: ['set-cookie-parser', 'cookie', 'react-router'],
+ include: [
+ 'set-cookie-parser',
+ 'cookie',
+ 'react-router',
+ '@qwik.dev/partytown/react',
+ ],
},
},
server: {
```
***
## Next steps
After applying this recipe:
1. Install dependencies:
```bash
npm install
```
1. Copy Partytown library files:
```bash
npm run partytown
```
1. Add your GTM container ID to `.env`:
```bash
GTM_CONTAINER_ID=GTM-XXXXXXX
```
1. For TypeScript projects, update `env.d.ts`:
```typescript
interface Env extends HydrogenEnv {
GTM_CONTAINER_ID?: `GTM-${string}`;
GTM_ID?: `GTM-${string}`;
}
```
1. Test your implementation:
```bash
npm run dev
```
Visit your site and check the Network tab to verify GTM is loading via the web worker.
***
* [Requirements](https://shopify.dev/docs/storefronts/headless/hydrogen/cookbook/partytown.md#requirements)
* [Ingredients](https://shopify.dev/docs/storefronts/headless/hydrogen/cookbook/partytown.md#ingredients)
* [Step 1: Ignore Partytown library files](https://shopify.dev/docs/storefronts/headless/hydrogen/cookbook/partytown.md#step-1-ignore-partytown-library-files)
* [Step 2: Create GTM web worker component](https://shopify.dev/docs/storefronts/headless/hydrogen/cookbook/partytown.md#step-2-create-gtm-web-worker-component)
* [Step 3: Document Partytown setup](https://shopify.dev/docs/storefronts/headless/hydrogen/cookbook/partytown.md#step-3-document-partytown-setup)
* [Step 4: Add CORS reverse proxy](https://shopify.dev/docs/storefronts/headless/hydrogen/cookbook/partytown.md#step-4-add-cors-reverse-proxy)
* [Step 5: Configure CSP headers](https://shopify.dev/docs/storefronts/headless/hydrogen/cookbook/partytown.md#step-5-configure-csp-headers)
* [Step 6: Add URL resolver for proxying](https://shopify.dev/docs/storefronts/headless/hydrogen/cookbook/partytown.md#step-6-add-url-resolver-for-proxying)
* [Step 7: Initialize Partytown and GTM](https://shopify.dev/docs/storefronts/headless/hydrogen/cookbook/partytown.md#step-7-initialize-partytown-and-gtm)
* [Step 8: Enable atomic mode](https://shopify.dev/docs/storefronts/headless/hydrogen/cookbook/partytown.md#step-8-enable-atomic-mode)
* [Step 9: Install Partytown](https://shopify.dev/docs/storefronts/headless/hydrogen/cookbook/partytown.md#step-9-install-partytown)
* [Step 10: Configure Vite for Partytown](https://shopify.dev/docs/storefronts/headless/hydrogen/cookbook/partytown.md#step-10-configure-vite-for-partytown)
* [Next steps](https://shopify.dev/docs/storefronts/headless/hydrogen/cookbook/partytown.md#next-steps)