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

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("/");
}

流程说明

  1. 用户访问受保护路由 → 中间件拦截,跳转至NextAuth默认登录页并携带原callbackUrl。
  2. 用户通过Google登录 → SignIn回调创建用户(若不存在)并标记为首次访问。
  3. 登录成功后,NextAuth跳转至原callbackUrl → 中间件检查Session中的isFirstVisit状态,重定向至首次访问页面。
  4. 用户完成引导后,状态更新为非首次访问,后续登录直接跳转至目标页面。

内容的提问来源于stack exchange,提问作者Eden Berdugo

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.02 11:44:56