Next.js中间件重定向逻辑异常排查求助
Next.js 中间件重定向失效问题排查与修复
问题描述
开发Next.js应用时,自定义middleware处理认证与访问控制,预期逻辑:
- 用户携带有效token访问公共路由(/login、/)时重定向至dashboard
- 无token访问私有路由时重定向至login页面
- 额外校验用户权限,若访问无权限的路由则重定向至dashboard
目前日志显示已触发重定向逻辑,但重定向未生效,请求仍按原流程执行,导致页面渲染错误或未授权访问风险。
相关代码片段
import { NextResponse } from "next/server"; import type { NextRequest } from "next/server"; import { sidebarItems } from "./presentation/components/sidebars/sidebar/sidebar-items"; import { IResponseDto } from "./domain/dto"; import { IAccessByToken } from "./infrastructure/redux/apis/users"; export async function middleware(request: NextRequest) { const path = request.nextUrl.pathname; const isPublicPath = path === "/login" || path === "/"; const token = request.cookies.get("token")?.value || ""; if (isPublicPath && token) { return NextResponse.redirect(new URL("/dashboard", request.nextUrl)); } if (!isPublicPath && !token) { return NextResponse.redirect(new URL("/login", request.nextUrl)); } const data: IResponseDto<IAccessByToken> = await ( await fetch(`${process.env.NEXT_PUBLIC_BASE_URL}~users/auth/access?token=${token}`, { headers: { "Content-Type": "application/json", Authorization: `Bearer ${token}`, }, }) ).json(); const itemSidebar = sidebarItems .filter((item) => { if (item.role) { return item.role.some((role) => data?.data.accessKeys?.items.includes(role)); } return true; }) .map((item) => ({ ...item, children: item.children?.filter((child) => { if (child.role) { return child.role.some((role) => data?.data.accessKeys?.items.includes(role)); } return true; }), })); itemSidebar.forEach((item) => { console.log("1"); if (item.route && !item.route.includes(path)) { console.log("2"); return NextResponse.redirect(new URL("/dashboard", request.nextUrl)); } console.log("3"); item.children?.forEach((child) => { console.log("4"); if (child.route && !child.route.includes(path)) { console.log("5"); return NextResponse.redirect(new URL("/dashboard", request.nextUrl)); } console.log("6"); }); }); } export const config = { matcher: [ "/", "/dashboard", "/add-purchase", "/brands", "/add-product", "/product-list", "/edit-product/:path*", "/purchase-list/:path*", "/add-leave", "/leave-list", "/my-leaves", "/public/:path*", "/login", ], };
问题核心原因
- forEach循环中return无效:
forEach的回调函数里return NextResponse.redirect()只会终止当前回调,不会中断整个middleware函数的执行,也不会把redirect响应返回给Next.js,导致middleware最终无响应返回,请求继续原流程。 - 权限校验逻辑偏差:原代码遍历所有有权限的sidebar项,只要某个项的路由不包含当前path就重定向,逻辑不合理——正确逻辑应为当前path不在任何有权限的路由列表中时,才触发重定向。
修复方案
步骤1:替换forEach为for...of循环,确保return能中断middleware
将权限校验部分的forEach改为for...of,这样在循环内return就能直接返回redirect响应,终止middleware执行。
步骤2:修正权限校验逻辑
先收集所有用户有权限访问的路由,再检查当前path是否在该列表中,不在则重定向。
修复后的middleware代码:
import { NextResponse } from "next/server"; import type { NextRequest } from "next/server"; import { sidebarItems } from "./presentation/components/sidebars/sidebar/sidebar-items"; import { IResponseDto } from "./domain/dto"; import { IAccessByToken } from "./infrastructure/redux/apis/users"; export async function middleware(request: NextRequest) { const path = request.nextUrl.pathname; const isPublicPath = path === "/login" || path === "/"; const token = request.cookies.get("token")?.value || ""; // 公共路由+有token → 跳转到dashboard if (isPublicPath && token) { return NextResponse.redirect(new URL("/dashboard", request.nextUrl)); } // 私有路由+无token → 跳转到login if (!isPublicPath && !token) { return NextResponse.redirect(new URL("/login", request.nextUrl)); } // 获取用户权限信息,添加异常捕获 let data: IResponseDto<IAccessByToken> | null = null; try { data = await ( await fetch(`${process.env.NEXT_PUBLIC_BASE_URL}~users/auth/access?token=${token}`, { headers: { "Content-Type": "application/json", Authorization: `Bearer ${token}`, }, }) ).json(); } catch (err) { // 接口请求失败,直接跳转到login return NextResponse.redirect(new URL("/login", request.nextUrl)); } // 过滤出用户有权限的sidebar项 const accessibleSidebarItems = sidebarItems .filter((item) => { if (item.role && data?.data.accessKeys?.items) { return item.role.some((role) => data.data.accessKeys.items.includes(role)); } return true; }) .map((item) => ({ ...item, children: item.children?.filter((child) => { if (child.role && data?.data.accessKeys?.items) { return child.role.some((role) => data.data.accessKeys.items.includes(role)); } return true; }), })); // 收集所有用户有权限访问的路由 const accessibleRoutes: string[] = []; for (const item of accessibleSidebarItems) { if (item.route) accessibleRoutes.push(item.route); if (item.children) { for (const child of item.children) { if (child.route) accessibleRoutes.push(child.route); } } } // 检查当前path是否在有权限的路由中(支持动态路由匹配) const hasAccess = accessibleRoutes.some(route => { // 转换动态路由为正则表达式,适配/:path*和/:param格式 const regexPattern = route .replace(/:path\*/g, ".*") .replace(/:([^/]+)/g, "[^/]+"); return new RegExp(`^${regexPattern}$`).test(path); }); // 无权限则重定向到dashboard if (!hasAccess) { return NextResponse.redirect(new URL("/dashboard", request.nextUrl)); } // 所有校验通过,继续原请求 return NextResponse.next(); } export const config = { matcher: [ // 简化匹配规则,排除静态资源和api路由 "/((?!api|_next/static|_next/image|favicon.ico).*)", ], };
额外优化建议
- 缓存权限校验结果:频繁调用权限接口会影响性能,可以结合Cookie缓存用户权限信息,设置合理的过期时间,避免重复请求。
- 完善错误处理:添加fetch请求的异常捕获,避免接口报错导致middleware崩溃。
- matcher配置简化:使用通配符规则覆盖所有页面路由,排除静态资源和API路由,减少手动枚举路由的维护成本。
内容的提问来源于stack exchange,提问作者david retrrieve
相关产品推荐
相关产品推荐

