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 环境。

这意味着:

  1. 不能 使用 Node.js 原生模块(如 fs, path, crypto)。
  2. 不能 直接连接数据库(如 Prisma 默认不支持 Edge,除非用 Data Proxy)。
  3. 不能 执行耗时的操作(通常限制执行时间在 30ms - 1.5s 内)。

如果需要在中间件里查数据库校验 Token,建议调用外部独立的 HTTP API,或者使用支持 Edge 的数据库驱动 (如 Drizzle + Neon)。