名称: add-analytics
描述: 为任意项目添加 Google Analytics 4 跟踪。自动检测框架、添加跟踪代码、设置事件并配置隐私设置。
argument-hint: "
本指南将帮助您为项目正确配置 Google Analytics 4 (GA4)。
从 $ARGUMENTS 中解析以下参数:
- Measurement ID:格式为 G-XXXXXXXXXX(必需,如未提供则询问)
- --events:包含自定义事件跟踪辅助工具
- --consent:包含 Cookie 同意集成
- --debug:启用开发调试模式
扫描项目以确定框架/配置:
优先级检测顺序:
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 中包含 "react-scripts" → Create React App
11. 仅有 index.html → 纯 HTML
12. _app.tsx/jsx → Next.js(检查 App Router:app/ 目录)
同时检查:
- TypeScript 使用情况(tsconfig.json)
- 现有分析工具(搜索 gtag、GA、analytics)
- 包管理器(pnpm-lock.yaml、yarn.lock、package-lock.json)
Measurement ID 必须:
- 以 G- 开头(GA4 格式)
- 后跟恰好 10 个字母数字字符
- 示例:G-ABC1234567
如果用户提供 UA- 格式的 ID,请告知:
“您提供的是 Universal Analytics ID (UA-)。GA4 使用以 'G-' 开头的 Measurement ID。
Universal Analytics 已于 2024 年 7 月停止服务。您需要在 analytics.google.com 上创建一个 GA4 属性。”
创建 app/layout.tsx 修改或创建 components/GoogleAnalytics.tsx:
// 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>
</>
)
}
添加到根布局:
// app/layout.tsx
import { GoogleAnalytics } from '@/components/GoogleAnalytics'
// 在 <body> 或 <html> 内添加:
<GoogleAnalytics measurementId="G-XXXXXXXXXX" />
修改 pages/_app.tsx:
// 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} />
</>
)
}
创建 src/lib/analytics.ts:
// 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.push(arguments)
}
window.gtag('js', new Date())
window.gtag('config', GA_MEASUREMENT_ID)
}
export const pageview = (url: string) => {
window.gtag('config', GA_MEASUREMENT_ID, {
page_path: url,
})
}
export const event = (action: string, params?: Record<string, unknown>) => {
window.gtag('event', action, params)
}
在 src/main.tsx 中初始化:
import { initGA } from './lib/analytics'
// 在渲染前初始化
if (import.meta.env.PROD) {
initGA()
}
创建 src/plugins/analytics.ts:
// 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 }) {
// 加载 gtag 脚本
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)
// 跟踪路由变化
router.afterEach((to) => {
window.gtag('config', GA_MEASUREMENT_ID, {
page_path: to.fullPath,
})
})
// 提供全局方法
app.config.globalProperties.$gtag = window.gtag
}
}
创建 plugins/analytics.client.ts:
// plugins/analytics.client.ts
export default defineNuxtPlugin(() => {
const config = useRuntimeConfig()
const measurementId = config.public.gaMeasurementId
if (!measurementId) return
// 加载 gtag
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,
})
})
})
添加到 nuxt.config.ts:
export default defineNuxtConfig({
runtimeConfig: {
public: {
gaMeasurementId: process.env.NUXT_PUBLIC_GA_MEASUREMENT_ID,
},
},
})
创建 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>
添加到布局:
---
import Analytics from '../components/Analytics.astro'
---
<html>
<head>
<Analytics measurementId="G-XXXXXXXXXX" />
</head>
</html>
创建 src/lib/analytics.ts 和 src/routes/+layout.svelte:
// src/lib/analytics.ts
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)
}
export function trackPageview(url: string) {
if (!browser) return
window.gtag('config', GA_MEASUREMENT_ID, { page_path: 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 />
添加到 <head>:
<!-- Google Analytics -->
<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>
创建或更新 .env / .env.local:
# Next.js 使用
NEXT_PUBLIC_GA_MEASUREMENT_ID=G-XXXXXXXXXX
# Vite (React/Vue/Svelte) 使用
VITE_GA_MEASUREMENT_ID=G-XXXXXXXXXX
# Nuxt 使用
NUXT_PUBLIC_GA_MEASUREMENT_ID=G-XXXXXXXXXX
如果存在 .env.example,请添加(不包含实际 ID):
# Google Analytics 4 Measurement ID
NEXT_PUBLIC_GA_MEASUREMENT_ID=G-XXXXXXXXXX
重要:如果尚未包含,请将 .env.local 添加到 .gitignore。
创建全面的事件工具集:
// lib/analytics-events.ts
/**
* GA4 事件跟踪工具
*
* 推荐事件:https://support.google.com/analytics/answer/9267735
*/
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,
})
}
export const trackScroll = (percentage: number) => {
trackEvent({
action: 'scroll',
category: 'engagement',
value: percentage,
})
}
// 转化事件
export const trackSignUp = (method: string) => {
trackEvent({
action: 'sign_up',
method,
})
}
export const trackLogin = (method: string) => {
trackEvent({
action: 'login',
method,
})
}
export const trackPurchase = (params: {
transactionId: string
value: number
currency: string
items?: Array<{
itemId: string
itemName: string
price: number
quantity: number
}>
}) => {
trackEvent({
action: 'purchase',
transaction_id: params.transactionId,
value: params.value,
currency: params.currency,
items: params.items,
})
}
// 内容事件
export const trackSearch = (searchTerm: string) => {
trackEvent({
action: 'search',
search_term: searchTerm,
})
}
export const trackShare = (method: string, contentType: string, itemId: string) => {
trackEvent({
action: 'share',
method,
content_type: contentType,
item_id: itemId,
})
}
// 表单事件
export const trackFormStart = (formName: string) => {
trackEvent({
action: 'form_start',
form_name: formName,
})
}
export const trackFormSubmit = (formName: string, success: boolean) => {
trackEvent({
action: 'form_submit',
form_name: formName,
success,
})
}
// 错误跟踪
export const trackError = (errorMessage: string, errorLocation?: string) => {
trackEvent({
action: 'exception',
description: errorMessage,
fatal: false,
error_location: errorLocation,
})
}
// 自定义事件构建器,提供灵活性
export const createCustomEvent = (eventName: string) => {
return (params?: Record<string, unknown>) => {
trackEvent({
action: eventName,
...params,
})
}
}
创建支持同意的包装器:
```typescript
// lib/analytics-consent.ts
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, // 等待同意横幅
})
// 加载 gtag
const script = document.createElement('script')
script.src = https://www.googletagmanager.com/gtag/js?id=${measurementId}
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', measurementId)
// 检查现有同意状态
const savedConsent = getCookie(CONSENT_COOKIE)
if (savedConsent) {
updateConsent(JSON.parse(savedConsent))
}
}
// 用户做出选择时更新同意状态
export const updateConsent = (consent: Partial
if (typeof window === 'undefined' || !window.gtag) return
const consentState: ConsentConfig = {
analytics_storage: consent.analytics_storage || 'denied',
ad_storage: consent.ad_storage || 'denied',
ad_user_data: consent.ad_user_data || 'denied',
ad_personalization: consent.ad_personalization || 'denied',
}
window.gtag('consent', 'update', consentState)
// 保存到 Cookie
setCookie(CONSENT_COOKIE, JSON.stringify(consentState), 365)
}
// 便捷函数
export const acceptAll = () => {
updateConsent({
analytics_storage: 'granted',
ad_storage: 'granted',
ad_user_data: 'granted',
ad_personalization: 'granted',
})
}
export const acceptAnalyticsOnly = () => {
updateConsent({
analytics_storage: 'granted',
ad_storage: 'denied',
ad_user_data: 'denied',
ad_personalization: 'denied',
})
}
export const denyAll = () => {
updateConsent({
analytics_st