Next.js自定义document.js按Cookie状态条件加载Google Analytics
Next.js 实现GA跟踪脚本条件按需注入的方案
之前实现踩坑的核心原因
- 自定义
_document.js的运行机制存在天然限制:它仅在服务端渲染页面外壳阶段执行,静态导出(SSG)构建阶段跑getInitialProps时,根本没有真实HTTP请求对象,所以看到req/res为undefined是必然结果;而且_document不会响应客户端路由跳转的状态变化,本身就不适合做请求级别的Cookie判断和脚本注入逻辑。 - 硬编码在
_documentHead里的GA脚本:不管用户最终是否同意跟踪,页面首次加载时就会被浏览器下载,既不符合合规要求,也完全做不到“拒绝就不加载”的条件判断。 - 关于“读不到浏览器侧Cookie”的误区:首次SSR请求时,浏览器存储的Cookie是会通过HTTP请求头传递到服务端的,只是
_document的上下文拿不到完整请求对象,才会出现读不到的问题,换到正确的读取位置就能解决。
分步实现方案
1. 移除原有硬编码的GA脚本
把之前写在_document.js <Head>标签里的所有GA相关script标签全部删除,避免初始加载就加载跟踪脚本。
2. 选择正确的Cookie读取位置
根据项目使用的路由模式,把跟踪Cookie的读取逻辑从_document挪到能拿到完整请求上下文的位置:
- Pages Router 模式:将Cookie解析逻辑写在
_app.js的getInitialProps方法中,这个方法的上下文能拿到完整的req对象(SSR、SSG页面首次加载时都能正常获取请求头里的Cookie)。拿到Cookie后判断:如果跟踪类Cookie值为false,就给_app传递一个isTrackingAllowed = false的props;如果值为true,则标记为允许跟踪。 - App Router(Next.js 13+)模式:直接在根布局
layout.js的Server Component中,通过next/headers导出的cookies()方法读取跟踪Cookie的取值,判断是否允许跟踪即可,完全不需要改动_document。
3. 按跟踪授权状态动态注入GA脚本
根据拿到的授权状态,分两种场景处理注入逻辑:
- 首次加载已授权的场景
当读取到Cookie标记为isTrackingAllowed = true时,通过Next.js内置的Script组件加载GA脚本,注意将脚本加载策略设为afterInteractive,避免阻塞页面渲染,示例代码:import Script from 'next/script' const GA_ID = '你的GA站点ID' // 仅在用户已授权时渲染Script组件 {isTrackingAllowed && ( <> <Script src={`https://www.googletagmanager.com/gtag/js?id=${GA_ID}`} strategy="afterInteractive" /> <Script id="gtag-init" strategy="afterInteractive"> {` window.dataLayer = window.dataLayer || []; function gtag(){dataLayer.push(arguments);} gtag('js', new Date()); gtag('config', '${GA_ID}'); `} </Script> </> )} - 用户未选择/拒绝跟踪的场景
这种场景下初始状态完全不加载任何GA脚本。等用户在Cookie选择横幅上点击“接受跟踪”按钮时,在浏览器端动态完成脚本加载逻辑:- 将用户的选择(值为
true)写入对应跟踪Cookie,保证后续访问时服务端能识别授权状态,直接注入GA脚本。 - 动态创建script标签加载GA资源,完成gtag初始化。
示例回调代码:
如果用户点击“拒绝跟踪”,就写入const handleAcceptTracking = () => { // 写入用户授权的Cookie,有效期1年,路径为全站 document.cookie = "tracking_consent=true; path=/; max-age=31536000; SameSite=Lax" // 避免重复加载 if (window.gtag) return // 动态创建GA脚本标签 const gaScript = document.createElement('script') gaScript.src = `https://www.googletagmanager.com/gtag/js?id=${GA_ID}` gaScript.async = true document.body.appendChild(gaScript) // 初始化gtag逻辑 window.dataLayer = window.dataLayer || [] function gtag(){window.dataLayer.push(arguments)} gtag('js', new Date()) gtag('config', GA_ID) }tracking_consent=false的Cookie,后续所有页面跳转、服务端渲染流程都会读取到这个标记,永远不触发GA脚本加载。 - 将用户的选择(值为
常见踩坑提醒
- 不要将GA脚本的加载策略设为
beforeInteractive,这类策略会在页面hydrate之前就强制加载,会打破“拒绝跟踪就不加载”的逻辑。 - 客户端路由跳转时,记得加判断如果
window.gtag已经存在,就不要重复注入脚本,避免重复统计。 - 不需要为了读Cookie强行在
_document的getInitialProps里做逻辑,官方本身就不推荐在_document中处理请求级别的业务逻辑,换位置就能解决之前req为undefined的问题。
内容的提问来源于stack exchange,提问作者Re-Angelo
相关产品推荐
相关产品推荐

