You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Next.js/React中按钮同意后添加分析脚本不生效的问题

问题解决:Next.js中点击同意后动态加载分析脚本并处理路由跳转跟踪

核心问题分析

你当前的实现存在两个关键问题:

  1. 直接通过document.head.innerHTML += document.head.innerHTML + ...操作DOM会重复整个head内容,导致DOM结构混乱,且部分浏览器不会执行这种方式插入的脚本。
  2. Next.js是单页应用,客户端路由跳转不会重新加载页面,即使脚本插入成功,分析工具的初始化逻辑仅执行一次,无法跟踪后续页面的访问行为。

优化解决方案

我们将状态管理、Next.js官方组件和路由监听结合,实现正确的脚本加载与页面跟踪。

步骤1:重构App.js,统一管理同意状态与脚本加载

import { useState, useEffect } from 'react';
import { useRouter } from 'next/router';
import Head from 'next/head';
import Script from 'next/script';
import cookie from 'js-cookie';
import CookieBanner from '../components/CookieBanner';

export default function App({ Component, pageProps }) {
  const [gdprOptIn, setGdprOptIn] = useState(null);
  const router = useRouter();

  // 从Cookie读取初始同意状态
  useEffect(() => {
    const optIn = cookie.get('gdprOptIn') === 'true';
    setGdprOptIn(optIn);
  }, []);

  // 监听路由变化,发送页面跟踪事件
  useEffect(() => {
    const handleRouteChange = (url) => {
      if (gdprOptIn && window.gtag) {
        window.gtag('config', 'G-MY G TAG HERE', {
          page_path: url,
        });
      }
      // Hotjar手动触发页面状态更新
      if (gdprOptIn && window.hj) {
        window.hj('stateChange', url);
      }
    };

    router.events.on('routeChangeComplete', handleRouteChange);
    return () => router.events.off('routeChangeComplete', handleRouteChange);
  }, [gdprOptIn, router]);

  // 处理用户同意/拒绝的回调
  const handleGDPRConsent = (optIn) => {
    cookie.set('gdprOptIn', optIn, { expires: 365 });
    setGdprOptIn(optIn);
  };

  return (
    <>
      {gdprOptIn && (
        <>
          <Head>
            {/* Hotjar初始化脚本 */}
            <script
              dangerouslySetInnerHTML={{
                __html: `
                  (function(h,o,t,j,a,r){
                    h.hj=h.hj||function(){(h.hj.q=h.hj.q||[]).push(arguments)};
                    h._hjSettings={hjid:YOUR_HOTJAR_ID,hjsv:6};
                    a=o.getElementsByTagName('head')[0];
                    r=o.createElement('script');r.async=1;
                    r.src=t+h._hjSettings.hjid+j+h._hjSettings.hjsv;
                    a.appendChild(r);
                  })(window,document,'https://static.hotjar.com/c/hotjar-','.js?sv=');
                `,
              }}
            />
            {/* Google Analytics初始化 */}
            <script
              dangerouslySetInnerHTML={{
                __html: `
                  window.dataLayer = window.dataLayer || [];
                  function gtag(){dataLayer.push(arguments);}
                  gtag('js', new Date());
                  gtag('config', 'G-MY G TAG HERE');
                `,
              }}
            />
          </Head>
          {/* Google Tag Manager脚本 */}
          <Script
            id='ga_tagmanager'
            strategy='afterInteractive'
            async
            src="https://www.googletagmanager.com/gtag/js?id=G-MY G TAG HERE"
          />
        </>
      )}

      {/* 仅当未设置同意状态或拒绝时显示横幅 */}
      {(gdprOptIn === null || !gdprOptIn) && (
        <CookieBanner onConsent={handleGDPRConsent} />
      )}

      <Component {...pageProps} />
    </>
  );
}

步骤2:简化CookieBanner组件,移除DOM操作

import Link from "next/link";
import React, { useState } from 'react';

export default function CookieBanner({ onConsent }) {
    const [showBanner, setBanner] = useState(true);

    const handleBanner = (setOptIn) => {
        setBanner(false);
        // 调用App组件的回调处理同意逻辑
        onConsent(setOptIn);
    };

    return (
        <div className={`cookie-banner ${!showBanner ? 'hide-banner' : ''}`} id="cookieBanner">
            <div className="cookie-card">
                <h1>Cookies</h1>
                <p>我们希望使用分析Cookie,以便了解您如何使用网站并进行改进。</p>
                <p>我们还使用必要Cookie来记住您是否接受了分析Cookie。<Link href={'/privacy/cookies'}>了解更多</Link></p>
                <form className="button-wrapper">
                    <button value={'accept'} type="button" className='btn btn-primary' onClick={() => handleBanner(true)}>全部接受</button>
                    <button value={'reject'} type="button" className='btn btn-primary' onClick={() => handleBanner(false)}>拒绝</button>
                </form>
            </div>
        </div>
    );
}

关键优化点说明

  1. 状态统一管理:将GDPR同意状态提升到App组件,避免在子组件中直接操作DOM,符合React的状态管理规范。
  2. 路由事件监听:通过router.events监听路由切换完成事件,手动触发分析工具的页面跟踪,解决单页应用的页面访问统计问题。
  3. 安全加载脚本:使用Next.js官方的<Script>组件和dangerouslySetInnerHTML,确保脚本正确执行,同时支持加载策略配置(如afterInteractive)。

内容的提问来源于stack exchange,提问作者jonnow

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.27 00:27:03