avatar

cooyue

博客网站 - COOYUE

  • 首页
  • 友链
主页 什么是 Next.js Middleware?
文章

什么是 Next.js Middleware?

发表于 5天前 更新于 4天前
作者 Administrator
37~47 分钟 阅读

什么是 Next.js Middleware?

Next.js Middleware(在 v16.0.0 后更名为 Proxy)是一个强大的功能,允许你在请求完成之前在服务器上运行代码。基于传入的请求,你可以通过重写、重定向、修改请求或响应头,或直接响应来修改响应。

Middleware 在路由渲染之前执行,特别适合实现自定义服务器端逻辑,如身份验证、日志记录或处理重定向。

基础配置

1. 创建 Middleware 文件

在项目根目录(或 src 目录内)创建 middleware.ts 或 middleware.js 文件,与 pages 或 app 目录同级。

// middleware.ts
import { NextResponse, NextRequest } from 'next/server'

export function middleware(request: NextRequest) {
  return NextResponse.redirect(new URL('/home', request.url))
}

export const config = {
  matcher: '/about/:path*',
}

2. 基本结构

Middleware 文件必须导出:

  • middleware 函数:处理请求的主函数
  • config 对象(可选):配置 middleware 应用的路径

Matcher 配置详解

单路径匹配

export const config = {
  matcher: '/about'
}

多路径匹配

export const config = {
  matcher: ['/about', '/contact', '/dashboard/:path*']
}

使用正则表达式

排除特定路径:

export const config = {
  matcher: [
    // 排除 API 路由、静态文件、图片优化和 .png 文件
    '/((?!api|_next/static|_next/image|.*\\\\.png$).*)',
  ],
}

高级 Matcher 配置

export const config = {
  matcher: [
    {
      source: '/api/:path*',
      locale: false,  // 忽略基于语言环境的路由
      has: [
        { type: 'header', key: 'Authorization', value: 'Bearer Token' },
        { type: 'query', key: 'userId', value: '123' },
      ],
      missing: [
        { type: 'cookie', key: 'session', value: 'active' }
      ],
    },
  ],
}

Matcher 路径模式规则

  1. 必须以 / 开头
  2. 命名参数:/about/:path 匹配 /about/a 和 /about/b
  3. 修饰符:
    • * = 零个或多个:/about/:path* 匹配 /about/a/b/c
    • ? = 零个或一个
    • + = 一个或多个
  4. 正则表达式:/about/(.*) 等同于 /about/:path*
  5. 锚定到路径开头:/about 匹配 /about 和 /about/team,但不匹配 /blog/about

常见使用场景

1. 身份验证

import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'

export function middleware(request: NextRequest) {
  const token = request.cookies.get('auth-token')
  
  if (!token) {
    return NextResponse.redirect(new URL('/login', request.url))
  }
  
  return NextResponse.next()
}

export const config = {
  matcher: ['/dashboard/:path*', '/profile/:path*']
}

2. 国际化重定向

import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'

export function middleware(request: NextRequest) {
  const country = request.geo?.country || 'US'
  
  if (country === 'CN') {
    return NextResponse.redirect(new URL('/zh-cn', request.url))
  }
  
  if (country === 'JP') {
    return NextResponse.redirect(new URL('/ja', request.url))
  }
  
  return NextResponse.next()
}

3. A/B 测试

import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'

export function middleware(request: NextRequest) {
  const bucket = request.cookies.get('bucket')
  
  if (!bucket) {
    const newBucket = Math.random() < 0.5 ? 'a' : 'b'
    const response = NextResponse.next()
    response.cookies.set('bucket', newBucket)
    return response
  }
  
  if (bucket.value === 'b') {
    return NextResponse.rewrite(new URL('/experiment-b', request.url))
  }
  
  return NextResponse.next()
}

4. API 限流

import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'

const rateLimit = new Map()

export function middleware(request: NextRequest) {
  const ip = request.ip || 'unknown'
  const now = Date.now()
  const windowMs = 60000 // 1 分钟
  const maxRequests = 10
  
  const requests = rateLimit.get(ip) || []
  const recentRequests = requests.filter((time: number) => now - time < windowMs)
  
  if (recentRequests.length >= maxRequests) {
    return new NextResponse('Too Many Requests', { status: 429 })
  }
  
  recentRequests.push(now)
  rateLimit.set(ip, recentRequests)
  
  return NextResponse.next()
}

