02. 中间件与路由守卫 (Middleware)
Next.js 的中间件 (middleware.ts) 运行在页面渲染 之前。它允许你修改请求 (Request) 和响应 (Response)。
它是实现 路由守卫 (Route Guard)、国际化重定向、A/B 测试 的最佳位置。
1. 基础用法
在项目根目录(如果你用了 src,则在 src 下)创建 middleware.ts。
注意:middleware.ts 只能放在根目录,不能放在 app 文件夹里!
// src/middleware.ts
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
export function middleware(request: NextRequest) {
// 简单的日志记录
console.log('Request path:', request.nextUrl.pathname)
// 必须返回 NextResponse.next() 让请求继续,否则请求会挂起
return NextResponse.next()
}
// ✨ 关键配置:匹配器
// 只有匹配这些路径的请求才会触发中间件
export const config = {
matcher: [
// 匹配 /dashboard 开头的所有路径
'/dashboard/:path*',
// 匹配 /profile 开头的所有路径
'/profile/:path*',
],
}2. 实战:登录拦截 (路由守卫)
这是中间件最常见的用途:如果用户没登录,访问 /dashboard 时强制跳回 /login。
// src/middleware.ts
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
export function middleware(request: NextRequest) {
const path = request.nextUrl.pathname
// 1. 获取 Token (假设存放在 Cookie 中)
const token = request.cookies.get('token')?.value
// 2. 定义受保护的路由
const isProtectedRoute = path.startsWith('/dashboard') || path.startsWith('/profile')
// 3. 定义公开路由 (如登录页、注册页)
const isPublicRoute = path === '/login' || path === '/register'
// 4. 逻辑判断
// 情况 A: 没登录,且试图访问受保护路由 -> 踢回登录页
if (isProtectedRoute && !token) {
return NextResponse.redirect(new URL('/login', request.url))
}
// 情况 B: 已登录,且试图访问登录页 -> 踢回 Dashboard
if (isPublicRoute && token) {
return NextResponse.redirect(new URL('/dashboard', request.url))
}
return NextResponse.next()
}
// 排除静态资源 (图片、JS、CSS) 和 API 路由
export const config = {
matcher: ['/((?!api|_next/static|_next/image|favicon.ico).*)'],
}3. 高级:修改 Headers
你可以在中间件里给请求头添加信息,这样后续的 Server Component 就能读到了。
export function middleware(request: NextRequest) {
// 比如:识别用户所在的地理位置 (Vercel 会自动注入 x-vercel-ip-country)
const country = request.geo?.country || 'US'
// 克隆 Headers 并添加新字段
const requestHeaders = new Headers(request.headers)
requestHeaders.set('x-user-country', country)
requestHeaders.set('x-url', request.url) // 把当前 URL 传给组件方便读取
return NextResponse.next({
request: {
headers: requestHeaders,
},
})
}在 Server Component 中读取:
// app/page.tsx
import { headers } from 'next/headers'
export default function Page() {
const headersList = headers()
const country = headersList.get('x-user-country')
return <div>Hello user from {country}</div>
}4. ⚠️ 重要限制
Next.js Middleware 运行在 Edge Runtime (边缘运行时) 中,而不是标准的 Node.js 环境。
这意味着:
- 不能 使用 Node.js 原生模块(如
fs,path,crypto)。 - 不能 直接连接数据库(如 Prisma 默认不支持 Edge,除非用 Data Proxy)。
- 不能 执行耗时的操作(通常限制执行时间在 30ms - 1.5s 内)。
如果需要在中间件里查数据库校验 Token,建议调用外部独立的 HTTP API,或者使用支持 Edge 的数据库驱动 (如 Drizzle + Neon)。