我正试图在我的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
对我有效。我注意到您也在使用async
和defer
作为<script>
标签。如果我删除了async,那么在运行next build && next export
时就会遇到问题。如果我保留/删除defer
,我没有注意到任何差异。
在代码中的这一行之前"window.google.accounts.id.initialize({quot;尝试使用/全局谷歌/,然后尝试运行它。
在此处输入图像描述