1. 什么是中间件?
中间件是一个函数 ,它运行在服务器端,位于请求 和响应之间。当一个请求进入你的 Next.js 应用时,它会首先经过中间件,然后才会继续到对应的页面路由。
核心作用:
- 认证与授权:检查用户是否已登录,重定向未登录用户。
- 地理位置定位:根据用户的 IP 地址重定向到特定语言或地区的页面。
- A/B 测试:将用户随机分配到不同版本的页面。
- 日志与分析:记录请求信息,用于分析用户行为。
- 机器人检测与防火墙:阻止恶意请求。
2. 如何创建中间件
创建中间件非常简单,只需要在你的项目根目录下创建一个名为 middleware.ts (或 .js) 的文件。 文件位置:
lua
.
├── src/
│ ├── app/
│ │ └── page.tsx
│ └── middleware.ts <-- 在这里创建
├── package.json
└── ...
或者 ,如果你没有使用 src 目录:
lua
.
├── app/
│ └── page.tsx
├── middleware.ts <-- 在这里创建
├── package.json
└── ...
3. 中间件的核心:matcher 配置
默认情况下,中间件会匹配所有路径 (除了 _next/static, _next/image, api 等内部路径)。但这通常不是我们想要的,我们只想让中间件作用于特定的路由。 这时就需要 matcher 配置。它是一个数组,定义了哪些路径会触发中间件。
typescript
// middleware.ts
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
export function middleware(request: NextRequest) {
// 中间件逻辑...
}
// matcher 配置
export const config = {
matcher: [
/*
* 匹配所有路径路径,除了以这些开头的:
* - api (API routes)
* - _next/static (static files)
* - _next/image (image optimization files)
* - favicon.ico (favicon file)
*/
'/((?!api|_next/static|_next/image|favicon.ico).*)',
],
};
matcher 的高级用法
-
通配符 :
/about/:path*:匹配/about/及其所有子路径,如/about/team。
-
数组 :可以指定多个路径。
typescriptmatcher: ['/dashboard/:path*', '/profile', '/settings'] -
否定断言
(?!...):上面的例子中,(?!api|_next/static|...)是一个正则表达式的否定断言,意思是"不匹配以api、_next/static... 开头的路径"。这是最常用和推荐的模式,可以避免中间件影响静态资源和 API。
4. 实战示例
让我们通过几个常见例子来理解如何编写中间件逻辑。
示例 1:认证保护
目标 :保护 /dashboard 下的所有页面,如果用户未登录(没有 token),则重定向到登录页。
typescript
// middleware.ts
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
export function middleware(request: NextRequest) {
// 1. 检查请求路径是否包含 /dashboard
if (request.nextUrl.pathname.startsWith('/dashboard')) {
// 2. 从 cookies 中获取 token
const token = request.cookies.get('auth_token')?.value;
// 3. 如果没有 token,重定向到登录页
if (!token) {
// new URL('目标路径', '基础路径') 是创建重定向 URL 的安全方法
const loginUrl = new URL('/login', request.url);
return NextResponse.redirect(loginUrl);
}
}
// 4. 如果已登录或不访问 /dashboard,则正常放行
return NextResponse.next();
}
// 只对 /dashboard 及其子路径生效
export const config = {
matcher: ['/dashboard/:path*'],
};
示例 2:国际化重定向
目标 :根据用户的 Accept-Language 头部信息,将他们重定向到对应的语言页面(如 /en-US/ 或 /fr/)。
typescript
// middleware.ts
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
export function middleware(request: NextRequest) {
// 检查路径中是否已经有语言前缀
const pathname = request.nextUrl.pathname;
if (pathname.startsWith('/en-US') || pathname.startsWith('/fr')) {
return NextResponse.next();
}
// 获取 Accept-Language 头部
const acceptLanguage = request.headers.get('accept-language');
let locale = 'en-US'; // 默认语言
if (acceptLanguage) {
// 简单解析,实际项目中可能需要更复杂的逻辑
if (acceptLanguage.includes('fr')) {
locale = 'fr';
}
}
// 重定向到带语言前缀的首页
const localeUrl = new URL(`/${locale}${pathname}`, request.url);
return NextResponse.redirect(localeUrl);
}
// 匹配所有路径,但排除内部路径
export const config = {
matcher: [
'/((?!_next|api|favicon.ico).*)',
],
};
5. 中间件 API 详解
在 middleware 函数中,你主要会用到两个对象:
NextRequest 对象
它扩展了标准的 Request API,提供了一些便捷属性:
request.nextUrl:一个扩展了URL的对象,包含了路由信息。request.cookies:方便地读写 cookies。request.geo:获取请求的地理位置信息(国家、地区、城市等)。
NextResponse 对象
它扩展了标准的 Response API,用于生成响应:
NextResponse.next():继续处理请求,进入路由链。NextResponse.redirect(url):重定向到指定的 URL。NextResponse.rewrite(url):重写 URL。这是非常强大的功能,它会在服务器端"静默"地将请求/old-page映射到/new-page,但用户浏览器地址栏的 URL 仍然显示为/old-page。NextResponse.json():发送 JSON 响应。
6. 路由配置(App Router)
在 Next.js 的 App Router 中,路由配置主要是基于文件系统约定的,而不是一个中央配置文件。
- 动态路由 :
app/blog/[slug]/page.tsx->/blog/hello-world - 路由组 :
app/(marketing)/about/page.tsx->/about(括号不影响 URL) - 并行路由 :
app/@analytics/page.tsx-> 在layout.tsx中渲染 - 拦截路由 :
app/(.)photo/[id]/page.tsx-> 可以拦截/photo/123并在模态框中显示 中间件 (middleware.ts) 是这个文件系统路由之上的全局或条件性逻辑层 。它不直接定义路由,而是影响路由的行为(重定向、重写、认证等)。
总结
| 特性 | 描述 |
|---|---|
| 文件 | middleware.ts (或 .js) |
| 位置 | 项目根目录或 src/ 目录 |
| 核心功能 | 在页面渲染前执行服务器端代码 |
| 关键配置 | matcher 数组,用于定义中间件匹配的路径 |
| 主要用途 | 认证、重定向、A/B 测试、国际化、日志 |
| 核心 API | NextRequest (请求信息), NextResponse (响应控制) |
| 与路由关系 | 是文件系统路由之上的逻辑增强层 |