如何在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
相关产品推荐
相关产品推荐

