05-服务端渲染与元框架——07. 中间件 - Middleware
·
07. 中间件 - Middleware
概述
Middleware 是在请求完成前执行的代码,可以拦截请求、修改响应、实现身份验证、重定向、日志记录等功能。它在 Next.js 中运行在边缘环境,性能极高。
| 维度 | 内容 |
|---|---|
| What | 在请求完成前执行的代码,可修改响应 |
| Why | 实现身份验证、重定向、请求日志等 |
| When | 请求处理前需要执行逻辑时 |
| Where | 项目根目录的 middleware.js 文件 |
| Who | 需要请求拦截的开发者 |
| How | export function middleware(request) { if (!request.cookies.get('token')) return NextResponse.redirect('/login') } |
1. Middleware 基础
1.1 创建 Middleware
// middleware.js (项目根目录)
import { NextResponse } from 'next/server';
export function middleware(request) {
// 中间件逻辑
console.log('请求路径:', request.nextUrl.pathname);
return NextResponse.next(); // 继续处理请求
}
// 配置匹配路径
export const config = {
matcher: '/about/:path*',
};
1.2 基础示例
// middleware.js
import { NextResponse } from 'next/server';
export function middleware(request) {
// 获取当前路径
const { pathname } = request.nextUrl;
// 记录日志
console.log(`[${new Date().toISOString()}] ${request.method} ${pathname}`);
// 添加自定义请求头
const response = NextResponse.next();
response.headers.set('X-Custom-Header', 'middleware-added');
return response;
}
// 匹配所有路径
export const config = {
matcher: '/:path*',
};
2. 路径匹配
2.1 单个路径
// middleware.js
export const config = {
matcher: '/about',
};
2.2 多个路径
// middleware.js
export const config = {
matcher: ['/about', '/contact', '/dashboard/:path*'],
};
2.3 排除路径
// middleware.js
export const config = {
matcher: [
'/((?!api|_next/static|_next/image|favicon.ico).*)',
],
};
2.4 正则匹配
// middleware.js
export const config = {
matcher: [
'/dashboard/:path*',
'/profile/:path*',
'/settings/:path*',
],
};
3. 身份验证
3.1 基于 Cookie 的认证
// middleware.js
import { NextResponse } from 'next/server';
export function middleware(request) {
const { pathname } = request.nextUrl;
// 公开路径
const publicPaths = ['/login', '/register', '/'];
const isPublicPath = publicPaths.includes(pathname);
// 获取 token
const token = request.cookies.get('token')?.value;
const isAuthenticated = !!token;
// 未认证且访问私有路径 → 重定向到登录页
if (!isAuthenticated && !isPublicPath) {
const loginUrl = new URL('/login', request.url);
loginUrl.searchParams.set('from', pathname);
return NextResponse.redirect(loginUrl);
}
// 已认证且访问登录页 → 重定向到仪表盘
if (isAuthenticated && pathname === '/login') {
return NextResponse.redirect(new URL('/dashboard', request.url));
}
return NextResponse.next();
}
export const config = {
matcher: [
'/dashboard/:path*',
'/profile/:path*',
'/settings/:path*',
'/login',
'/register',
],
};
3.2 基于 Session 的认证
// middleware.js
import { NextResponse } from 'next/server';
import { getToken } from 'next-auth/jwt';
export async function middleware(request) {
const token = await getToken({ req: request });
const { pathname } = request.nextUrl;
// 需要认证的路由
const authRoutes = ['/dashboard', '/profile', '/settings'];
const isAuthRoute = authRoutes.some(route => pathname.startsWith(route));
if (isAuthRoute && !token) {
const loginUrl = new URL('/login', request.url);
loginUrl.searchParams.set('from', pathname);
return NextResponse.redirect(loginUrl);
}
// 管理员路由
if (pathname.startsWith('/admin')) {
if (!token || token.role !== 'admin') {
return NextResponse.redirect(new URL('/dashboard', request.url));
}
}
return NextResponse.next();
}
3.3 JWT 验证
// middleware.js
import { NextResponse } from 'next/server';
import jwt from 'jsonwebtoken';
export function middleware(request) {
const token = request.cookies.get('jwt')?.value;
const { pathname } = request.nextUrl;
// 验证 JWT
if (pathname.startsWith('/api/protected')) {
try {
jwt.verify(token, process.env.JWT_SECRET);
} catch (error) {
return NextResponse.json(
{ error: '未授权' },
{ status: 401 }
);
}
}
return NextResponse.next();
}
4. 重定向和重写
4.1 重定向
// middleware.js
import { NextResponse } from 'next/server';
export function middleware(request) {
const { pathname } = request.nextUrl;
// 旧路径重定向到新路径
if (pathname === '/old-about') {
return NextResponse.redirect(new URL('/about', request.url));
}
// 永久重定向 (301)
if (pathname === '/old-blog') {
return NextResponse.redirect(new URL('/blog', request.url), { status: 301 });
}
// 临时重定向 (307)
if (pathname === '/maintenance') {
return NextResponse.redirect(new URL('/maintenance.html', request.url), { status: 307 });
}
return NextResponse.next();
}
4.2 重写
// middleware.js
import { NextResponse } from 'next/server';
export function middleware(request) {
const { pathname } = request.nextUrl;
// A/B 测试:随机显示不同页面
if (pathname === '/landing') {
const isVariant = Math.random() < 0.5;
const url = request.nextUrl.clone();
url.pathname = isVariant ? '/landing-variant' : '/landing';
return NextResponse.rewrite(url);
}
// 国际化:根据 cookie 重写路径
const locale = request.cookies.get('locale')?.value || 'zh';
if (pathname === '/') {
const url = request.nextUrl.clone();
url.pathname = `/${locale}`;
return NextResponse.rewrite(url);
}
return NextResponse.next();
}
4.3 地理重定向
// middleware.js
import { NextResponse } from 'next/server';
export function middleware(request) {
const country = request.geo?.country || 'US';
const { pathname } = request.nextUrl;
// 根据国家重定向
if (pathname === '/') {
if (country === 'CN') {
return NextResponse.redirect(new URL('/cn', request.url));
}
if (country === 'JP') {
return NextResponse.redirect(new URL('/jp', request.url));
}
}
return NextResponse.next();
}
5. 请求和响应修改
5.1 添加请求头
// middleware.js
import { NextResponse } from 'next/server';
export function middleware(request) {
const response = NextResponse.next();
// 添加响应头
response.headers.set('X-Frame-Options', 'DENY');
response.headers.set('X-Content-Type-Options', 'nosniff');
response.headers.set('Referrer-Policy', 'strict-origin-when-cross-origin');
// 设置 Cookie
response.cookies.set('visited', 'true', { maxAge: 60 * 60 * 24 });
// 删除 Cookie
response.cookies.delete('temp');
return response;
}
5.2 修改请求
// middleware.js
import { NextResponse } from 'next/server';
export function middleware(request) {
const requestHeaders = new Headers(request.headers);
// 添加自定义请求头
requestHeaders.set('x-user-id', '123');
requestHeaders.set('x-request-time', Date.now().toString());
return NextResponse.next({
request: {
headers: requestHeaders,
},
});
}
6. 请求日志和监控
6.1 请求日志
// middleware.js
import { NextResponse } from 'next/server';
export function middleware(request) {
const { method, url, headers } = request;
const userAgent = headers.get('user-agent');
const ip = headers.get('x-forwarded-for') || 'unknown';
console.log({
timestamp: new Date().toISOString(),
method,
url,
userAgent,
ip,
});
return NextResponse.next();
}
export const config = {
matcher: '/:path*',
};
6.2 性能监控
// middleware.js
import { NextResponse } from 'next/server';
export function middleware(request) {
const start = Date.now();
const response = NextResponse.next();
response.headers.set('X-Response-Time', `${Date.now() - start}ms`);
return response;
}
7. 完整示例:认证中间件
// middleware.js
import { NextResponse } from 'next/server';
import { getToken } from 'next-auth/jwt';
// 公开路径(不需要认证)
const publicPaths = [
'/',
'/login',
'/register',
'/about',
'/contact',
'/api/auth',
];
// 角色权限映射
const rolePermissions = {
user: ['/dashboard', '/profile'],
admin: ['/dashboard', '/profile', '/admin', '/settings'],
};
// 检查路径是否需要认证
function isPublicPath(pathname) {
return publicPaths.some(path => pathname === path || pathname.startsWith(`${path}/`));
}
// 检查用户是否有权限访问路径
function hasPermission(pathname, role) {
const allowedPaths = rolePermissions[role] || [];
return allowedPaths.some(path => pathname === path || pathname.startsWith(`${path}/`));
}
export async function middleware(request) {
const { pathname } = request.nextUrl;
// 1. 跳过公开路径
if (isPublicPath(pathname)) {
return NextResponse.next();
}
// 2. 获取用户 token
const token = await getToken({ req: request });
// 3. 未认证 → 重定向到登录页
if (!token) {
const loginUrl = new URL('/login', request.url);
loginUrl.searchParams.set('from', pathname);
return NextResponse.redirect(loginUrl);
}
// 4. 检查角色权限
if (!hasPermission(pathname, token.role)) {
return NextResponse.redirect(new URL('/dashboard', request.url));
}
// 5. 添加用户信息到请求头
const response = NextResponse.next();
response.headers.set('x-user-id', token.sub);
response.headers.set('x-user-role', token.role);
return response;
}
// 配置匹配路径
export const config = {
matcher: [
'/dashboard/:path*',
'/profile/:path*',
'/settings/:path*',
'/admin/:path*',
'/api/protected/:path*',
],
};
8. 总结
核心要点
| 要点 | 说明 |
|---|---|
| 位置 | 项目根目录 middleware.js |
| 运行环境 | 边缘运行时(Edge Runtime) |
| 主要用途 | 认证、重定向、日志、A/B 测试 |
| 配置 | config.matcher 匹配路径 |
常用模式
| 场景 | 实现方式 |
|---|---|
| 身份验证 | 检查 token,无则重定向 |
| 重定向 | NextResponse.redirect() |
| 重写 URL | NextResponse.rewrite() |
| 添加头 | response.headers.set() |
| 设置 Cookie | response.cookies.set() |
记忆口诀
中间件在根目录,请求拦截好帮手
认证重定向都能做,路径匹配配置走
边缘运行速度快,性能提升不用愁
9. 相关资源
更多推荐
所有评论(0)