如何在Next.js 13中持续渲染Google Identity Services按钮?
Next.js 13 App Router中Google登录按钮渲染问题
我用Next.js 13的App Router构建应用,通过Google提供的HTML+脚本模板添加了「使用Google登录」功能。一开始把按钮直接放在Auth表单里,后来将登录、注册拆成不同页面,共享AuthForm组件并通过页面传参切换状态,但切换页面(重定向后重新渲染)时Google按钮消失了——页面上有渲染按钮的div和脚本,但div是空的,脚本没重新执行。
我判断原因是Google按钮不是Next/React组件,而是靠异步脚本填充div;页面重渲染时,Next/React认为div已经存在,不会重新触发脚本执行。
后来我把脚本放到account/layout.tsx里,把按钮拆成独立组件和子组件分开渲染,表单切换的问题解决了,但用户从登录页导航到首页再返回时,按钮还是没法渲染。
当前相关代码如下:
account/layout.tsx
import Script from 'next/script'; import GoogleButton from '@/app/_components/GoogleButton'; export default function AccountLayout( { children, }: { children: React.ReactNode } ) { return ( <section> <Script src="https://accounts.google.com/gsi/client" async></Script> {children} <GoogleButton /> </section> ) }
account/page.tsx
import SignIn from "./signin/page"; export default function Account() { return ( <div> <SignIn /> </div> ) }
account/signin/page.tsx
import AuthForm from '@/app/_components/AuthForm'; export default function SignIn() { return ( <> <AuthForm /> </> ) }
_components/GoogleButton.tsx
import Container from "@mui/material/Container" export default function GoogleButton() { return ( <> <Container id="google-signin-container" sx={{ border: "1px solid darkgrey", borderTop: "none", paddingTop: "10px", paddingBottom: "40px", width: "80%", display: "flex", flexDirection: "column", justifySelf: "center", alignItems: "center" }}> <div id="g_id_onload" data-client_id={`${process.env.GOOGLE_CLIENT_ID}`} data-context="signin" data-ux_mode="popup" data-login_uri="http://localhost:3000/" data-nonce="" data-itp_support="true"> </div> <div className="g_id_signin" data-type="standard" data-shape="rectangular" data-theme="outline" data-text="signin_with" data-size="large" data-logo_alignment="left"> </div> </Container> </> ) }
解决方案
通过以下方法解决了问题,还避免了直接访问window对象上的google属性:
核心思路是利用Next.js的useEffect钩子,在组件挂载或路由变化时,手动调用Google身份验证脚本提供的渲染方法,确保按钮每次都能正确初始化:
- 调整
GoogleButton组件,添加useEffect监听组件挂载和路由变化:
import Container from "@mui/material/Container"; import { useEffect } from "react"; import { usePathname } from "next/navigation"; export default function GoogleButton() { const pathname = usePathname(); useEffect(() => { // 检查Google脚本是否已加载完成 const initializeGoogleButton = () => { if (typeof window !== "undefined" && window.google?.accounts?.id) { // 先销毁已存在的实例,避免重复渲染 window.google.accounts.id.cancel(); // 重新渲染Google登录按钮 window.google.accounts.id.renderButton( document.querySelector(".g_id_signin")!, { type: "standard", shape: "rectangular", theme: "outline", text: "signin_with", size: "large", logo_alignment: "left" } ); // 同时初始化配置 window.google.accounts.id.initialize({ client_id: `${process.env.GOOGLE_CLIENT_ID}`, context: "signin", ux_mode: "popup", login_uri: "http://localhost:3000/", itp_support: true }); } }; // 如果脚本已加载,直接初始化;否则监听加载事件 if (window.google?.accounts?.id) { initializeGoogleButton(); } else { const script = document.querySelector('script[src="https://accounts.google.com/gsi/client"]'); if (script) { script.addEventListener("load", initializeGoogleButton); return () => script.removeEventListener("load", initializeGoogleButton); } } }, [pathname]); // 路由变化时重新执行 return ( <> <Container id="google-signin-container" sx={{ border: "1px solid darkgrey", borderTop: "none", paddingTop: "10px", paddingBottom: "40px", width: "80%", display: "flex", flexDirection: "column", justifySelf: "center", alignItems: "center" }}> <div className="g_id_signin"></div> </Container> </> ); }
- 移除原
GoogleButton组件中的g_id_onloaddiv,因为配置已通过代码初始化,避免重复配置冲突。
这样调整后,无论是页面切换还是路由导航返回,Google登录按钮都会重新初始化渲染,解决了消失的问题。
内容的提问来源于stack exchange,提问作者hjdjoo
相关产品推荐
相关产品推荐

