Google Tag Manager
Google Tag Manager is a tag management system that allows you to quickly and easily update tags and code snippets on your website or mobile app, such as those intended for traffic analysis and marketing optimization.
useScriptGoogleAnalytics() composable instead.Nuxt Config Setup
Add this to your nuxt.config.ts to load Google Tag Manager globally. Alternatively you can use the useScriptGoogleTagManager composable for more control.
export default defineNuxtConfig({
scripts: {
registry: {
googleTagManager: {
id: 'GTM-XXXXXX',
trigger: 'onNuxtReady',
}
}
}
})This config automatically enables first-party mode (bundle). See below to customise.
useScriptGoogleTagManager()
The useScriptGoogleTagManager composable lets you have fine-grain control over when and how Google Tag Manager is loaded on your site.
const { proxy } = useScriptGoogleTagManager()
proxy.dataLayer.push({ event: 'conversion', value: 1 })Please follow the Registry Scripts guide to learn more about advanced usage.
First-Party Mode: Privacy Focused Proxy
No extra config needed. The script is bundled and served from your domain instead of a third-party CDN, eliminating an extra DNS lookup and improving load times. Learn more.
export default defineNuxtConfig({
scripts: {
// ✅ First-party mode: bundled
registry: {
googleTagManager: {
id: 'GTM-XXXXXX',
trigger: 'onNuxtReady',
},
},
},
})Example
Using Google Tag Manager in a component.
<script setup lang="ts">
const { proxy } = useScriptGoogleTagManager()
// noop in development, ssr
// just works in production, client
function handleAction() {
proxy.dataLayer.push({ event: 'conversion', value: 1 })
}
</script>
<template>
<div>
<button @click="handleAction">
Send Event
</button>
</div>
</template>Guide: Sending Page Events
If you'd like to manually send page events to Google Tag Manager, you can use the proxy with the useScriptEventPage() composable.
This composable triggers the provided function on route change after Nuxt updates the page title.
const { proxy } = useScriptGoogleTagManager({
id: 'YOUR_ID' // id is only needed if you haven't configured globally
})
useScriptEventPage(({ title, path }) => {
// triggered on route change after title is updated
proxy.dataLayer.push({
event: 'pageview',
title,
path
})
})
Configuring GTM before it starts
useScriptGoogleTagManager() initializes Google Tag Manager by itself. This means it pushes the js, config and the gtm.start events for you.
If you need to configure GTM before it starts, for example setting the consent mode, you have two options:
Option 1: Using defaultConsent in nuxt.config (Recommended)
If you're configuring GTM in nuxt.config, use the defaultConsent option. See the Default consent mode example above.
Option 2: Using onBeforeGtmStart callback
If you're calling useScriptGoogleTagManager() with the ID directly in a component (not in nuxt.config), use the onBeforeGtmStart hook which runs right before the gtm.start event is pushed.
onBeforeGtmStart only works when the GTM ID is passed directly to useScriptGoogleTagManager(), not when configured globally in nuxt.config. For global config, use the defaultConsent option instead.Consent Mode v2 Signals
| Signal | Purpose |
|---|---|
ad_storage | Cookies for advertising |
ad_user_data | Send user data to Google for ads |
ad_personalization | Personalized ads (remarketing) |
analytics_storage | Cookies for analytics |
Updating Consent
When the user accepts, call gtag('consent', 'update', ...):
function acceptCookies() {
window.gtag?.('consent', 'update', {
ad_storage: 'granted',
ad_user_data: 'granted',
ad_personalization: 'granted',
analytics_storage: 'granted',
})
}
To block GTM until consent, combine with useScriptTriggerConsent().
<script setup lang="ts">
const consent = useState('consent', () => 'denied')
const { proxy } = useScriptGoogleTagManager({
onBeforeGtmStart: (gtag) => {
// set default consent state to denied
gtag('consent', 'default', {
ad_user_data: 'denied',
ad_personalization: 'denied',
ad_storage: 'denied',
analytics_storage: 'denied',
wait_for_update: 500,
})
// if consent was already given, update gtag accordingly
if (consent.value === 'granted') {
gtag('consent', 'update', {
ad_user_data: consent.value,
ad_personalization: consent.value,
ad_storage: consent.value,
analytics_storage: consent.value
})
}
}
})
// push pageview events to dataLayer
useScriptEventPage(({ title, path }) => {
proxy.dataLayer.push({
event: 'pageview',
title,
path
})
})
</script>
idstring required GTM container ID (format: GTM-XXXXXX)
lstring = 'dataLayer'Optional dataLayer variable name
authstringAuthentication token for environment-specific container versions
previewstringPreview environment name
cookiesWinboolean | 'x'debugboolean | 'x'Enables debug mode when true
npaboolean | '1'No Personal Advertising - disables advertising features when true
dataLayerstringenvNamestringEnvironment name for environment-specific container
authReferrerPolicystringdefaultConsentRecord<string, string | number>Default consent settings for GTM
Examples
Server-Side GTM Setup
Server-side GTM moves tag execution to your server for better privacy, performance (~500ms faster), and ad-blocker bypass.
Prerequisites: Server-side GTM container, hosting (Cloud Run / Docker), and a custom domain.
Configuration
Override the script source with your custom domain:
// nuxt.config.ts
export default defineNuxtConfig({
scripts: {
registry: {
googleTagManager: {
id: 'GTM-XXXXXX',
scriptInput: {
src: 'https://gtm.example.com/gtm.js'
}
}
}
}
})
For environment tokens (auth, preview), find them in GTM: Admin > Environments > Get Snippet.
Troubleshooting
| Issue | Cause | Solution |
|---|---|---|
| Script blocked by ad blocker | Custom domain detected as tracker | Use a non-obvious subdomain name (avoid gtm, analytics, tracking) |
| Cookies expire after 7 days in Safari | ITP treats subdomain as third-party | Use same-origin setup or implement cookie keeper |
| Preview mode not working | Missing or incorrect auth/preview tokens | Copy tokens from GTM: Admin > Environments > Get Snippet |
| CORS errors | Server container misconfigured | Ensure your server container allows requests from your domain |
gtm.js returns 404 | Incorrect path mapping | Verify your CDN/proxy routes /gtm.js to the container |