当初第一次听说 Next.js Middleware,我心想:“挺酷,但我真的需要吗?”
结论:需要。只要你做 鉴权、日志、A/B 测试、重定向 里的任意一件,它就像 被低估的超能力。
下面是我“开窍”的瞬间、现在的用法,以及它为什么能把页面逻辑从泥沼里救出来。
什么时候我真的懂了它?
多角色应用:管理员 vs 普通用户。每次用户访问受限页,我都要在页面/布局里塞:
结果:臃肿、重复、到处是 if。换上 middleware 后:一个文件、一个层,入口统一、页面干净。
Middleware 到底干啥?
在请求完成前、在边缘(Edge)就运行你的代码——在 React 渲染之前先把路清掉。
它放在项目根目录 /middleware.ts(或 .js),像轻量 serverless。
// middleware.ts
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
export function middleware(request: NextRequest) {
const token = request.cookies.get('token')
if (!token) {
return NextResponse.redirect(new URL('/login', request.url))
}
return NextResponse.next()
}
// ✅ 在同文件里选择性匹配
export const config = {
matcher: ['/dashboard/:path*', '/settings/:path*'], // 只对这些路由生效
}
小更正:路由匹配 matcher 应该导出在 middleware.ts 中的 config,而不是 next.config.js。
这段小小的中间件就能 保护指定路由,不用改任意一个页面文件。
我真正在用的场景
| 用途 |
带来的好处 |
| 🔐 鉴权重定向 |
未登录直送 /login,已登录拦截访问 /login |
| 🧪 A/B 测试 |
基于 Header/Cookie 切不同布局或实验 |
| 🌍 地域/语言路由 |
按地区/语言跳转到对应站点或路径 |
| 📈 轻量埋点 |
记录路径、来源、Session 等入口信息 |
| 🚧 维护模式 |
时间窗内屏蔽部分路由或返回占位页 |
| 🔁 Rewrites |
用 NextResponse.rewrite 映射到内部页面或 Edge API |
路由匹配像忍者一样精准
把匹配写进 middleware.ts 的 config.matcher,只对需要的页面生效,避免对静态资源、公开页加负担。
export const config = {
matcher: [
'/((?!_next/static|_next/image|favicon.ico|public|api/public).*)',
],
}
三步上手(今天就能用)
- 创建
middleware.ts
- 写一个简单鉴权:检查 Cookie/Session,未登录重定向
- 加上
config.matcher:只匹配需要保护/改写的路由
- 打开浏览器,看页面逻辑 瞬间变干净 🎉
进阶小技巧
- 读取请求信息:
request.nextUrl、request.headers、request.cookies
- 改写而非跳转:
NextResponse.rewrite(new URL('/internal', request.url)) 无刷新换内容
- 追加 Header/Cookie:
response.headers.set(...)、response.cookies.set(...)
- A/B 实验粘性:首次分流后 写入 Cookie,后续请求保持一致
- 边缘运行注意:Edge Runtime 没有 Node 内置模块,尽量 少做重 CPU 计算,<10ms 收工
什么时候用 Middleware,什么时候别用?
- 该用:入口控制(鉴权/重定向/国际化/实验)、轻量埋点、低延迟改写
- 别用:需要访问数据库做复杂业务;这类逻辑放 Route Handler / Server Actions / API 更合适
为什么它像“超能力”
- 页面更干净:把“进门检查”从组件里搬走
- 更快的跳转:渲染前 就决定去向
- 入口集中化:所有入口规则 一处维护
- 不依赖自建后端:没有自定义服务器也能搞定一堆事
一个实用范例(A/B + 登录保护)
// middleware.ts
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
export function middleware(req: NextRequest) {
const { nextUrl } = req
const res = NextResponse.next()
const token = req.cookies.get('token')?.value
const isProtected = nextUrl.pathname.startsWith('/app')
// 1) 登录保护
if (isProtected && !token) {
return NextResponse.redirect(new URL('/login', req.url))
}
// 2) A/B 实验:缺 cookie 则随机,写回 cookie 保持粘性
let bucket = req.cookies.get('ab-home')?.value
if (!bucket) {
bucket = Math.random() < 0.5 ? 'A' : 'B'
res.cookies.set('ab-home', bucket, { path: '/', httpOnly: false })
}
// 3) 根据实验分桶改写到不同页面
if (nextUrl.pathname === '/' && bucket === 'B') {
return NextResponse.rewrite(new URL('/home-b', req.url))
}
return res
}
export const config = {
matcher: ['/', '/app/:path*'],
}
把“进门要做的事” 收口到 Middleware,页面和布局变得 专注又轻盈。一个文件,守住所有入口——这就是 Next.js Middleware 的爽点。
如果你也在做类似的 Next.js 项目,欢迎来云栈社区一起交流。