export const config = {
  matcher: '/api/:path*'
}

5. 日志记录

import { NextResponse } from 'next/server'
import type { NextRequest, NextFetchEvent } from 'next/server'

export function middleware(request: NextRequest, event: NextFetchEvent) {
  // 使用 waitUntil 在后台记录日志
  event.waitUntil(
    fetch('https://analytics.example.com/log', {
      method: 'POST',
      body: JSON.stringify({
        pathname: request.nextUrl.pathname,
        timestamp: new Date().toISOString(),
        userAgent: request.headers.get('user-agent'),
      }),
    })
  )
  
  return NextResponse.next()
}

Cookie 操作

读取 Cookie

export function middleware(request: NextRequest) {
  // 获取单个 cookie
  const cookie = request.cookies.get('session')
  console.log(cookie) // { name: 'session', value: 'abc123', Path: '/' }
  
  // 获取所有 cookies
  const allCookies = request.cookies.getAll()
  
  // 检查 cookie 是否存在
  const hasSession = request.cookies.has('session')
  
  return NextResponse.next()
}

设置 Cookie

export function middleware(request: NextRequest) {
  const response = NextResponse.next()
  
  // 简单设置
  response.cookies.set('theme', 'dark')
  
  // 详细设置
  response.cookies.set({
    name: 'session',
    value: 'abc123',
    path: '/',
    maxAge: 3600,
    httpOnly: true,
    secure: true,
    sameSite: 'strict'
  })
  
  return response
}

删除 Cookie

export function middleware(request: NextRequest) {
  const response = NextResponse.next()
  
  // 删除单个 cookie
  response.cookies.delete('session')
  
  // 清除所有 cookies
  request.cookies.clear()
  
  return response
}

Header 操作

设置请求 Header

export function middleware(request: NextRequest) {
  // 克隆请求 headers
  const requestHeaders = new Headers(request.headers)
  requestHeaders.set('x-custom-header', 'my-value')
  requestHeaders.set('x-user-ip', request.ip || 'unknown')
  
  // 传递给下游
  return NextResponse.next({
    request: {
      headers: requestHeaders,
    },
  })
}

设置响应 Header

export function middleware(request: NextRequest) {
  const response = NextResponse.next()
  
  // 设置响应 header
  response.headers.set('x-custom-header', 'my-value')
  response.headers.set('x-response-time', Date.now().toString())
  
  return response
}

CORS 配置

import { NextRequest, NextResponse } from 'next/server'

const allowedOrigins = ['https://example.com', 'https://app.example.com']

const corsOptions = {
  'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
  'Access-Control-Allow-Headers': 'Content-Type, Authorization',
}

export function middleware(request: NextRequest) {
  const origin = request.headers.get('origin') ?? ''
  const isAllowedOrigin = allowedOrigins.includes(origin)
  
  // 处理预检请求
  if (request.method === 'OPTIONS') {
    const preflightHeaders = {
      ...(isAllowedOrigin && { 'Access-Control-Allow-Origin': origin }),
      ...corsOptions,
    }
    return NextResponse.json({}, { headers: preflightHeaders })
  }
  
  // 处理普通请求
  const response = NextResponse.next()
  
  if (isAllowedOrigin) {
    response.headers.set('Access-Control-Allow-Origin', origin)
  }
  
  Object.entries(corsOptions).forEach(([key, value]) => {
    response.headers.set(key, value)
  })
  
  return response
}

export const config = {
  matcher: '/api/:path*',
}

条件路由

基于路径的条件

