I'm currently building an app in Next.js 13 with the app router, and I've added a "Sign in with Google" functionality by utilizing the HTML + script template provided by Google. Originally, I placed this button directly in my Auth form.
However, I ran into trouble when changing the form on the authentication page from a "Log in" form to a "Sign up" form. Initially, I had set up the "Log In" and "Sign Up" as different pages, rendering a shared AuthForm component which would adjust itself based on state passed down from these pages. However, when the page re-rendered upon a redirect, the Google button would disappear from the page. I think it's important to note that the divs that should render the Google button are on the page, as is the script - it's just that the divs are blank and do not display the button like they should (seems like the script needs to be run).
I believe this is happening because the Google button is not a Next or React component -- rather, the div is populated by a script that is called asynchronously. Thus, when the page re-renders, I believe Next/React picks up the button divs, acknowledging them as present, and not running the script to actually display the button.
I've fixed the issue to an extent, by placing the script in "layout.tsx" before the "sign in" route and putting it in its own component, rendering it separately from the children components. This ensures that when a user is on the sign in page, clicking "sign up" changes the state of the form and renders a sign-up form without interfering with the Google script.
However, this issue persists in other ways -- if a user is on the "sign in" page, navigates to the homepage, and then returns to the "sign in" page, the Google button does not render, just as before.
Currently, the offending code looks like this:
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>
)
}
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>
</>
)
}
I'm wondering -- is there a way to tell the browser to run the script again? My gut is saying I may be able to do this by tying the render of the button to a useEffect hook. Any help would be appreciated!
EDIT: Solved with this approach, which avoids issues with trying to access the google property on the window object: https://stackoverflow.com/a/75692694/21195115