乐闻世界logo
搜索文章和话题

Next.js 的中间件是如何工作的?

2月17日 23:30

Next.js 的中间件(Middleware)是一个强大的功能,允许你在请求到达页面之前执行代码。这对于身份验证、重定向、A/B 测试等场景非常有用。

什么是中间件?

中间件是在 Next.js 应用中运行的函数,它在请求完成之前拦截传入的请求。中间件可以:

  1. 重写路径:将一个路径重写到另一个路径
  2. 重定向:将用户重定向到不同的 URL
  3. 修改请求/响应:添加或修改请求头、响应头
  4. 身份验证:检查用户是否已登录
  5. 地理位置路由:根据用户位置重定向

基本用法

创建中间件文件

在项目根目录创建 middleware.jsmiddleware.ts 文件:

javascript
// middleware.js import { NextResponse } from 'next/server'; export function middleware(request) { return NextResponse.next(); } export const config = { matcher: '/about/:path*', };

中间件配置

javascript
// middleware.js export const config = { matcher: [ // 匹配所有路径 '/((?!api|_next/static|_next/image|favicon.ico).*)', // 匹配特定路径 '/dashboard/:path*', // 使用正则表达式 '/((?!api|_next/static|_next/image|favicon.ico).*)', // 匹配多个路径 ['/about/:path*', '/contact/:path*'], ], };

中间件 API

NextResponse 方法

