自定义登录页如何获取NextAuth Google登录数据写入Sanity数据库
问题根因
你在自定义登录页拿不到session,核心是逻辑放的位置和执行时机完全不对:
- 自定义
/auth/signin页面只有用户未登录、被拦截需要登录的时候才会展示,这时候用户还没走Google授权流程,本地根本没有生成有效登录态,useSession()返回undefined是正常表现 - 你把查用户、写Sanity的逻辑塞在登录页的
useEffect里,依赖数组是空的,意味着这段代码只会在页面刚打开、用户还没点登录按钮的时候跑一次,就算后面用户登录成功session更新了,逻辑也不会再重新执行 - 代码没做空值兜底,session不存在的时候直接读
session.user.email会直接抛JS错误,代码根本跑不到后续逻辑
推荐解决方案(服务端回调处理,最稳定)
不要在前端登录页处理用户入库逻辑,直接把这段逻辑移到NextAuth的服务端回调里执行。用户授权成功后服务端会自动触发这段逻辑,不会受前端页面跳转、状态同步的影响,也不会出现漏写、重复写的问题,连你之前写的/api/createUser接口都可以省掉,还不用额外做接口鉴权,避免接口被恶意调用写脏数据。
修改后的[...nextauth].js代码如下:
import NextAuth from 'next-auth' import GoogleProvider from 'next-auth/providers/google' import { sanityClient } from '../../sanity' // 引入你的Sanity服务端客户端 export default NextAuth({ providers: [ GoogleProvider({ clientId: "xxxxxx", clientSecret: "xxxxx", }), ], secret: "something", pages: { signIn: '/auth/signin', }, callbacks: { // signIn回调在用户成功通过授权、会话正式创建前触发,专门适合做新用户校验入库 async signIn({ user, profile }) { // 先查询Sanity中当前邮箱的用户是否已存在 const existingUser = await sanityClient.fetch( `*[_type == "user" && email == $email][0]`, { email: user.email } ) // 不存在就写入新用户数据 if (!existingUser) { await sanityClient.create({ _type: 'user', name: user.name, email: user.email, image: user.image, uid: profile.sub, // Google返回的用户唯一标识 username: user.name.split(' ').join('').toLocaleLowerCase() }) } return true // 返回true才会继续完成后续登录流程 }, async session({ session, token }) { session.user.username = session.user.name .split(' ') .join('') .toLocaleLowerCase() session.user.uid = token.sub return session }, }, })
改完之后用户点击Google登录、完成授权的过程中,服务端会自动完成用户存在性校验和入库,等跳回首页的时候数据已经写好了,所有业务组件直接用useSession()拿用户数据就行,不需要额外处理。
备选方案(前端页面处理,不推荐)
如果因为特殊原因必须在前端处理,绝对不要在登录页写这段逻辑,要把逻辑放到你登录成功后跳转的目标页面(也就是你signIntoProvider里设置的callbackUrl: "/"对应的首页),同时要监听session的变化,等session加载完成再执行逻辑。
示例代码(放在首页组件中):
import { useSession } from 'next-auth/react' import { useEffect } from 'react' export default function Home() { const { data: session, status } = useSession() const createUserIfNotExist = async (userData) => { await fetch("/api/createUser", { method: "POST", body: JSON.stringify(userData), }) } useEffect(() => { // 等session加载完成、确认用户已登录再执行逻辑 if (status === 'authenticated' && session?.user) { const checkAndCreateUser = async () => { // 调用接口查询当前用户是否已在Sanity中存在 const res = await fetch(`/api/checkUser?email=${session.user.email}`) const userExists = await res.json() if (!userExists) { createUserIfNotExist(session.user) } } checkAndCreateUser() } }, [status, session]) // 把session和加载状态加入依赖,状态变化时自动重新执行 return <div>首页业务内容</div> }
这种方案稳定性很差,如果用户登录后没等跳转到首页就关掉页面,入库逻辑就不会执行,非必要不选。
内容的提问来源于stack exchange,提问作者Sai Krishnadas
相关产品推荐
相关产品推荐

