1. Next.js中间件基础概念与核心价值
在Next.js生态中,middleware(中间件)是一种在请求到达页面渲染前进行拦截处理的机制。它运行在Edge Runtime环境中,这意味着它比传统的Node.js服务器更接近用户,能够实现更快的响应速度。中间件最常见的应用场景包括:身份验证、路由重定向、请求头修改、A/B测试分流等。
与Express/Koa等传统Node框架的中间件不同,Next.js中间件具有以下特性:
- 边缘优先:默认部署在Vercel的边缘网络,全球分布式执行
- 零配置路由匹配:基于文件系统的路由自动生效,无需额外配置
- 现代API支持:原生支持Web API如Request/Response对象
- 类型安全:与TypeScript深度集成,提供完善的类型提示
一个典型的中间件文件位于项目根目录的middleware.ts(或.js)中,基础结构如下:
typescript复制import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
export function middleware(request: NextRequest) {
// 请求处理逻辑
if (request.nextUrl.pathname.startsWith('/admin')) {
return NextResponse.redirect(new URL('/login', request.url))
}
return NextResponse.next()
}
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 中间件核心配置与路由匹配策略
2.1 文件位置与作用范围
Next.js中间件的路由匹配遵循以下规则:
- 全局中间件:放置在项目根目录的
middleware.ts文件会作用于所有路由 - 路径限定中间件:可以通过文件位置限定作用范围,例如:
/pages/_middleware.ts→ 作用于/pages下所有路由(旧版pages目录)/app/(auth)/_middleware.ts→ 仅作用于/app/(auth)路由组
- 排除规则:使用
config.matcher排除特定路径
typescript复制export const config = {
matcher: [
'/((?!api|_next/static|favicon.ico).*)', // 排除静态资源
'/dashboard/:path*' // 仅匹配dashboard下的所有路由
]
}
2.2 条件匹配进阶技巧
实际项目中经常需要复杂匹配逻辑,以下是几种实用模式:
typescript复制// 多条件组合匹配
export const config = {
matcher: [
{
source: '/((?!api|_next).*)',
has: [
{ type: 'header', key: 'x-custom-header', value: 'special' }
]
}
]
}
// 动态参数捕获
export function middleware(req: NextRequest) {
const pathname = req.nextUrl.pathname
if (pathname.match(/^\/product\/([^\/]+)\/edit$/)) {
const productId = pathname.split('/')[2]
// 使用productId进行后续处理
}
}
3. 中间件实战应用场景
3.1 身份验证与权限控制
这是中间件最典型的应用场景。以下是实现JWT验证的完整示例:
typescript复制import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
import { verify } from 'jsonwebtoken'
const SECRET = process.env.JWT_SECRET!
export async function middleware(req: NextRequest) {
const pathname = req.nextUrl.pathname
const token = req.cookies.get('auth_token')?.value
// 公开路由白名单
const publicPaths = ['/login', '/register', '/api/public']
if (publicPaths.some(path => pathname.startsWith(path))) {
return NextResponse.next()
}
try {
// 验证JWT
await verify(token!, SECRET)
return NextResponse.next()
} catch (err) {
// 重定向到登录页并携带来源信息
const loginUrl = new URL('/login', req.url)
loginUrl.searchParams.set('from', pathname)
return NextResponse.redirect(loginUrl)
}
}
3.2 多租户与国际化处理
中间件非常适合处理基于URL或子域的多租户场景:
typescript复制export function middleware(req: NextRequest) {
const url = req.nextUrl.clone()
const hostname = req.headers.get('host')
// 子域名租户识别
if (hostname?.startsWith('tenant.')) {
const tenantId = hostname.split('.')[0]
url.pathname = `/tenants/${tenantId}${url.pathname}`
return NextResponse.rewrite(url)
}
// 国际化路由处理
const locale = req.cookies.get('NEXT_LOCALE')?.value
|| detectLocaleFromHeader(req)
|| 'en'
if (!url.pathname.startsWith(`/${locale}`)) {
url.pathname = `/${locale}${url.pathname}`
return NextResponse.redirect(url)
}
}
3.3 性能优化与安全加固
中间件可以在边缘节点实现安全防护和性能优化:
typescript复制export function middleware(req: NextRequest) {
const res = NextResponse.next()
// 安全头设置
res.headers.set('X-Frame-Options', 'DENY')
res.headers.set('X-Content-Type-Options', 'nosniff')
res.headers.set('Referrer-Policy', 'strict-origin-when-cross-origin')
// 缓存控制(针对静态资源)
if (req.nextUrl.pathname.startsWith('/_next/static')) {
res.headers.set('Cache-Control', 'public, max-age=31536000, immutable')
}
// Bot检测与限流
const isBot = req.headers.get('user-agent')?.match(/bot|crawl|spider/i)
if (isBot) {
return new Response('Bot access limited', { status: 429 })
}
return res
}
4. 高级技巧与性能优化
4.1 边缘函数与地理定位
利用Vercel边缘网络特性,可以实现基于地理位置的内容分发:
typescript复制export function middleware(req: NextRequest) {
const country = req.geo?.country || 'US'
const city = req.geo?.city || 'New York'
// 重定向到地区特定页面
if (country === 'CN' && !req.nextUrl.pathname.startsWith('/zh-CN')) {
return NextResponse.redirect(`/zh-CN${req.nextUrl.pathname}`)
}
// 注入地理头信息供页面使用
const res = NextResponse.next()
res.headers.set('X-Country', country)
res.headers.set('X-City', city)
return res
}
4.2 A/B测试与功能开关
中间件是实现无侵入式功能切换的理想场所:
typescript复制export function middleware(req: NextRequest) {
// 基于cookie或查询参数的实验分组
const variant = req.cookies.get('ab-test')
|| (Math.random() > 0.5 ? 'B' : 'A')
const res = NextResponse.next()
// 设置实验分组cookie(有效期7天)
res.cookies.set('ab-test', variant, {
maxAge: 60 * 60 * 24 * 7,
sameSite: 'lax'
})
// 重写到不同实验版本
if (variant === 'B' && req.nextUrl.pathname === '/pricing') {
return NextResponse.rewrite(new URL('/pricing-new', req.url))
}
return res
}
4.3 中间件性能监控
通过自定义header和日志实现中间件性能追踪:
typescript复制export async function middleware(req: NextRequest) {
const start = Date.now()
const res = await NextResponse.next()
const duration = Date.now() - start
// 注入执行时间头
res.headers.set('X-Middleware-Time', `${duration}ms`)
// 日志记录(实际项目应使用专业日志服务)
console.log(JSON.stringify({
path: req.nextUrl.pathname,
duration,
ua: req.headers.get('user-agent'),
ip: req.ip
}))
return res
}
5. 常见问题与调试技巧
5.1 中间件执行顺序问题
当存在多个中间件时,执行顺序遵循以下规则:
- 父目录中间件先于子目录执行
- 同一层级按字母顺序执行
- 可通过
export const config = { runtime: 'experimental-edge' }强制指定Edge Runtime
调试建议:
typescript复制// 在中间件开头添加调试日志
console.log('Middleware executing for:', req.nextUrl.pathname)
// 使用Vercel CLI本地测试
// vercel dev
// 或直接运行Next.js并开启调试
// NEXT_DEBUG=middleware npm run dev
5.2 静态资源处理陷阱
中间件默认会拦截所有请求,包括静态文件。典型解决方案:
typescript复制export const config = {
matcher: [
'/((?!_next/static|_next/image|favicon.ico|robots.txt).*)'
]
}
// 或者在中间件内部判断
export function middleware(req: NextRequest) {
if (req.nextUrl.pathname.startsWith('/_next/static')) {
return NextResponse.next()
}
// ...其他逻辑
}
5.3 生产环境调试方法
当中间件在生产环境出现问题时:
- 检查Vercel日志:
vercel logs --middleware - 添加错误边界:
typescript复制try {
// 中间件逻辑
} catch (err) {
console.error('Middleware error:', err)
return NextResponse.json(
{ error: 'Internal Server Error' },
{ status: 500 }
)
}
- 使用Sentry等错误监控工具:
typescript复制import * as Sentry from '@sentry/nextjs'
export async function middleware(req: NextRequest) {
try {
// ...
} catch (error) {
Sentry.captureException(error)
return NextResponse.error()
}
}
6. 中间件与App Router的深度集成
在Next.js 13+的App Router架构下,中间件与服务器组件、路由处理器等新特性有更多协同可能:
6.1 向页面传递数据
通过修改请求头将中间件数据传递给页面组件:
typescript复制// middleware.ts
export function middleware(req: NextRequest) {
const requestHeaders = new Headers(req.headers)
requestHeaders.set('x-user-role', 'premium')
return NextResponse.next({
request: { headers: requestHeaders }
})
}
// page.tsx
import { headers } from 'next/headers'
export default function Page() {
const userRole = headers().get('x-user-role')
// ...
}
6.2 与Server Actions配合
中间件可以预处理表单提交等Server Actions请求:
typescript复制export async function middleware(req: NextRequest) {
if (req.nextUrl.pathname === '/api/contact'
&& req.method === 'POST') {
// 验证reCAPTCHA
const formData = await req.formData()
const token = formData.get('g-recaptcha-response')
const valid = await verifyRecaptcha(token as string)
if (!valid) {
return NextResponse.json(
{ error: 'Invalid CAPTCHA' },
{ status: 403 }
)
}
}
return NextResponse.next()
}
6.3 动态路由重写技巧
结合App Router的动态路由特性实现智能路由:
typescript复制export function middleware(req: NextRequest) {
// 根据设备类型重写路由
const ua = req.headers.get('user-agent') || ''
const isMobile = /mobile|android|iphone/i.test(ua)
if (isMobile && req.nextUrl.pathname === '/') {
return NextResponse.rewrite(new URL('/mobile-home', req.url))
}
// 动态API版本控制
if (req.nextUrl.pathname.startsWith('/api/')) {
const version = req.cookies.get('api-version') || 'v1'
return NextResponse.rewrite(
new URL(`/api/${version}/${req.nextUrl.pathname.slice(5)}`, req.url)
)
}
}
