Middleware
Middleware 允许在请求完成之前执行代码,然后,基于即将到来的请求,你可以修改 response、重定向、修改 request 或者 response headers,或者直接响应。
中间件在缓存内容和路由匹配之前运行。
约定
在项目的根目录下使用middleware.ts来定义中间件。需要跟 app 或者 pages 同一层级。
例子
js1// middleware.ts 2import { NextResponse } from 'next/server' 3import type { NextRequest } from 'next/server' 4 5// This function can be marked `async` if using `await` inside 6export function middleware(request: NextRequest) { 7 return NextResponse.redirect(new URL('/home', request.url)) 8} 9 10// See "Matching Paths" below to learn more 11export const config = { 12 matcher: '/about/:path*', 13}
匹配 path
有两种方式可以规定哪些 paths 会调用 Middleware:
- Matcher
- Conditional statements
Matcher
使用 Matcher 让 Middleware 只在指定的 path 执行:
js1export const config = { 2 matcher: '/about/:path*', 3}
你可以指定单个或者多个 path:
js1export const config = { 2 matcher: ['/about/:path*', '/dashboard/:path*'], 3}
matcher 设置允许使用正则表达式匹配:
js1export const config = { 2 matcher: [ 3 /* 4 * Match all request paths except for the ones starting with: 5 * - api (API routes) 6 * - _next/static (static files) 7 * - _next/image (image optimization files) 8 * - favicon.ico (favicon file) 9 */ 10 '/((?!api|_next/static|_next/image|favicon.ico).*)', 11 ], 12}
matcher 值必须是常量,这样才能在构建时对其进行静态分析
配置 matcher 的规则:
- 必须以
/开头 - 可以包含参数,比如:
/about/:path会匹配/about/a和/about/b但是不匹配/about/a/c - 可以用修饰符,比如
/about/:path*匹配/about/a/b/c,*表示 0 或更多,?表示0或 1,+表示 1 或更多。 - 可以用括号包住常用的正则表达式来匹配,
/about/(.*)的效果跟/about/:path*是一样的。
条件声明
js1import { NextResponse } from 'next/server' 2import type { NextRequest } from 'next/server' 3 4export function middleware(request: NextRequest) { 5 if (request.nextUrl.pathname.startsWith('/about')) { 6 return NextResponse.rewrite(new URL('/about-2', request.url)) 7 } 8 9 if (request.nextUrl.pathname.startsWith('/dashboard')) { 10 return NextResponse.rewrite(new URL('/dashboard/user', request.url)) 11 } 12}
NextResponse
NextResponse允许我们做:
- 重定向
- 重写 response
- 设置 request headers
- 设置 response cookies
- 设置 response headers
要从中间件生成 response,你可以:
- 重写一个生成响应的路由(page 或 路由 handler)
- 直接 return
NextResponse
使用 cookies
Cookies 是常用的 header。在 Request 中,他们被存到 header 的 Cookie 字段,在 Response 中,他们在Set-Cookie中。
Nextjs 提供了一个方便的方法,可以通过 NextRequest 和 NextResponse 上的 cookie 扩展来访问和操作这些 cookie。
- 对于 requests,cookie 具有以下方法:get、getAll、set 和 delete cookie。您可以使用 has 检查是否存在 cookie,也可以使用 clear 删除所有 cookie。
- 对于响应,cookies 也有下面的方法:
get,getAll,set, anddelete.
js1import { NextResponse } from 'next/server' 2import type { NextRequest } from 'next/server' 3 4export function middleware(request: NextRequest) { 5 // Assume a "Cookie:nextjs=fast" header to be present on the incoming request 6 // Getting cookies from the request using the `RequestCookies` API 7 let cookie = request.cookies.get('nextjs') 8 console.log(cookie) // => { name: 'nextjs', value: 'fast', Path: '/' } 9 const allCookies = request.cookies.getAll() 10 console.log(allCookies) // => [{ name: 'nextjs', value: 'fast' }] 11 12 request.cookies.has('nextjs') // => true 13 request.cookies.delete('nextjs') 14 request.cookies.has('nextjs') // => false 15 16 // Setting cookies on the response using the `ResponseCookies` API 17 const response = NextResponse.next() 18 response.cookies.set('vercel', 'fast') 19 response.cookies.set({ 20 name: 'vercel', 21 value: 'fast', 22 path: '/', 23 }) 24 cookie = response.cookies.get('vercel') 25 console.log(cookie) // => { name: 'vercel', value: 'fast', Path: '/' } 26 // The outgoing response will have a `Set-Cookie:vercel=fast;path=/test` header. 27 28 return response 29}
设置 Headers
你可以使用NextResponse来设置 request 或者 response 的 headers。(自 13.0.0 开始,支持设置 request headers)。
js1import { NextResponse } from 'next/server' 2import type { NextRequest } from 'next/server' 3 4export function middleware(request: NextRequest) { 5 // Clone the request headers and set a new header `x-hello-from-middleware1` 6 const requestHeaders = new Headers(request.headers) 7 requestHeaders.set('x-hello-from-middleware1', 'hello') 8 9 // You can also set request headers in NextResponse.rewrite 10 const response = NextResponse.next({ 11 request: { 12 // New request headers 13 headers: requestHeaders, 14 }, 15 }) 16 17 // Set a new response header `x-hello-from-middleware2` 18 response.headers.set('x-hello-from-middleware2', 'hello') 19 return response 20}
生成 Response
你可以直接通过中间件来返回response,或者返回NextResponse实例。
js1import { NextRequest, NextResponse } from 'next/server' 2import { isAuthenticated } from '@lib/auth' 3 4// Limit the middleware to paths starting with `/api/` 5export const config = { 6 matcher: '/api/:function*', 7} 8 9export function middleware(request: NextRequest) { 10 // Call our authentication function to check the request 11 if (!isAuthenticated(request)) { 12 // Respond with JSON indicating an error message 13 return new NextResponse(JSON.stringify({ success: false, message: 'authentication failed' }), { 14 status: 401, 15 headers: { 'content-type': 'application/json' }, 16 }) 17 } 18}
高级中间件标志
在 Next.js 的 v13.1 版本中,引入了两个额外的中间件标志,分别是 skipMiddlewareUrlNormalize 和 skipTrailingSlashRedirect,用于处理高级用例。
skipTrailingSlashRedirect 允许禁用 Next.js 的默认重定向,以便在中间件内部进行自定义处理,从而允许保留一些路径的尾随斜杠,而对其他路径不做处理,使得渐进式迁移变得更加容易。
js1module.exports = { 2 skipTrailingSlashRedirect: true, 3}
js1const legacyPrefixes = ['/docs', '/blog'] 2 3export default async function middleware(req) { 4 const { pathname } = req.nextUrl 5 6 if (legacyPrefixes.some((prefix) => pathname.startsWith(prefix))) { 7 return NextResponse.next() 8 } 9 10 // apply trailing slash handling 11 if (!pathname.endsWith('/') && !pathname.match(/((?!\.well-known(?:\/.*)?)(?:[^/]+\/)*[^/]+\.\w+)/)) { 12 req.nextUrl.pathname += '/' 13 return NextResponse.redirect(req.nextUrl) 14 } 15}
skipMiddlewareUrlNormalize 允许禁用 Next.js 对 URL 进行的规范化处理,以便处理直接访问和客户端过渡的方式保持一致。在一些高级情况下,你可能需要完全控制使用原始 URL,而这个功能可以实现这一点。
js1module.exports = { 2 skipMiddlewareUrlNormalize: true, 3}
js1export default async function middleware(req) { 2 const { pathname } = req.nextUrl 3 4 // GET /_next/data/build-id/hello.json 5 6 console.log(pathname) 7 // with the flag this now /_next/data/build-id/hello.json 8 // without the flag this would be normalized to /hello 9}