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

使用Next.js Link导航时Google GSI脚本无法加载求助

问题:Next.js Link导航时Google GSI脚本无法正常工作

在开发使用https://accounts.google.com/gsi/client API的Next.js应用时,遇到以下问题:

  • 使用Next.js的Link组件在登录页和注册页之间导航时,Google登录按钮无法正常渲染
  • 直接在浏览器输入URL刷新页面时,脚本和按钮都能正常加载

相关代码采用React Context管理Google脚本加载状态,通过Next.js Script组件加载GSI脚本,登录/注册页通过Link组件互相跳转。

原因分析

  1. Next.js的Link组件采用客户端导航,不会刷新整个页面,因此Script组件只会在首次加载时执行一次,后续导航不会重新加载脚本
  2. Google GSI脚本加载后,全局window.google对象会一直存在,但切换页面后,登录按钮的初始化逻辑没有重新触发(因为scriptLoaded状态未变化,useEffect依赖未更新)
  3. 旧的按钮实例未被清理,导致新页面无法渲染新的按钮

解决方案

1. 监听路由变化,触发按钮重新初始化

在GoogleSignInButton组件中引入Next.js路由对象,监听路由变化,确保每次页面切换时重新执行按钮初始化逻辑:

import { GoogleSignInContext } from '@/context/GoogleSignIn'
import { GoogleCredentialResponse } from '@/interfaces';
import React, { useContext, useEffect, useRef } from 'react'
import { useRouter } from 'next/router'

export interface GoogleSignInButtonProps {
  onSingIn?: (credentials: GoogleCredentialResponse) => void;
  calcWidth: number | null;
}

export const GoogleSignInButton: React.FC<GoogleSignInButtonProps> = ({
  onSingIn,
  calcWidth
}) => {

  const googleContext = useContext(GoogleSignInContext);
  const buttonRef = useRef<HTMLDivElement>(null);
  const router = useRouter();
  
  useEffect(() => {
    if(!googleContext.scriptLoaded || !buttonRef.current || !calcWidth) return;

    // 清理旧的Google按钮实例
    window.google?.accounts.id.cancel();
    buttonRef.current.innerHTML = '';

    // 重新初始化并渲染按钮
    window.google?.accounts.id.initialize({
      client_id: process.env.NEXT_PUBLIC_GOOGLE_OAUTH_CLIENTID,
      callback(credentialResponse: GoogleCredentialResponse) {
          onSingIn?.(credentialResponse);
      },
    });

    window.google?.accounts.id.renderButton(buttonRef.current, {
      type: 'standard',
      shape: 'rectangular',
      size: 'large',
      theme: 'outline',
      locale: "en_US",
      width: calcWidth.toString(),
    })

  }, [googleContext.scriptLoaded, calcWidth, router.asPath])

  if(!googleContext.scriptLoaded || !calcWidth) return <></>;

  return (
    <div ref={buttonRef} id='google-button' style={{height: 45}}></div>
  )
}

2. 优化Context的脚本加载逻辑(可选)

如果需要确保脚本在加载失败后能重新尝试加载,可在Context中添加路由监听逻辑:

import { googleGsiClient } from '@/utils';
import Script from 'next/script';
import React, { createContext, useEffect, useState } from 'react';
import { useRouter } from 'next/router';

export interface IGoogleSignInContext {
  scriptLoaded: boolean;
  scriptError?: string;
}

export const GoogleSignInContext = createContext<IGoogleSignInContext>({
  scriptLoaded: false,
});

export const GoogleSignInProvider: React.FC<{
  children: JSX.Element | JSX.Element[];
}> = ({ children }) => {
  const [state, setState] = useState<IGoogleSignInContext>({
    scriptLoaded: false,
  });
  const router = useRouter();

  const handleScriptLoaded = () => {
    console.log('GOOGLE SCRIPT LOADED')
    setState((actual) => ({
      ...actual,
      scriptLoaded: true,
    }));
  };

  const handleScriptError = () => {
    setState((actual) => ({
      ...actual,
      scriptLoaded: false,
      scriptError: 'Failed to load script',
    }));
  };

  // 路由变化时,若脚本未加载则重置状态,触发重新加载尝试
  useEffect(() => {
    if (!state.scriptLoaded) {
      setState({ scriptLoaded: false, scriptError: undefined });
    }
  }, [router.asPath, state.scriptLoaded]);

  return (
    <>
      <GoogleSignInContext.Provider value={state}>
        <Script
          src={googleGsiClient}
          onLoad={handleScriptLoaded}
          onError={handleScriptError}
          strategy="lazyOnload"
        />
        {children}
      </GoogleSignInContext.Provider>
    </>
  );
};

3. 关键注意事项

  • Google GSI脚本只需要加载一次,客户端导航时无需重复加载,仅需重新初始化按钮
  • 使用window.google?.accounts.id.cancel()清理旧的按钮实例,避免DOM冲突
  • 将router.asPath加入useEffect依赖,确保路由切换时重新执行按钮渲染逻辑

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.23 08:27:07