I'm trying to incorporate the Google Sign In feature in my Next app. Here's how I've been doing it.
In _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>
);
}
}
And then in 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" />
</>)
}
But this throws an error:
login.js:48 Uncaught TypeError: Cannot read properties of undefined (reading 'accounts')
In other words, window.google is not defined.
What's wrong with this?
NextJS always prerender pages on server, in this case window is unavailable. You can always use next/router library. to wait until page loads on client
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" />
</>)
}