Next.js(App Router)中next-auth登录后如何实现无会话取消的条件重定向?
解决Next.js App Router中NextAuth Google登录后的自定义重定向问题
核心思路
通过Session回调注入用户状态 + 中间件拦截处理重定向的组合方式,既保留原有的中间件保护逻辑,又能根据用户首次访问状态动态跳转目标页面,无需修改默认登录页的触发逻辑。
具体实现步骤
1. 配置NextAuth的Session与SignIn回调
在你的auth.ts(或lib/auth.ts)中,扩展Session回调注入用户首次访问状态,同时在SignIn回调中完成用户初始化创建:
import NextAuth from "next-auth"; import GoogleProvider from "next-auth/providers/google"; import { PrismaClient } from "@prisma/client"; // 替换为你的数据库客户端 const prisma = new PrismaClient(); export const authOptions = { providers: [ GoogleProvider({ clientId: process.env.GOOGLE_CLIENT_ID!, clientSecret: process.env.GOOGLE_CLIENT_SECRET!, }), ], callbacks: { // 登录时初始化用户(若不存在则创建) async signIn({ user }) { const existingUser = await prisma.user.findUnique({ where: { id: user.id }, }); if (!existingUser) { await prisma.user.create({ data: { id: user.id, email: user.email!, name: user.name!, isFirstVisit: true, // 标记为首次访问 }, }); } return true; // 允许登录流程继续 }, // 向Session注入用户首次访问状态 async session({ session, user }) { const dbUser = await prisma.user.findUnique({ where: { id: user.id }, }); if (dbUser) { session.user.isFirstVisit = dbUser.isFirstVisit; } return session; }, }, }; export default NextAuth(authOptions);
2. 编写中间件处理重定向逻辑
在middleware.ts中,拦截已登录用户的请求,根据Session中的isFirstVisit状态决定跳转目标:
import { NextResponse } from "next/server"; import type { NextRequest } from "next/server"; import { getServerSession } from "next-auth/next"; import { authOptions } from "@/lib/auth"; import { PrismaClient } from "@prisma/client"; const prisma = new PrismaClient(); export async function middleware(request: NextRequest) { const session = await getServerSession(authOptions); const path = request.nextUrl.pathname; // 跳过首次访问页面和API路由的重定向检查 if (path.startsWith("/first-visit") || path.startsWith("/api")) { return NextResponse.next(); } // 若用户已登录且标记为首次访问,重定向到首次访问页面 if (session && session.user.isFirstVisit) { // 提前将用户状态改为非首次(避免重复跳转) await prisma.user.update({ where: { id: session.user.id }, data: { isFirstVisit: false }, }); return NextResponse.redirect(new URL("/first-visit", request.url)); } // 原有保护路由逻辑:未登录用户访问受保护页面时跳转到登录页 const protectedPaths = ["/dashboard", "/profile"]; if (protectedPaths.some((p) => path.startsWith(p)) && !session) { const callbackUrl = encodeURIComponent(request.nextUrl.href); return NextResponse.redirect( new URL(`/api/auth/signin?callbackUrl=${callbackUrl}`, request.url) ); } return NextResponse.next(); } // 指定中间件生效的路径 export const config = { matcher: ["/((?!_next/static|_next/image|favicon.ico).*)"], };
3. 实现首次访问页面
在app/first-visit/page.tsx中创建首次访问引导页,完成后可跳转至首页或原目标页面:
import { redirect } from "next/navigation"; import { getServerSession } from "next-auth/next"; import { authOptions } from "@/lib/auth"; export default async function FirstVisitPage() { const session = await getServerSession(authOptions); if (!session) redirect("/api/auth/signin"); return ( <div className="min-h-screen flex flex-col items-center justify-center p-4"> <h1 className="text-3xl font-bold mb-6">欢迎首次使用!</h1> <p className="text-lg mb-8">请完成以下引导设置...</p> {/* 这里添加引导内容,比如表单、教程等 */} <form action="/api/complete-first-visit" method="post"> <button type="submit" className="px-6 py-3 bg-blue-600 text-white rounded-lg hover:bg-blue-700" > 完成引导,进入应用 </button> </form> </div> ); }
4. 完成引导后的状态更新(可选)
若需要用户手动触发状态更新,创建app/api/complete-first-visit/route.ts:
import { redirect } from "next/navigation"; import { getServerSession } from "next-auth/next"; import { authOptions } from "@/lib/auth"; import { PrismaClient } from "@prisma/client"; const prisma = new PrismaClient(); export async function POST(request: Request) { const session = await getServerSession(authOptions); if (!session) redirect("/api/auth/signin"); // 标记用户已完成首次访问 await prisma.user.update({ where: { id: session.user.id }, data: { isFirstVisit: false }, }); // 跳转至首页或原callbackUrl(可从Session或查询参数获取) redirect("/"); }
流程说明
- 用户访问受保护路由 → 中间件拦截,跳转至NextAuth默认登录页并携带原
callbackUrl。 - 用户通过Google登录 → SignIn回调创建用户(若不存在)并标记为首次访问。
- 登录成功后,NextAuth跳转至原
callbackUrl→ 中间件检查Session中的isFirstVisit状态,重定向至首次访问页面。 - 用户完成引导后,状态更新为非首次访问,后续登录直接跳转至目标页面。
内容的提问来源于stack exchange,提问作者Eden Berdugo
相关产品推荐
相关产品推荐

