If you’re setting up Nuxt Google Analytics 4 and find that tracking isn’t working, you’re not alone. Recently, we ran into this exact issue on afitpilot.com — pageviews weren’t showing, no g/collect
requests were firing, and GA was basically dead in the water.
Here’s the full breakdown of what went wrong, how we fixed it, and the lessons worth remembering for Nuxt Google Analytics 4.
The Problem
Even though the GA Measurement ID was set correctly in nuxt.config.ts
, Analytics was failing completely in production.


Symptoms:
- No
<script>
tags forgtag.js
in the HTML source - No inline GA initialization script
- No
g/collect
requests in the Network tab - GA dashboard showed zero activity
At first glance, everything looked fine — GA ID was present, GTM noscript iframe was loaded — but tracking just wasn’t happening for Nuxt Google Analytics 4.
The Root Cause
The issue came down to script loading order and timing.
Our analytics plugin (analytics-unified.client.ts
) was:
- Injecting the GA script client-side with
document.createElement('script')
- Calling
gtag()
before the library had finished loading - Never outputting the GA scripts during server-side rendering (SSR)
This created a race condition: GA was called before it was ready. As a result, no tracking initialized.
The Fix
The Nuxt Google Analytics 4 solution was surprisingly simple: move Analytics initialization into app.vue
using Nuxt’s useHead()
composable.


Correct Implementation in app.vue
<script setup>
const config = useRuntimeConfig()
const gaId = config.public.googleAnalyticsId
useHead({
script: gaId && gaId !== 'G-XXXXXXXXXX' ? [
{
children: `window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', '${gaId}', {
send_page_view: true,
anonymize_ip: true,
cookie_flags: 'SameSite=None;Secure'
});`,
type: 'text/javascript'
},
{
src: `https://www.googletagmanager.com/gtag/js?id=${gaId}`,
async: true
}
] : [],
link: [
{ rel: 'preconnect', href: 'https://www.googletagmanager.com' },
{ rel: 'preconnect', href: 'https://www.google-analytics.com' }
]
})
</script>
Why This Works


✅ SSR Injection: Scripts are rendered in the initial HTML, not added after hydration.
✅ Correct Load Order: Inline gtag()
definition comes before the external script loads.
✅ Immediate Execution: GA is ready as soon as the page loads.
✅ Async Script: gtag.js loads non-blocking but still finds the pre-populated dataLayer
.
Verifying the Fix
After deploying, here’s how we confirmed everything was working:
- View Source → GA inline + external scripts appear in HTML
- Network Tab →
g/collect
requests firing - Console →
window.dataLayer
populated - GA Dashboard → Real-time users now showing
Key Lessons
- Do this: use
useHead()
inapp.vue
for critical scripts - Don’t do this: inject GA via client-only plugins with DOM manipulation
- Order matters: define
gtag()
and dataLayer before loading gtag.js - Test in production: GA often fails silently unless you check HTML + network traffic
Final Take
Google Analytics is notoriously picky about how it’s initialized. In our case, fixing the load order and moving initialization to SSR was the difference between no tracking at all and a fully functioning GA setup.
If you’re struggling with GA not firing in your Nuxt app, start by checking your HTML source. If the scripts aren’t there, chances are you’re injecting them too late.
For more of my dev fixes, see:
Leave a Reply