export function middleware(request: NextRequest) {
  const { pathname } = request.nextUrl
  
  if (pathname.startsWith('/admin')) {
    // 管理员路由逻辑
    return checkAdminAuth(request)
  }
  
  if (pathname.startsWith('/api')) {
    // API 路由逻辑
    return handleApiRequest(request)
  }
  
  if (pathname.startsWith('/blog')) {
    // 博客路由逻辑
    return NextResponse.rewrite(new URL(\`/posts\${pathname}\`, request.url))
  }
  
  return NextResponse.next()
}

基于用户角色的路由

export function middleware(request: NextRequest) {
  const userRole = request.cookies.get('role')?.value
  const { pathname } = request.nextUrl
  
  // 管理员专属路由
  if (pathname.startsWith('/admin') && userRole !== 'admin') {
    return NextResponse.redirect(new URL('/unauthorized', request.url))
  }
  
  // VIP 专属内容
  if (pathname.startsWith('/premium') && userRole !== 'vip') {
    return NextResponse.redirect(new URL('/upgrade', request.url))
  }
  
  return NextResponse.next()
}

重写和重定向

重写(Rewrite)

重写会改变 URL 的内部处理,但浏览器地址栏不变:

export function middleware(request: NextRequest) {
  // 将 /old-blog/post-1 重写为 /blog/post-1
  if (request.nextUrl.pathname.startsWith('/old-blog')) {
    const newPath = request.nextUrl.pathname.replace('/old-blog', '/blog')
    return NextResponse.rewrite(new URL(newPath, request.url))
  }
  
  return NextResponse.next()
}

重定向(Redirect)

重定向会改变浏览器地址栏的 URL:

export function middleware(request: NextRequest) {
  // 永久重定向(301)
  if (request.nextUrl.pathname === '/old-page') {
    return NextResponse.redirect(new URL('/new-page', request.url), 301)
  }
  
  // 临时重定向(302,默认)
  if (request.nextUrl.pathname === '/temp') {
    return NextResponse.redirect(new URL('/temporary-page', request.url))
  }
  
  return NextResponse.next()
}

直接响应

从 Next.js v13.1.0 开始,可以直接从 middleware 返回响应:

export function middleware(request: NextRequest) {
  const isAuthenticated = checkAuth(request)
  
  if (!isAuthenticated) {
    return Response.json(
      { success: false, message: 'Authentication failed' },
      { status: 401 }
    )
  }
  
  return NextResponse.next()
}

export const config = {
  matcher: '/api/:path*',
}

执行顺序

Middleware 会在每个路由上调用,执行顺序如下:

  1. next.config.js 中的 headers
  2. next.config.js 中的 redirects
  3. Middleware(rewrites、redirects 等)
  4. next.config.js 中的 beforeFiles(rewrites)
  5. 文件系统路由(public/、_next/static/、pages/、app/ 等)
  6. next.config.js 中的 afterFiles(rewrites)
  7. 动态路由(/blog/[slug])
  8. next.config.js 中的 fallback(rewrites)

高级配置

跳过尾部斜杠重定向

// next.config.js
module.exports = {
  skipTrailingSlashRedirect: true,
}
// middleware.js
const legacyPrefixes = ['/docs', '/blog']

export default async function middleware(req) {
  const { pathname } = req.nextUrl
  
  if (legacyPrefixes.some((prefix) => pathname.startsWith(prefix))) {
    return NextResponse.next()
  }
  
  // 应用尾部斜杠处理
  if (!pathname.endsWith('/') && !pathname.match(/\\.[\\w]+$/)) {
    return NextResponse.redirect(
      new URL(\`\${req.nextUrl.pathname}/\`, req.nextUrl)
    )
  }
}

跳过 URL 规范化

// next.config.js
module.exports = {
  skipMiddlewareUrlNormalize: true,
}

性能优化建议

1. 精确的 Matcher

使用精确的 matcher 避免不必要的执行:

// ❌ 不好 - 会在所有路由上执行
export const config = {
  matcher: '/:path*'
}

// ✅ 好 - 只在需要的路由上执行
export const config = {
  matcher: ['/api/:path*', '/dashboard/:path*']
}

2. 早期返回

尽早返回,避免不必要的处理:

export function middleware(request: NextRequest) {
  // 快速路径 - 静态资源直接通过
  if (request.nextUrl.pathname.startsWith('/_next')) {
    return NextResponse.next()
  }
  
  // 其他逻辑...
}

3. 避免重量级操作

Middleware 应该快速执行,避免:

  • 复杂的数据库查询
  • 大量的外部 API 调用
  • 重量级的计算

使用 waitUntil 处理后台任务:

export function middleware(request: NextRequest, event: NextFetchEvent) {
  // 后台任务不会阻塞响应
  event.waitUntil(
    logAnalytics(request)
  )
  
  return NextResponse.next()
}

调试技巧

1. 添加日志

export function middleware(request: NextRequest) {
  console.log('Middleware executed for:', request.nextUrl.pathname)
  console.log('Headers:', Object.fromEntries(request.headers))
  console.log('Cookies:', request.cookies.getAll())
  
  return NextResponse.next()
}

2. 添加调试 Header

export function middleware(request: NextRequest) {
  const response = NextResponse.next()
  
  if (process.env.NODE_ENV === 'development') {
    response.headers.set('x-middleware-executed', 'true')
    response.headers.set('x-pathname', request.nextUrl.pathname)
  }
  
  return response
}

常见问题

1. Middleware 不执行

检查项:

  • 文件位置是否正确(项目根目录或 src 目录)
  • Matcher 配置是否正确
  • 是否有语法错误

2. 无限重定向

**原因:**重定向目标也匹配 matcher

解决:

export function middleware(request: NextRequest) {
  // ❌ 错误 - 会导致无限重定向
  if (!isAuthenticated(request)) {
    return NextResponse.redirect(new URL('/login', request.url))
  }
}

export const config = {
  matcher: '/:path*'  // 包括 /login
}

// ✅ 正确 - 排除登录页
export const config = {
  matcher: '/((?!login).*)'
}

3. Cookie 未设置

**原因:**忘记返回修改后的响应

解决:

// ❌ 错误
export function middleware(request: NextRequest) {
  const response = NextResponse.next()
  response.cookies.set('theme', 'dark')
  // 忘记返回 response
}

// ✅ 正确
export function middleware(request: NextRequest) {
  const response = NextResponse.next()
  response.cookies.set('theme', 'dark')
  return response  // 必须返回
}

最佳实践

  1. 使用 TypeScript:获得更好的类型安全和 IDE 支持
  2. 精确的 Matcher:只在需要的路由上执行
  3. 早期返回:尽快返回,避免不必要的处理
  4. 后台任务:使用 waitUntil 处理非关键任务
  5. 错误处理:添加 try-catch 避免 middleware 崩溃
  6. 测试:为 middleware 编写单元测试
  7. 文档:注释复杂的逻辑和业务规则

总结

Next.js Middleware 是一个强大的工具,可以在请求到达页面之前进行各种处理。通过合理配置 matcher 和使用适当的 API,你可以实现:

  • ✅ 身份验证和授权
  • ✅ 国际化和地理位置路由
  • ✅ A/B 测试和功能标志
  • ✅ API 限流和安全
  • ✅ 日志和分析
  • ✅ 重写和重定向
  • ✅ CORS 处理

记住:Middleware 应该快速执行,专注于路由和请求/响应修改,避免重量级操作。


相关资源:

  • Next.js Middleware 官方文档
  • NextRequest API
  • NextResponse API
OpenClaw
许可协议:  CC BY 4.0
分享

相关文章

3月 2, 2026

什么是 Next.js Middleware?

什么是 Next.js Middleware? Next.js Middleware(在 v16.0.0 后更名为 Proxy)是一个强大的功能,允许你在请求完成之前在服务器上运行代码。基于传入的请求,你可以通过重写、重定向、修改请求或响应头,或直接响应来修改响应。 Middleware 在路由渲染

2月 28, 2026

OpenClaw 飞书配置完全指南OpenClaw

什么是 OpenClaw 飞书集成? OpenClaw 是一个强大的 AI 助手框架,支持通过飞书机器人与用户交互。通过飞书集成,你可以: 在飞书私聊中与 AI 助手对话 在群组中 @机器人获取帮助 使用流式输出实时查看 AI 生成内容 管理文档、知识库等飞书资源 实现多 Agent 路由,不同用户

2月 28, 2026

OpenClaw:开源的多渠道 AI 智能助手网关

什么是 OpenClaw? OpenClaw 是一个自托管的网关系统,它能够将你喜欢的聊天应用(WhatsApp、Telegram、Discord、iMessage 等)连接到 AI 编码助手。你只需在自己的机器(或服务器)上运行一个 Gateway 进程,它就会成为你的消息应用和始终可用的 AI

下一篇

用 OpenClaw 做视频:从创意到发布OpenClaw的完整流程

上一篇

Nextjs 实现国际化翻译 - App Router 模式解决方案

最近更新

  • Nextjs 实现国际化翻译 - App Router 模式解决方案
  • 什么是 Next.js Middleware?
  • 用 OpenClaw 做视频:从创意到发布OpenClaw的完整流程
  • OpenClaw 企业微信配置完全指南OpenClaw
  • OpenClaw 飞书配置完全指南OpenClaw

热门标签

工程 Flutter React JavaScript 面经 OpenClaw 前端 负载均衡

目录

©2026 cooyue. 保留部分权利。

使用 Halo 主题 Chirpy