You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

自定义登录页如何获取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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.28 09:09:57