NextJS App Router中NextAuth验证后Navbar状态不同步问题求助
背景
基于NextJS App Router开发,使用NextAuth + Resend实现注册流程,采用NextAuth官方Resend Provider。注册流程:
- 通过表单获取用户邮箱
- 提交后调用Server Action触发NextAuth的
signIn() - 发送自定义6位OTP验证邮件,同时NextAuth Prisma Adapter自动在数据库创建
verificationToken - 用户点击邮件按钮跳转到验证码输入页
- 提交验证码后Server Action验证通过,重定向到onboarding页面
问题现象
完成第5步重定向到onboarding页面后:
- Cookie已生成session token,根
layout.tsx中await auth()能获取到session对象 - Navbar组件中
console.log("Session in Navbar: ", session)有输出,但内部isLoggedIn状态未设为true - 服务器端日志显示
isLoggedIn=true,客户端日志显示isLoggedIn=false - 手动刷新onboarding页面后,Navbar状态恢复正常
相关代码
根layout.tsx
import "./globals.css"; import { auth } from "../../auth"; import { cn } from "@/lib/utils"; import { Poppins } from "next/font/google"; import { Navbar } from "@/components/Navbar"; import { SessionProvider } from "next-auth/react"; import { Toaster } from "@/components/ui/toaster"; const fontHeading = Poppins({ subsets: ["latin"], display: "swap", variable: "--font-heading", weight: ["100", "200", "300", "400", "500", "600", "700", "800", "900"], }); const fontBody = Poppins({ subsets: ["latin"], display: "swap", variable: "--font-body", weight: ["100", "200", "300", "400", "500", "600", "700", "800", "900"], }); export default async function RootLayout({ children, }: Readonly<{ children: React.ReactNode; }>) { const session = await auth(); console.log("Session in Layout: ", session); return ( <html lang="en"> <body className={cn( "antialiased", fontHeading.variable, fontBody.variable )} > <SessionProvider session={session}> <Navbar /> <section className="px-[5%]">{children}</section> </SessionProvider> <Toaster /> </body> </html> ); }
Navbar组件
"use client"; import Link from "next/link"; import { Sheet, SheetTrigger, SheetContent } from "@/components/ui/sheet"; import { NavLink, Button } from "@/components/ui/button"; import Logo from "./Logo"; import { Menu } from "lucide-react"; import { Separator } from "./ui/separator"; import { useEffect, useState } from "react"; import { signOut, useSession } from "next-auth/react"; export function Navbar() { const { data: session, status, update } = useSession(); const [isSheetOpen, setSheetOpen] = useState(false); const [isLoggedIn, setIsLoggedIn] = useState(false); const handleCloseSheet = () => { setSheetOpen(false); }; useEffect(() => { if (status === "authenticated") { setIsLoggedIn(true); update(); } else { setIsLoggedIn(false); } }, [status, session]); console.log("Session in Navbar: ", session); console.log("Current time is: ", new Date().toLocaleTimeString()); return ( // 基于isLoggedIn值修改Navbar UI的逻辑 ) }
已尝试方案
- 在Navbar中使用
useRouter()刷新页面 - 使用
window.location.reload()模拟手动刷新 - 在Navbar中用
useEffect监听状态
解决方案
1. 移除冗余状态,直接从useSession状态推导登录状态
额外维护的isLoggedInstate与useSession的status/session存在同步延迟,直接基于useSession的状态判断即可:
修改Navbar组件:
"use client"; // 保留其他import import { signOut, useSession } from "next-auth/react"; export function Navbar() { const { data: session, status } = useSession(); // 直接从status推导登录状态,无需额外state const isLoggedIn = status === "authenticated"; const [isSheetOpen, setSheetOpen] = useState(false); const handleCloseSheet = () => { setSheetOpen(false); }; console.log("Session in Navbar: ", session); console.log("Current time is: ", new Date().toLocaleTimeString()); return ( // 直接使用isLoggedIn变量渲染UI ) }
2. 在onboarding页面强制更新session
重定向到onboarding后,客户端的useSession可能还持有旧状态,在onboarding页面(客户端组件)触发session更新:
"use client"; import { useSession } from "next-auth/react"; import { useEffect } from "react"; export default function Onboarding() { const { update } = useSession(); useEffect(() => { // 页面加载时强制拉取最新session update(); }, [update]); return <div>Onboarding Content</div>; }
3. 确保Server Action中认证逻辑正确
验证OTP的Server Action中,需确保signIn调用正确完成用户认证,再执行重定向:
async function verifyOTP(formData: FormData) { const email = formData.get('email') as string; const otp = formData.get('otp') as string; // 自定义OTP验证逻辑 const verificationToken = await prisma.verificationToken.findFirst({ where: { email, token: otp }, }); if (!verificationToken || verificationToken.expires < new Date()) { throw new Error("Invalid or expired OTP"); } // 完成认证,redirect: false避免自动跳转 await signIn("resend", { email, redirect: false }); // 删除已使用的验证token await prisma.verificationToken.delete({ where: { id: verificationToken.id } }); // 重定向到onboarding redirect("/onboarding"); }
原因分析
服务器端渲染Root Layout时,已经拿到了最新的session并传递给SessionProvider,但客户端的useSession初始值是SSR时的快照。注册完成重定向后,客户端没有自动触发session刷新,导致status仍为unauthenticated,直到手动刷新或主动调用update()。
内容的提问来源于stack exchange,提问作者Jean Svy
相关产品推荐
相关产品推荐

