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

如何在Create T3 App的TRPC中间件中实现用户未登录/未完善资料的服务端重定向

在Create T3 App中处理两类重定向需求

1. 用户未登录时在TRPC中间件内实现重定向

TRPC中间件运行在服务端API上下文里,无法直接触发服务端重定向(因为API接口返回JSON格式响应,而非HTTP重定向指令),可以通过以下两种方式实现需求:

方式一:抛出错误,前端捕获后跳转

保留现有中间件的UNAUTHORIZED错误逻辑,前端调用TRPC方法时捕获错误并执行客户端跳转:

// 服务端TRPC中间件
const enforceUserIsAuthed = t.middleware(({ ctx, next }) => {
  if (!ctx.session || !ctx.session.user) {
    throw new TRPCError({ code: "UNAUTHORIZED" });
  }
  return next({
    ctx: {
      session: { ...ctx.session, user: ctx.session.user },
    },
  });
});
// 前端组件调用示例(React+Next.js)
import { useRouter } from "next/router";
import { api } from "@/utils/api";

const ProtectedPage = () => {
  const router = useRouter();
  const { error } = api.protectedData.get.useQuery();

  if (error?.data?.code === "UNAUTHORIZED") {
    router.push("/auth/login");
    return <div>正在跳转至登录页...</div>;
  }

  return <div>受保护页面内容</div>;
};

方式二:结合Next.js路由中间件提前拦截

如果希望在进入页面之前完成重定向(无需等待TRPC调用),可以在middleware.ts中提前处理认证逻辑:

// middleware.ts
import { getToken } from "next-auth/jwt";
import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";

export async function middleware(req: NextRequest) {
  const token = await getToken({ req, secret: process.env.NEXTAUTH_SECRET });
  const isProtectedRoute = req.nextUrl.pathname.startsWith("/dashboard");
  const isAuthRoute = req.nextUrl.pathname.startsWith("/auth");

  // 未登录访问受保护路由,重定向到登录页
  if (isProtectedRoute && !token) {
    return NextResponse.redirect(new URL("/auth/login", req.url));
  }

  // 已登录访问认证路由,重定向到主页
  if (isAuthRoute && token) {
    return NextResponse.redirect(new URL("/", req.url));
  }

  return NextResponse.next();
}

export const config = {
  matcher: ["/dashboard/:path*", "/auth/:path*"],
};

2. 已登录用户未完善个人资料时的服务端重定向

同样,TRPC中间件无法直接触发服务端重定向,可通过以下两种方案实现:

方案一:TRPC中间件返回状态,前端处理跳转

在中间件中检查用户资料完善情况,抛出自定义错误,前端捕获后跳转至资料完善页:

// 服务端TRPC中间件(修改后)
const enforceUserIsAuthed = t.middleware(async ({ ctx, next }) => {
  if (!ctx.session || !ctx.session.user) {
    throw new TRPCError({ code: "UNAUTHORIZED" });
  }

  // 从数据库查询用户资料,判断是否完善(示例逻辑)
  const userProfile = await prisma.userProfile.findUnique({
    where: { userId: ctx.session.user.id },
  });

  if (!userProfile || !userProfile.fullName || !userProfile.contact) {
    throw new TRPCError({
      code: "FORBIDDEN",
      message: "请完善个人资料",
    });
  }

  return next({
    ctx: {
      session: { ...ctx.session, user: ctx.session.user },
    },
  });
});
// 前端组件处理跳转
import { useRouter } from "next/router";
import { api } from "@/utils/api";

const DashboardPage = () => {
  const router = useRouter();
  const { error } = api.dashboard.getStats.useQuery();

  if (error?.data?.code === "FORBIDDEN") {
    router.push("/profile/complete");
    return <div>正在跳转至资料完善页面...</div>;
  }

  return <div>Dashboard内容</div>;
};

方案二:Next.js服务端组件/Page Router中直接处理

如果使用App Router的Server Component,可在页面组件中直接检查并触发重定向;如果是Page Router,使用getServerSideProps处理:

App Router(Server Component)示例:

// app/dashboard/page.tsx
import { getServerSession } from "next-auth/next";
import { authOptions } from "@/app/api/auth/[...nextauth]/route";
import prisma from "@/lib/prisma";
import { redirect } from "next/navigation";

export default async function DashboardPage() {
  const session = await getServerSession(authOptions);

  if (!session) {
    redirect("/auth/login");
  }

  const userProfile = await prisma.userProfile.findUnique({
    where: { userId: session.user.id },
  });

  if (!userProfile || !userProfile.fullName || !userProfile.contact) {
    redirect("/profile/complete");
  }

  return <div>Dashboard内容</div>;
}

Page Router(getServerSideProps)示例:

// pages/dashboard.tsx
import { getServerSession } from "next-auth/next";
import { authOptions } from "@/pages/api/auth/[...nextauth]";
import prisma from "@/lib/prisma";

export async function getServerSideProps(context) {
  const session = await getServerSession(context.req, context.res, authOptions);

  if (!session) {
    return {
      redirect: {
        destination: "/auth/login",
        permanent: false,
      },
    };
  }

  const userProfile = await prisma.userProfile.findUnique({
    where: { userId: session.user.id },
  });

  if (!userProfile || !userProfile.fullName || !userProfile.contact) {
    return {
      redirect: {
        destination: "/profile/complete",
        permanent: false,
      },
    };
  }

  return { props: {} };
}

export default function DashboardPage() {
  return <div>Dashboard内容</div>;
}

内容的提问来源于stack exchange,提问作者Mani Muridi

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.04 15:40:24