3.1 中间件 Middleware


3.1 中间件(Middleware)

本节摘要:中间件是"请求到达页面/接口之前"的拦截层——重定向、鉴权、地域路由、请求改写都能在这里做。本节讲清中间件的执行时机、middleware.ts 的写法、matcher 配置,以及"中间件 vs 服务器组件鉴权"的分工。

阅读收获

阅读完本节,你应当能够:

  1. 解释中间件的执行时机与位置
  2. 用 middleware.ts 做重定向与请求改写
  3. 用 matcher 精确控制拦截范围
  4. 在中间件里读取 Cookie 做鉴权跳转
  5. 理解中间件与服务器组件鉴权的分工

问题与直觉:为什么需要"请求前的拦截层"

有些逻辑不该等页面渲染才发现:未登录访问 /dashboard 应该直接跳转登录页;访问 /old 应该301 到新地址;来自海外的请求应该走国际版。这些"请求级"决策,在页面组件里做就太晚了、太分散了。

直觉类比:中间件是"门卫"——访客(请求)进大楼(应用)前,门卫先检查:有没有预约(登录态)、走哪个门(重定向)、去哪里(路由)。检查通过才放行到具体楼层(页面/接口)

💡 关键直觉:中间件运行在"边缘"(Edge Runtime)——在 CDN 边缘节点执行,比服务器更靠近用户,响应极快。但它运行环境受限(Node.js API 有限),所以只适合"轻量决策"(读 Cookie、重定向),不适合"重量级逻辑"(查数据库)。

核心原理:middleware.ts

2.1 第一个中间件

// middleware.ts(项目根目录) import { NextResponse } from "next/server"; import type { NextRequest } from "next/server"; export function middleware(request: NextRequest) { // 读取 Cookie const token = request.cookies.get("token"); if (!token && request.nextUrl.pathname.startsWith("/dashboard")) { // 未登录访问受保护页面 → 跳转登录 return NextResponse.redirect(new URL("/login", request.url)); } return NextResponse.next(); // 放行 }

执行时机:请求到达 → 中间件(可重定向/改写)→ 页面/接口。

2.2 matcher:控制拦截范围