javascript
import { NextResponse } from 'next/server'; export function middleware(request) { // 继续到下一个中间件或页面 return NextResponse.next(); // 重定向 return NextResponse.redirect(new URL('/login', request.url)); // 重写路径 return NextResponse.rewrite(new URL('/about', request.url)); // 返回自定义响应 return NextResponse.json({ message: 'Hello' }); }

修改请求和响应

javascript
import { NextResponse } from 'next/server'; export function middleware(request) { const response = NextResponse.next(); // 添加响应头 response.headers.set('x-custom-header', 'custom-value'); // 设置 Cookie response.cookies.set('theme', 'dark', { httpOnly: true, secure: process.env.NODE_ENV === 'production', sameSite: 'strict', }); return response; }

实际应用场景

1. 身份验证保护

javascript
// middleware.js import { NextResponse } from 'next/server'; export function middleware(request) { const { pathname } = request.nextUrl; // 公开路径 const publicPaths = ['/login', '/register', '/api/auth']; const isPublicPath = publicPaths.some(path => pathname.startsWith(path) ); if (isPublicPath) { return NextResponse.next(); } // 检查认证令牌 const token = request.cookies.get('auth-token')?.value; if (!token) { return NextResponse.redirect(new URL('/login', request.url)); } // 验证令牌 try { const user = verifyToken(token); // 将用户信息添加到请求头 const response = NextResponse.next(); response.headers.set('x-user-id', user.id); response.headers.set('x-user-role', user.role); return response; } catch (error) { return NextResponse.redirect(new URL('/login', request.url)); } } export const config = { matcher: [ '/((?!api|_next/static|_next/image|favicon.ico).*)', ], };

2. 国际化路由

javascript
// middleware.js import { NextResponse } from 'next/server'; const locales = ['en', 'zh', 'es']; const defaultLocale = 'en'; export function middleware(request) { const { pathname } = request.nextUrl; // 检查路径中是否包含语言代码 const pathnameIsMissingLocale = locales.every( locale => !pathname.startsWith(`/${locale}/`) && pathname !== `/${locale}` ); if (pathnameIsMissingLocale) { // 检查 Accept-Language 头 const acceptLanguage = request.headers.get('accept-language') || ''; const preferredLocale = locales.find(locale => acceptLanguage.includes(locale) ) || defaultLocale; return NextResponse.redirect( new URL(`/${preferredLocale}${pathname}`, request.url) ); } return NextResponse.next(); } export const config = { matcher: [ '/((?!api|_next/static|_next/image|favicon.ico).*)', ], };

3. A/B 测试

javascript
// middleware.js import { NextResponse } from 'next/server'; export function middleware(request) { const { pathname } = request.nextUrl; // 只对特定页面进行 A/B 测试 if (pathname === '/landing') { // 获取或创建用户 ID const userId = request.cookies.get('user-id')?.value || generateUserId(); // 根据 ID 决定显示哪个版本 const variant = hash(userId) % 2 === 0 ? 'a' : 'b'; const response = NextResponse.rewrite( new URL(`/landing-${variant}`, request.url) ); // 设置用户 ID Cookie if (!request.cookies.get('user-id')) { response.cookies.set('user-id', userId); } // 添加 A/B 测试头 response.headers.set('x-ab-variant', variant); return response; } return NextResponse.next(); } function generateUserId() { return Math.random().toString(36).substring(2, 15); } function hash(str) { let hash = 0; for (let i = 0; i < str.length; i++) { const char = str.charCodeAt(i); hash = ((hash << 5) - hash) + char; hash = hash & hash; } return Math.abs(hash); }

4. 地理位置路由

javascript
// middleware.js import { NextResponse } from 'next/server'; export function middleware(request) { const { pathname } = request.nextUrl; // 只对首页进行地理位置路由 if (pathname === '/') { const country = request.geo?.country || 'US'; // 根据国家重定向到不同版本 const countryRoutes = { 'US': '/us', 'CN': '/cn', 'JP': '/jp', 'DE': '/de', }; const targetPath = countryRoutes[country] || '/us'; return NextResponse.redirect( new URL(targetPath, request.url) ); } return NextResponse.next(); }

5. 维护模式

javascript
// middleware.js import { NextResponse } from 'next/server'; export function middleware(request) { const { pathname } = request.nextUrl; // 检查是否在维护模式 const isMaintenanceMode = process.env.MAINTENANCE_MODE === 'true'; if (isMaintenanceMode) { // 允许管理员访问 const isAdmin = request.cookies.get('admin-token')?.value === process.env.ADMIN_TOKEN; if (isAdmin) { return NextResponse.next(); } // 重定向到维护页面 return NextResponse.rewrite( new URL('/maintenance', request.url) ); } return NextResponse.next(); } export const config = { matcher: [ '/((?!api|_next/static|_next/image|favicon.ico|maintenance).*)', ], };

6. 速率限制

javascript
// middleware.js import { NextResponse } from 'next/server'; const rateLimit = new Map(); export function middleware(request) { const { pathname } = request.nextUrl; // 只对 API 路由进行速率限制 if (!pathname.startsWith('/api/')) { return NextResponse.next(); } const ip = request.ip || 'unknown'; const now = Date.now(); const windowMs = 60 * 1000; // 1 分钟 const maxRequests = 100; // 获取或创建速率限制记录 const record = rateLimit.get(ip) || { count: 0, resetTime: now + windowMs }; // 重置过期的记录 if (now > record.resetTime) { record.count = 0; record.resetTime = now + windowMs; } // 增加请求计数 record.count++; rateLimit.set(ip, record); // 检查是否超过限制 if (record.count > maxRequests) { return NextResponse.json( { error: 'Too many requests' }, { status: 429, headers: { 'Retry-After': Math.ceil((record.resetTime - now) / 1000).toString(), }, } ); } return NextResponse.next(); }

7. CORS 配置

javascript
// middleware.js import { NextResponse } from 'next/server'; export function middleware(request) { const { pathname } = request.nextUrl; // 只对 API 路由应用 CORS if (!pathname.startsWith('/api/')) { return NextResponse.next(); } const response = NextResponse.next(); // 设置 CORS 头 response.headers.set('Access-Control-Allow-Credentials', 'true'); response.headers.set('Access-Control-Allow-Origin', '*'); response.headers.set('Access-Control-Allow-Methods', 'GET,OPTIONS,PATCH,DELETE,POST,PUT'); response.headers.set( 'Access-Control-Allow-Headers', 'X-CSRF-Token, X-Requested-With, Accept, Accept-Version, Content-Length, Content-MD5, Content-Type, Date, X-Api-Version' ); return response; }

8. 日志记录

javascript
// middleware.js import { NextResponse } from 'next/server'; export function middleware(request) { const start = Date.now(); const response = NextResponse.next(); // 记录请求信息 const logData = { method: request.method, url: request.url, userAgent: request.headers.get('user-agent'), ip: request.ip, timestamp: new Date().toISOString(), }; console.log('Request:', JSON.stringify(logData)); // 记录响应时间 response.headers.set('x-response-time', `${Date.now() - start}ms`); return response; }

高级用法

1. 条件中间件

javascript
// middleware.js import { NextResponse } from 'next/server'; export function middleware(request) { const { pathname } = request.nextUrl; // 根据路径应用不同的逻辑 if (pathname.startsWith('/api/')) { return apiMiddleware(request); } else if (pathname.startsWith('/dashboard/')) { return dashboardMiddleware(request); } else if (pathname.startsWith('/admin/')) { return adminMiddleware(request); } return NextResponse.next(); } function apiMiddleware(request) { // API 特定逻辑 return NextResponse.next(); } function dashboardMiddleware(request) { // 仪表板特定逻辑 return NextResponse.next(); } function adminMiddleware(request) { // 管理员特定逻辑 return NextResponse.next(); }

2. 链式中间件

javascript
// middleware.js import { NextResponse } from 'next/server'; export function middleware(request) { const middlewares = [ authMiddleware, rateLimitMiddleware, loggingMiddleware, ]; for (const mw of middlewares) { const result = mw(request); if (result) { return result; } } return NextResponse.next(); } function authMiddleware(request) { const token = request.cookies.get('auth-token')?.value; if (!token) { return NextResponse.redirect(new URL('/login', request.url)); } return null; } function rateLimitMiddleware(request) { // 速率限制逻辑 return null; } function loggingMiddleware(request) { // 日志记录逻辑 return null; }

3. 动态重写

javascript
// middleware.js import { NextResponse } from 'next/server'; export function middleware(request) { const { pathname } = request.nextUrl; // 动态路由重写 if (pathname.startsWith('/blog/')) { const slug = pathname.split('/')[2]; return NextResponse.rewrite( new URL(`/api/blog?slug=${slug}`, request.url) ); } // 用户资料重写 if (pathname.startsWith('/u/')) { const username = pathname.split('/')[2]; return NextResponse.rewrite( new URL(`/profile?username=${username}`, request.url) ); } return NextResponse.next(); }

最佳实践

1. 避免在中间件中进行繁重操作

javascript
// ❌ 不好的做法:在中间件中进行数据库查询 export function middleware(request) { const user = await db.user.findUnique({ where: { id: request.cookies.get('user-id')?.value } }); if (!user) { return NextResponse.redirect(new URL('/login', request.url)); } return NextResponse.next(); } // ✅ 好的做法:只验证令牌 export function middleware(request) { const token = request.cookies.get('auth-token')?.value; if (!token || !verifyToken(token)) { return NextResponse.redirect(new URL('/login', request.url)); } return NextResponse.next(); }

2. 使用 matcher 精确匹配

javascript
// ❌ 不好的做法:匹配所有路径 export const config = { matcher: '/(.*)', }; // ✅ 好的做法:排除不需要的路径 export const config = { matcher: [ '/((?!api|_next/static|_next/image|favicon.ico).*)', ], };

3. 处理边缘情况

javascript
export function middleware(request) { // 处理 OPTIONS 请求 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', }, }); } return NextResponse.next(); }

4. 使用环境变量

javascript
export function middleware(request) { // 根据环境变量应用不同逻辑 if (process.env.NODE_ENV === 'production') { // 生产环境特定逻辑 return productionMiddleware(request); } else { // 开发环境特定逻辑 return developmentMiddleware(request); } }

Next.js 中间件是一个强大的工具,通过合理使用,可以实现复杂的路由逻辑、身份验证、重定向等功能,同时保持应用的性能和安全性。

标签:Next.js