跳到主要内容

中间件

中间件(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();
}

中间件执行顺序

  1. 中间件
  2. 静态文件(如果匹配)
  3. 路由处理器
  4. 页面组件

路由匹配

匹配器配置

使用 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 }
);
const response = NextResponse.next();

response.cookies.set("token", "jwt-token", {
httpOnly: true,
secure: true,
sameSite: "strict",
maxAge: 60 * 60 * 24 * 7,
});

return response;
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();
}

小结

本章我们学习了:

  1. 中间件的基本用法
  2. 路由匹配配置
  3. 常见用例(认证、日志、重定向等)
  4. 响应操作
  5. 高级用法

练习

  1. 实现一个认证中间件,保护需要登录的页面
  2. 创建一个日志中间件,记录所有请求
  3. 实现基于角色的访问控制
  4. 创建一个简单的限流中间件