export const config = { matcher: [ "/dashboard/:path*", // /dashboard 下所有路径 "/admin/:path*", "/api/:path*", // 也拦截 API 路由 ], };

不配 matcher 会拦截所有请求(含静态资源),性能差且易出问题。始终用 matcher 精确限定范围

2.3 中间件能做的事

操作 示例
重定向 未登录 → /login
请求改写 rewrite 到不同页面(A/B 测试)
读取/设置 Cookie 读取登录态、设置地域标记
请求头注入 添加自定义头传给页面
地域路由 按 geo 信息路由到国际版
响应头设置 安全头(CSP 等)
// 地域路由示例 export function middleware(request: NextRequest) { const country = request.geo?.country ?? "US"; const url = request.nextUrl; if (country === "CN" && !url.pathname.startsWith("/cn")) { url.pathname = `/cn${url.pathname}`; return NextResponse.rewrite(url); } return NextResponse.next(); }

工程实践要点:鉴权分工

3.1 中间件 vs 服务器组件鉴权

维度 中间件 服务器组件
执行位置 边缘(Edge) 服务器
能力 轻量(Cookie/重定向) 完整(查库/校验)
适用 粗粒度拦截(跳转) 细粒度权限(角色/数据级)
响应方式 重定向/改写 渲染或抛错

最佳实践

  1. 中间件做"粗粒度":未登录跳转、地域路由、重定向——减少无效请求到服务器;
  2. 服务器组件/Server Action 做"细粒度":真正的权限校验、数据级授权;
  3. 两层都做:中间件拦"大门",组件校验"每个房间"——纵深防御。

3.2 避免的坑

  • 中间件里查数据库:Edge Runtime 受限,且慢;数据校验放服务器组件;
  • matcher 漏配:全部请求被拦或该拦的没拦;
  • 重定向死循环:登录页本身也被 matcher 拦截 → 加排除;
  • 依赖 Node.js API:Edge 环境没有 fs/process。

常见误区与排查

误区 现象 正解
中间件不生效 请求直达页面 检查文件位置(根目录)与 matcher
登录页重定向循环 死循环 matcher 排除 /login
中间件用 Node API 报错 只用 Edge 支持的 API
静态资源被拦 图片/JS 加载失败 matcher 限定动态路径
想在中间件查库 慢/报错 移到服务器组件

动手演练:登录保护 + 维护模式

// middleware.ts import { NextResponse } from "next/server"; import type { NextRequest } from "next/server"; export function middleware(request: NextRequest) { const token = request.cookies.get("token")?.value; // 1. 维护模式开关(环境变量控制) if (process.env.MAINTENANCE_MODE === "on") { const url = request.nextUrl.clone(); url.pathname = "/maintenance"; return NextResponse.rewrite(url); } // 2. 登录保护:受保护路径未登录 → 跳登录 if (!token && request.nextUrl.pathname.startsWith("/dashboard")) { const login = new URL("/login", request.url); login.searchParams.set("next", request.nextUrl.pathname); return NextResponse.redirect(login); } return NextResponse.next(); } export const config = { matcher: ["/dashboard/:path*", "/settings/:path*"], };
// app/maintenance/page.tsx export default function Maintenance() { return ( <div className="flex h-screen items-center justify-center"> <h1>系统维护中,请稍后再来</h1> </div> ); }

中间件一肩挑两件事:维护模式(改写入口)与登录保护(重定向)——请求级决策集中在入口层,页面组件专注业务

本章回顾

  • 中间件:请求到达前的拦截层,运行在边缘,响应快。
  • 能力:重定向、改写、Cookie 读取、请求头、地域路由。
  • matcher:精确控制拦截范围,必须配置。
  • 限制:Edge Runtime,不能用 Node API、不查数据库。
  • 分工:中间件粗粒度拦截,服务器组件细粒度校验。
  • 纵深防御:大门(中间件)+ 每个房间(组件/Server Action)。

深入理解:中间件的执行链与场景

中间件执行位置

中间件在边缘(CDN 节点)执行,比服务器更靠近用户,但只能用轻量 API。

中间件执行链示意

中间件执行链示意

典型场景清单

场景 中间件做法
未登录拦截 读 Cookie → 无 token 重定向登录页
维护模式 读环境变量 → rewrite 到维护页
地域路由 读 geo → rewrite 到语言版
A/B 测试 读 Cookie/随机 → rewrite 到变体页
安全头 在响应上加 CSP 等头

最佳实践:中间件只做"入口决策",不放业务逻辑;matcher 始终精确配置,避免拦截静态资源。

常见问题速答

问:中间件和服务器组件的鉴权有什么区别?
中间件在边缘执行(快、轻),适合"未登录跳转"这类粗粒度判断;服务器组件在服务器执行(可查库),适合角色、数据级权限。推荐两层都做:中间件拦大门,组件查细节。

问:中间件能访问数据库吗?
不建议。中间件运行在 Edge Runtime,数据库客户端(Prisma)在 Node 环境更合适,且中间件里查库会拖慢每个请求。数据校验放服务器组件。

问:matcher 怎么写才能不拦静态资源?
只匹配动态路径:matcher: ["/dashboard/:path*", "/api/:path*"]。不写 matcher 会匹配所有请求(含静态资源),性能差。

问:中间件里设置响应头怎么做?
const res = NextResponse.next(); res.headers.set("X-Header", "value"); return res;——放行前设置头。

问:中间件能读环境变量吗?
能,但注意 NEXT_PUBLIC_ 与普通变量的区别:普通环境变量在 Edge 环境同样可用(边缘函数配置里注入)。


作者与出处
原作者: 灏天文库
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U