访问Next.js上的window.google时发生类型错误



我正试图在我的Next应用程序中加入谷歌登录功能。以下是我的做法。

_document.js

import React from 'react';
import Document, {Html, Head, Main, NextScript } from 'next/document';
export default class MyDocument extends Document{
render(){
return(
<Html lang="en">
<Head>
<meta name="theme-color" />
{/* This should add `google` to `window` */}
<script type="application/javascript" src="https://accounts.google.com/gsi/client" async />
</Head>
<body>
<Main />
<NextScript />
</body>
</Html>
);
}
}

然后在pages/login.js

import { React, useEffect, ... } from 'react'
export default function LoginPage (props) {
// When page is rendered, render the 'Sign-in with Google' button
useEffect(() => {
window.google.accounts.id.initialize({
client_id: process.env.NEXT_PUBLIC_GOOGLE_CLIENT_ID,
callback: res => { console.log(res) }
})
window.google.accounts.id.renderButton(
document.getElementById('googleSignIn'),
{ theme: 'filled_blue', size: 'large', text: 'continue_with' }
)
}, [])
return (<>
{/* Provide an element for the button to render into */}
<div id="googleSignIn" />
</>)
}

但这引发了一个错误:

login.js:48 Uncaught TypeError: Cannot read properties of undefined (reading 'accounts')

换句话说,window.google没有定义。

这个怎么了?

您可以从脚本中删除async属性,以确保它尽早同步加载。

<script type="application/javascript" src="https://accounts.google.com/gsi/client" />

或者,您也可以将next/script组件与beforeInteractive策略一起使用,以实现类似的行为。

import Script from 'next/script'
<Script type="application/javascript" src="https://accounts.google.com/gsi/client" strategy="beforeInteractive" />

NextJS总是在服务器上预呈现页面,在这种情况下window不可用。您可以随时使用next/router库。等待页面加载到客户端

import { React, useEffect, ... } from 'react'
import { useRouter } from 'next/router'
export default function LoginPage (props) {
// When page is rendered, render the 'Sign-in with Google' button
const router=useRouter() //create router state
useEffect(() => {
if(window){ //check window if exist on each effect execution
window.google.accounts.id.initialize({
client_id: process.env.NEXT_PUBLIC_GOOGLE_CLIENT_ID,
callback: res => { console.log(res) }
})
window.google.accounts.id.renderButton(
document.getElementById('googleSignIn'),
{ theme: 'filled_blue', size: 'large', text: 'continue_with' }
)
}
}, [router]) // to run again when client router loads
return (<>
{/* Provide an element for the button to render into */}
<div id="googleSignIn" />
</>)
}

我在谷歌按钮所在的同一个文件中添加了<script>标记。我尝试将脚本移到其他地方,但没有成功。

<Fragment>
<Head>
<script src="https://accounts.google.com/gsi/client" async defer/>
</Head>
<div id="googleButton"/>
</Fragment>

此外,我还向useEffect钩子添加了回调函数window.onGoogleLibraryLoad。我不知道是否所有浏览器都支持它,但它对我来说是在chrome上工作的。这是文档,下面是用window.onGoogleLibraryLoad加载谷歌按钮逻辑的useEffect代码。

useEffect(() => {
window.onGoogleLibraryLoad = () => {
google.accounts.id.initialize({
client_id: process.env.NEXT_PUBLIC_GOOGLE_CLIENT_ID,
callback: handleCredentialResponse
});
google.accounts.id.renderButton(
document.getElementById("googleButton"),
{
theme: "filled_blue",
size: "large",
width: "304",
shape: "pill"
}  // customization attributes
);
}
});

从技术上讲,您可以使用window.google,但仅引用google对我有效。我注意到您也在使用asyncdefer作为<script>标签。如果我删除了async,那么在运行next build && next export时就会遇到问题。如果我保留/删除defer,我没有注意到任何差异。

在代码中的这一行之前"window.google.accounts.id.initialize({quot;尝试使用/全局谷歌/,然后尝试运行它。

在此处输入图像描述

相关内容

最新更新