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

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",
  ],
};

问题核心原因

  1. forEach循环中return无效:forEach的回调函数里return NextResponse.redirect()只会终止当前回调,不会中断整个middleware函数的执行,也不会把redirect响应返回给Next.js,导致middleware最终无响应返回,请求继续原流程。
  2. 权限校验逻辑偏差:原代码遍历所有有权限的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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.14 06:05:34