Next Auth useSession钩子首次渲染无法获取会话问题求助
NextAuth v4 + Next.js 13 useSession首次渲染为null的问题分析与解决
问题场景
使用Next.js 13和NextAuth v4搭建网站时,调用useSession钩子获取会话,页面/组件首次渲染时session初始值为null,导致依赖会话的业务逻辑无法正常执行。
示例问题代码(QuestionPage)
'use client' import { useEffect, useState } from 'react' import { Question } from '@prisma/client' import { useSession } from 'next-auth/react' function QuestionPage() { const { data: session } = useSession() // 首次渲染时为null const [questions, setQuestions] = useState<Question[]>([]) const [questionStatus, setQuestionStatus] = useState(''); useEffect(() => { console.log('Session: ', session) if (!session) return; setQuestionStatus('Loading questions...'); fetch('/api/questions') .then((res) => res.json()) .then((data) => { setQuestions(data.questions) setQuestionStatus(''); }) }, []) // 依赖为空数组,仅执行一次 return ( <div> <p>{questionStatus}</p> {questions && questions.map(question => ( <p key={question.id}>{question.title}</p> ))} </div> ) }
配套的Provider与Layout配置
ProvidersWrapper组件
'use client' import QuestionContextWrapper from '@/context/QuestionContext' import { SessionProvider } from 'next-auth/react' export default function ProvidersWrapper({ children }: { children: React.ReactNode }) { return ( <SessionProvider> <QuestionContextWrapper> {children} </QuestionContextWrapper> </SessionProvider> ) }
RootLayout组件
import './globals.css' import Header from '@/components/Header' import ProvidersWrapper from './ProvidersWrapper' import Footer from '@/components/Footer' export const metadata = { title: 'My app', description: 'My description', } export default function RootLayout({ children, }: { children: React.ReactNode }) { return ( <html lang="en"> <body> <ProvidersWrapper> <Header /> {children} <Footer /> </ProvidersWrapper> </body> </html> ) }
问题原因
- NextAuth会话初始化的异步性:
SessionProvider在客户端初始化时,需要向服务端请求会话信息,这个过程是异步的。组件首次渲染时会话还未完成解析,useSession返回的data自然为null。 - useEffect依赖缺失:原代码中
useEffect的依赖数组为空,只会在组件挂载时执行一次。此时session还处于未加载状态,直接跳过了请求逻辑,后续session加载完成后也不会重新触发请求。
解决方案
方案1:给useEffect添加session依赖
修改useEffect的依赖数组,加入session,这样每当session状态更新时,都会重新执行业务逻辑:
'use client' import { useEffect, useState } from 'react' import { Question } from '@prisma/client' import { useSession } from 'next-auth/react' function QuestionPage() { const { data: session } = useSession() const [questions, setQuestions] = useState<Question[]>([]) const [questionStatus, setQuestionStatus] = useState(''); useEffect(() => { if (!session) { setQuestionStatus('请先登录'); return; } setQuestionStatus('Loading questions...'); fetch('/api/questions') .then((res) => res.json()) .then((data) => { setQuestions(data.questions) setQuestionStatus(''); }) .catch(() => { setQuestionStatus('加载失败'); }) }, [session]) // 添加session为依赖 return ( <div> <p>{questionStatus}</p> {questions.map(question => ( <p key={question.id}>{question.title}</p> ))} </div> ) }
方案2:利用useSession的status状态判断加载状态
useSession还会返回status字段,值为loading/authenticated/unauthenticated,可以根据状态直接处理UI逻辑:
'use client' import { useEffect, useState } from 'react' import { Question } from '@prisma/client' import { useSession } from 'next-auth/react' function QuestionPage() { const { data: session, status } = useSession() const [questions, setQuestions] = useState<Question[]>([]) const [questionStatus, setQuestionStatus] = useState(''); useEffect(() => { if (status !== 'authenticated') return; setQuestionStatus('Loading questions...'); fetch('/api/questions') .then((res) => res.json()) .then((data) => { setQuestions(data.questions) setQuestionStatus(''); }) }, [status, session]) // 依赖status和session // 根据状态显示对应UI if (status === 'loading') return <p>加载中...</p>; if (status === 'unauthenticated') return <p>请登录后查看</p>; return ( <div> <p>{questionStatus}</p> {questions.map(question => ( <p key={question.id}>{question.title}</p> ))} </div> ) }
方案3:服务端预传初始会话(可选)
如果使用Next.js服务端组件,可以在服务端提前获取会话,传递给SessionProvider作为初始值,减少客户端等待时间:
- 修改ProvidersWrapper,接收服务端传递的session:
'use client' import QuestionContextWrapper from '@/context/QuestionContext' import { SessionProvider } from 'next-auth/react' import { Session } from 'next-auth' export default function ProvidersWrapper({ children, session, }: { children: React.ReactNode, session?: Session }) { return ( <SessionProvider session={session}> <QuestionContextWrapper> {children} </QuestionContextWrapper> </SessionProvider> ) }
- 在RootLayout(服务端组件)中获取会话并传递:
import './globals.css' import Header from '@/components/Header' import ProvidersWrapper from './ProvidersWrapper' import Footer from '@/components/Footer' import { getServerSession } from 'next-auth/next' import { authOptions } from '@/app/api/auth/[...nextauth]/route' export const metadata = { title: 'My app', description: 'My description', } export default async function RootLayout({ children, }: { children: React.ReactNode }) { const session = await getServerSession(authOptions) return ( <html lang="en"> <body> <ProvidersWrapper session={session}> <Header /> {children} <Footer /> </ProvidersWrapper> </body> </html> ) }
内容的提问来源于stack exchange,提问作者Maramal
相关产品推荐
相关产品推荐

