中间件
中间件(Middleware)允许在请求完成之前运行代码。可以用于认证、日志、重写、重定向等场景。
基本用法
在项目根目录创建 middleware.ts 文件:
// middleware.ts
import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";
export function middleware(request: NextRequest) {
console.log("请求路径:", request.nextUrl.pathname);
return NextResponse.next();
}
中间件执行顺序
- 中间件
- 静态文件(如果匹配)
- 路由处理器
- 页面组件
路由匹配
匹配器配置
使用 matcher 配置指定中间件应用的路径:
export const config = {
matcher: "/about",
};
多路径匹配
export const config = {
matcher: ["/about", "/contact"],
};
路径参数
export const config = {
matcher: ["/blog/:path*"],
};
排除路径
export const config = {
matcher: ["/((?!api|_next/static|_next/image|favicon.ico).*)"],
};
常见用例
认证保护
// middleware.ts
import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";
export function middleware(request: NextRequest) {
const token = request.cookies.get("token");
if (!token) {
const loginUrl = new URL("/login", request.url);
loginUrl.searchParams.set("callbackUrl", request.nextUrl.pathname);
return NextResponse.redirect(loginUrl);
}
return NextResponse.next();
}
export const config = {
matcher: ["/dashboard/:path*", "/profile/:path*"],
};
日志记录
import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";
export function middleware(request: NextRequest) {
const start = Date.now();
const response = NextResponse.next();
const duration = Date.now() - start;
console.log(`${request.method} ${request.nextUrl.pathname} - ${duration}ms`);
return response;
}
地理重定向
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("/cn", request.url));
}
return NextResponse.next();
}
请求重写
import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";
export function middleware(request: NextRequest) {
if (request.nextUrl.pathname.startsWith("/api/old")) {
const newUrl = new URL(
request.nextUrl.pathname.replace("/api/old", "/api/new"),
request.url
);
return NextResponse.rewrite(newUrl);
}
return NextResponse.next();
}
设置请求头
import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";
export function middleware(request: NextRequest) {
const response = NextResponse.next();
response.headers.set("x-custom-header", "custom-value");
response.headers.set("x-pathname", request.nextUrl.pathname);
return response;
}
CORS 处理
import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";
export function middleware(request: NextRequest) {
if (request.method === "OPTIONS") {
return new NextResponse(null, {
status: 200,
headers: {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, OPTIONS",
"Access-Control-Allow-Headers": "Content-Type, Authorization",
},
});
}
const response = NextResponse.next();
response.headers.set("Access-Control-Allow-Origin", "*");
response.headers.set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE");
response.headers.set("Access-Control-Allow-Headers", "Content-Type, Authorization");
return response;
}
export const config = {
matcher: "/api/:path*",
};
响应操作
重定向
return NextResponse.redirect(new URL("/login", request.url));
重写
return NextResponse.rewrite(new URL("/new-path", request.url));
JSON 响应
return NextResponse.json(
{ error: "未授权" },
{ status: 401 }
);
设置 Cookie
const response = NextResponse.next();
response.cookies.set("token", "jwt-token", {
httpOnly: true,
secure: true,
sameSite: "strict",
maxAge: 60 * 60 * 24 * 7,
});
return response;
删除 Cookie
const response = NextResponse.next();
response.cookies.delete("token");
return response;
高级用法
基于角色的访问控制
import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";
const roleRoutes: Record<string, string[]> = {
admin: ["/admin", "/dashboard"],
editor: ["/editor", "/dashboard"],
user: ["/dashboard"],
};
export function middleware(request: NextRequest) {
const token = request.cookies.get("token");
const role = request.cookies.get("role")?.value || "user";
if (!token) {
return NextResponse.redirect(new URL("/login", request.url));
}
const allowedRoutes = roleRoutes[role] || [];
const pathname = request.nextUrl.pathname;
const isAllowed = allowedRoutes.some((route) =>
pathname.startsWith(route)
);
if (!isAllowed) {
return NextResponse.redirect(new URL("/unauthorized", request.url));
}
return NextResponse.next();
}
export const config = {
matcher: ["/admin/:path*", "/editor/:path*", "/dashboard/:path*"],
};
限流
import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";
const rateLimit = new Map<string, { count: number; lastRequest: number }>();
export function middleware(request: NextRequest) {
const ip = request.ip || "unknown";
const now = Date.now();
const windowMs = 60 * 1000; // 1 分钟
const maxRequests = 100;
const userLimit = rateLimit.get(ip);
if (!userLimit || now - userLimit.lastRequest > windowMs) {
rateLimit.set(ip, { count: 1, lastRequest: now });
} else if (userLimit.count >= maxRequests) {
return NextResponse.json(
{ error: "请求过于频繁" },
{ status: 429 }
);
} else {
rateLimit.set(ip, {
count: userLimit.count + 1,
lastRequest: userLimit.lastRequest,
});
}
return NextResponse.next();
}
小结
本章我们学习了:
- 中间件的基本用法
- 路由匹配配置
- 常见用例(认证、日志、重定向等)
- 响应操作
- 高级用法
练习
- 实现一个认证中间件,保护需要登录的页面
- 创建一个日志中间件,记录所有请求
- 实现基于角色的访问控制
- 创建一个简单的限流中间件