name add-analytics description Add Google Analytics 4 tracking to any project. Detects framework, adds tracking code, sets up events, and configures privacy settings. argument-hint <measurement-id> [--events] [--consent] [--debug]
Google Analytics 4 Setup Skill
You are setting up Google Analytics 4 (GA4) for a project. Follow this comprehensive guide to add analytics properly.
Arguments
Parse the following from $ARGUMENTS:
Measurement ID : Format G-XXXXXXXXXX (required, ask if not provided)
--events : Include custom event tracking helpers
--consent : Include cookie consent integration
--debug : Enable debug mode for development
Step 1: Detect Project Type
Scan the project to determine the framework/setup:
Priority detection order:
1. next.config.js/ts → Next.js
2. nuxt.config.js/ts → Nuxt.js
3. astro.config.mjs → Astro
4. svelte.config.js → SvelteKit
5. remix.config.js → Remix
6. gatsby-config.js → Gatsby
7. vite.config.js + src/App.vue → Vue + Vite
8. vite.config.js + src/App.tsx → React + Vite
9. angular.json → Angular
10. package.json with "react-scripts" → Create React App
11. index.html only → Plain HTML
12. _app.tsx/jsx → Next.js (App Router check: app/ directory)
Also check for:
TypeScript usage (tsconfig.json)
Existing analytics (search for gtag, GA, analytics)
Package manager (pnpm-lock.yaml, yarn.lock, package-lock.json)
Step 2: Validate Measurement ID
The Measurement ID must:
Start with G- (GA4 format)
Be followed by exactly 10 alphanumeric characters
Example: G-ABC1234567
If the user provides a UA- ID, inform them:
"You provided a Universal Analytics ID (UA-). GA4 uses Measurement IDs starting with 'G-'.
Universal Analytics was sunset in July 2024. You'll need to create a GA4 property at analytics.google.com"
Step 3: Implementation by Framework
Next.js (App Router - app/ directory)
Create app/layout.tsx modification or create components/GoogleAnalytics.tsx:
'use client'
import Script from 'next/script'
interface GoogleAnalyticsProps {
measurementId : string
}
export function GoogleAnalytics ({ measurementId }: GoogleAnalyticsProps ) {
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(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', '${measurementId} ');
` }
</Script >
</>
)
}
Add to root layout:
import { GoogleAnalytics } from '@/components/GoogleAnalytics'
<GoogleAnalytics measurementId="G-XXXXXXXXXX" />
Next.js (Pages Router - pages/ directory)
Modify pages/_app.tsx:
import type { AppProps } from 'next/app'
import Script from 'next/script'
const GA_MEASUREMENT_ID = process.env .NEXT_PUBLIC_GA_MEASUREMENT_ID
export default function App ({ Component, pageProps }: AppProps ) {
return (
<>
<Script
src ={ `https: //www.googletagmanager.com /gtag /js ?id =${GA_MEASUREMENT_ID} `}
strategy ="afterInteractive"
/>
<Script id ="google-analytics" strategy ="afterInteractive" >
{`
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', '${GA_MEASUREMENT_ID} ');
` }
</Script >
<Component {...pageProps } />
</>
)
}
React (Vite/CRA)
Create src/lib/analytics.ts:
export const GA_MEASUREMENT_ID = import .meta .env .VITE_GA_MEASUREMENT_ID
declare global {
interface Window {
gtag : (...args : unknown [] ) => void
dataLayer : unknown []
}
}
export const initGA = ( ) => {
if (typeof window === 'undefined' ) return
const script = document .createElement ('script' )
script.src = `https://www.googletagmanager.com/gtag/js?id=${GA_MEASUREMENT_ID} `
script.async = true
document .head .appendChild (script)
window .dataLayer = window .dataLayer || []
window .gtag = function gtag ( ) {
window .dataLayer . ( )
}
. ( , ())
. ( , )
}
= ( ) => {
. ( , , {
: url,
})
}
= ( ) => {
. ( , action, params)
}
Initialize in src/main.tsx:
import { initGA } from './lib/analytics'
if (import .meta .env .PROD ) {
initGA ()
}
Vue 3 (Vite)
Create src/plugins/analytics.ts:
import type { App } from 'vue'
import type { Router } from 'vue-router'
const GA_MEASUREMENT_ID = import .meta .env .VITE_GA_MEASUREMENT_ID
declare global {
interface Window {
gtag : (...args : unknown [] ) => void
dataLayer : unknown []
}
}
export const analyticsPlugin = {
install (app : App , { router }: { router: Router } ) {
const script = document .createElement ('script' )
script.src = `https://www.googletagmanager.com/gtag/js?id=${GA_MEASUREMENT_ID} `
script.async = true
document .head .appendChild (script)
window .dataLayer = window . || []
. = ( ) {
. . ( )
}
. ( , ())
. ( , )
router. ( {
. ( , , {
: to. ,
})
})
app. . . = .
}
}
Nuxt 3
Create plugins/analytics.client.ts:
export default defineNuxtPlugin (() => {
const config = useRuntimeConfig ()
const measurementId = config.public .gaMeasurementId
if (!measurementId) return
useHead ({
script : [
{
src : `https://www.googletagmanager.com/gtag/js?id=${measurementId} ` ,
async : true ,
},
{
innerHTML : `
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', '${measurementId} ');
` ,
},
],
})
const router = useRouter ()
router.afterEach ((to ) => {
window .gtag ('config' , measurementId, {
page_path : to.fullPath ,
})
})
})
Add to nuxt.config.ts:
export default defineNuxtConfig ({
runtimeConfig : {
public : {
gaMeasurementId : process.env .NUXT_PUBLIC_GA_MEASUREMENT_ID ,
},
},
})
Astro
Create src/components/Analytics.astro:
---
// src/components/Analytics.astro
interface Props {
measurementId: string
}
const { measurementId } = Astro.props
---
<script
is:inline
define:vars={{ measurementId }}
src={`https://www.googletagmanager.com/gtag/js?id=${measurementId}`}
></script>
<script is:inline define:vars={{ measurementId }}>
window.dataLayer = window.dataLayer || [];
function gtag() {
dataLayer.push(arguments);
}
gtag('js', new Date());
gtag('config', measurementId);
</script>
Add to layout:
---
import Analytics from '../components/Analytics.astro'
---
<html>
<head>
<Analytics measurementId="G-XXXXXXXXXX" />
</head>
</html>
SvelteKit
Create src/lib/analytics.ts and src/routes/+layout.svelte:
import { browser } from '$app/environment'
export const GA_MEASUREMENT_ID = import .meta .env .VITE_GA_MEASUREMENT_ID
export function initGA ( ) {
if (!browser) return
const script = document .createElement ('script' )
script.src = `https://www.googletagmanager.com/gtag/js?id=${GA_MEASUREMENT_ID} `
script.async = true
document .head .appendChild (script)
window .dataLayer = window .dataLayer || []
window .gtag = function gtag ( ) {
window .dataLayer .push (arguments )
}
window .gtag ('js' , new Date ())
window .gtag ('config' , GA_MEASUREMENT_ID )
}
( ) {
(!browser)
. ( , , { : url })
}
<!-- src/routes/+layout.svelte -->
<script lang="ts">
import { onMount } from 'svelte'
import { page } from '$app/stores'
import { initGA, trackPageview } from '$lib/analytics'
onMount(() => {
initGA()
})
$: if ($page.url.pathname) {
trackPageview($page.url.pathname)
}
</script>
<slot />
Plain HTML
Add to <head>:
<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' );
</script >
Step 4: Environment Variables
Create or update .env / .env.local:
NEXT_PUBLIC_GA_MEASUREMENT_ID=G-XXXXXXXXXX
VITE_GA_MEASUREMENT_ID=G-XXXXXXXXXX
NUXT_PUBLIC_GA_MEASUREMENT_ID=G-XXXXXXXXXX
Add to .env.example if it exists (without the actual ID):
NEXT_PUBLIC_GA_MEASUREMENT_ID=G-XXXXXXXXXX
IMPORTANT : Add .env.local to .gitignore if not already present.
Step 5: Event Tracking Helpers (if --events flag)
Create a comprehensive events utility:
type GTagEvent = {
action : string
category ?: string
label ?: string
value ?: number
[key : string ]: unknown
}
export const trackEvent = ({ action, category, label, value, ...rest }: GTagEvent ) => {
if (typeof window === 'undefined' || !window .gtag ) return
window .gtag ('event' , action, {
event_category : category,
event_label : label,
value,
...rest,
})
}
export const trackClick = (elementName : string , location ?: string ) => {
trackEvent ({
action : 'click' ,
category : 'engagement' ,
label : elementName,
click_location : location,
})
}
= ( ) => {
({
: ,
: ,
: percentage,
})
}
= ( ) => {
({
: ,
method,
})
}
= ( ) => {
({
: ,
method,
})
}
= ( ) => {
({
: ,
: params. ,
: params. ,
: params. ,
: params. ,
})
}
= ( ) => {
({
: ,
: searchTerm,
})
}
= ( ) => {
({
: ,
method,
: contentType,
: itemId,
})
}
= ( ) => {
({
: ,
: formName,
})
}
= ( ) => {
({
: ,
: formName,
success,
})
}
= ( ) => {
({
: ,
: errorMessage,
: ,
: errorLocation,
})
}
= ( ) => {
{
({
: eventName,
...params,
})
}
}
Step 6: Cookie Consent Integration (if --consent flag)
Create a consent-aware wrapper:
type ConsentState = 'granted' | 'denied'
interface ConsentConfig {
analytics_storage : ConsentState
ad_storage : ConsentState
ad_user_data : ConsentState
ad_personalization : ConsentState
}
const CONSENT_COOKIE = 'analytics_consent'
export const initWithConsent = (measurementId : string ) => {
if (typeof window === 'undefined' ) return
window .gtag ('consent' , 'default' , {
analytics_storage : 'denied' ,
ad_storage : 'denied' ,
ad_user_data : 'denied' ,
ad_personalization : 'denied' ,
wait_for_update : 500 ,
})
const script = document .createElement ( )
script. =
script. =
. . (script)
. = . || []
. = ( ) {
. . ( )
}
. ( , ())
. ( , measurementId)
savedConsent = ( )
(savedConsent) {
( . (savedConsent))
}
}
= ( ) => {
( === || ! . )
: = {
: consent. || ,
: consent. || ,
: consent. || ,
: consent. || ,
}
. ( , , consentState)
( , . (consentState), )
}
= ( ) => {
({
: ,
: ,
: ,
: ,
})
}
= ( ) => {
({
: ,
: ,
: ,
: ,
})
}
= ( ) => {
({
: ,
: ,
: ,
: ,
})
}
( ) {
date = ()
date. (date. () + days * * * * )
. =
}
( ): | {
match = . . ( ( ))
match ? match[ ] :
}
Step 7: Debug Mode (if --debug flag)
Add debug configuration:
if (process.env .NODE_ENV === 'development' ) {
window .gtag ('config' , 'G-XXXXXXXXXX' , {
debug_mode : true ,
})
}
Also recommend installing the Google Analytics Debugger Chrome extension.
Step 8: TypeScript Declarations
Create types/gtag.d.ts if using TypeScript:
declare global {
interface Window {
gtag : Gtag .Gtag
dataLayer : object []
}
}
declare namespace Gtag {
interface Gtag {
(command : 'config' , targetId : string , config ?: ConfigParams ): void
(command : 'set' , targetId : string , config : ConfigParams ): void
(command : 'set' , config : ConfigParams ): void
(command : 'js' , date : Date ): void
(command : 'event' , eventName : string , eventParams ?: EventParams ): void
(command : 'consent' , consentArg : 'default' | 'update' , consentParams : ConsentParams ): void
(... : []):
}
{
?:
?:
?:
?:
?:
[ : ]:
}
{
?:
?:
?:
[ : ]:
}
{
?: |
?: |
?: |
?: |
?:
}
}
{}
Step 9: Verification Checklist
After implementation, verify:
Measurement ID is correct format (G-XXXXXXXXXX)
Script loads in production (check Network tab)
Real-time reports show activity in GA4 dashboard
Page views are tracked on navigation
No console errors related to gtag
Environment variables are not committed to git
TypeScript has no type errors (if applicable)
Step 10: Summary Output
After completing setup, provide the user with:
Files created/modified (list them)
Environment variables needed (with example values)
Next steps :
Add the Measurement ID to environment variables
Deploy and verify in GA4 Real-time reports
Set up conversions in GA4 dashboard
Consider adding custom events for key user actions
Common Issues & Solutions
"gtag is not defined"
Script hasn't loaded yet; ensure async loading is handled
No data in GA4
Check if ad blockers are preventing tracking
Verify Measurement ID is correct
Check browser console for errors
Double page views
SPA router sending duplicate events; implement deduplication
GDPR Compliance
Always implement consent mode for EU users
Use the --consent flag to add consent management