使用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组件互相跳转。
原因分析
- Next.js的
Link组件采用客户端导航,不会刷新整个页面,因此Script组件只会在首次加载时执行一次,后续导航不会重新加载脚本 - Google GSI脚本加载后,全局
window.google对象会一直存在,但切换页面后,登录按钮的初始化逻辑没有重新触发(因为scriptLoaded状态未变化,useEffect依赖未更新) - 旧的按钮实例未被清理,导致新页面无法渲染新的按钮
解决方案
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
相关产品推荐
相关产品推荐

