中介軟體
中介軟體允許您在請求完成之前執行程式碼。然後,根據傳入的請求,您可以透過重寫、重新導向、修改請求或回應標頭,或直接回應來修改回應。
中介軟體在快取內容和路由比對之前執行。請參閱比對路徑以取得更多詳細資訊。
使用案例
將中介軟體整合到您的應用程式中可以顯著提升效能、安全性和使用者體驗。中介軟體特別有效的一些常見情境包括
- 身份驗證和授權:確保使用者身份並檢查工作階段 Cookie,然後再授予對特定頁面或 API 路由的存取權。
- 伺服器端重新導向:根據特定條件(例如,地區設定、使用者角色)在伺服器層級重新導向使用者。
- 路徑重寫:透過根據請求屬性動態重寫 API 路由或頁面的路徑,來支援 A/B 測試、功能推出或舊版路徑。
- 機器人偵測:透過偵測和封鎖機器人流量來保護您的資源。
- 記錄和分析:擷取和分析請求資料以取得深入見解,然後再由頁面或 API 處理。
- 功能標記:動態啟用或停用功能,以實現無縫功能推出或測試。
認識到中介軟體可能不是最佳方法的狀況也同樣重要。以下是一些需要注意的情境
- 複雜的資料獲取和操作:中介軟體並非設計用於直接資料獲取或操作,這應該在路由處理器或伺服器端公用程式中完成。
- 繁重的計算任務:中介軟體應該是輕量級且快速回應的,否則可能會導致頁面載入延遲。繁重的計算任務或長時間執行的程序應該在專用的路由處理器中完成。
- 廣泛的工作階段管理:雖然中介軟體可以管理基本工作階段任務,但廣泛的工作階段管理應該由專用的身份驗證服務或在路由處理器中管理。
- 直接資料庫操作:不建議在中介軟體中執行直接資料庫操作。資料庫互動應該在路由處理器或伺服器端公用程式中完成。
慣例
在專案根目錄中使用檔案 middleware.ts
(或 .js
) 來定義中介軟體。例如,與 pages
或 app
相同的層級,或在 src
內(如果適用)。
注意:雖然每個專案僅支援一個
middleware.ts
檔案,您仍然可以模組化地組織您的中介軟體邏輯。將中介軟體功能分解為個別的.ts
或.js
檔案,並將它們匯入您的主要middleware.ts
檔案中。這允許更清晰地管理特定於路由的中介軟體,並在middleware.ts
中彙總以進行集中控制。透過強制使用單一中介軟體檔案,它可以簡化設定、防止潛在的衝突,並透過避免多個中介軟體層來最佳化效能。
範例
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
// This function can be marked `async` if using `await` inside
export function middleware(request: NextRequest) {
return NextResponse.redirect(new URL('/home', request.url))
}
// See "Matching Paths" below to learn more
export const config = {
matcher: '/about/:path*',
}
比對路徑
將為專案中的每個路由調用中介軟體。鑑於此,使用比對器精確地鎖定或排除特定路由至關重要。以下是執行順序
- 來自
next.config.js
的headers
- 來自
next.config.js
的redirects
- 中介軟體 (
rewrites
、redirects
等) - 來自
next.config.js
的beforeFiles
(rewrites
) - 檔案系統路由 (
public/
、_next/static/
、pages/
、app/
等) - 來自
next.config.js
的afterFiles
(rewrites
) - 動態路由 (
/blog/[slug]
) - 來自
next.config.js
的fallback
(rewrites
)
有兩種方法可以定義中介軟體將在哪些路徑上執行
Matcher
matcher
允許您篩選中介軟體以在特定路徑上執行。
export const config = {
matcher: '/about/:path*',
}
您可以使用陣列語法比對單一路徑或多個路徑
export const config = {
matcher: ['/about/:path*', '/dashboard/:path*'],
}
matcher
設定允許完整的 regex,因此支援像負向先行斷言或字元比對之類的比對。此處可以看到負向先行斷言的範例,以比對除了特定路徑之外的所有路徑
export const config = {
matcher: [
/*
* Match all request paths except for the ones starting with:
* - api (API routes)
* - _next/static (static files)
* - _next/image (image optimization files)
* - favicon.ico, sitemap.xml, robots.txt (metadata files)
*/
'/((?!api|_next/static|_next/image|favicon.ico|sitemap.xml|robots.txt).*)',
],
}
您也可以使用 missing
或 has
陣列,或兩者的組合來繞過某些請求的中介軟體
export const config = {
matcher: [
/*
* Match all request paths except for the ones starting with:
* - api (API routes)
* - _next/static (static files)
* - _next/image (image optimization files)
* - favicon.ico, sitemap.xml, robots.txt (metadata files)
*/
{
source:
'/((?!api|_next/static|_next/image|favicon.ico|sitemap.xml|robots.txt).*)',
missing: [
{ type: 'header', key: 'next-router-prefetch' },
{ type: 'header', key: 'purpose', value: 'prefetch' },
],
},
{
source:
'/((?!api|_next/static|_next/image|favicon.ico|sitemap.xml|robots.txt).*)',
has: [
{ type: 'header', key: 'next-router-prefetch' },
{ type: 'header', key: 'purpose', value: 'prefetch' },
],
},
{
source:
'/((?!api|_next/static|_next/image|favicon.ico|sitemap.xml|robots.txt).*)',
has: [{ type: 'header', key: 'x-present' }],
missing: [{ type: 'header', key: 'x-missing', value: 'prefetch' }],
},
],
}
小知識:
matcher
值需要是常數,以便可以在建置時靜態分析它們。動態值(例如變數)將被忽略。
已設定的比對器
- 必須以
/
開頭 - 可以包含具名參數:
/about/:path
比對/about/a
和/about/b
,但不比對/about/a/c
- 可以在具名參數上使用修飾符(以
:
開頭):/about/:path*
比對/about/a/b/c
,因為*
是零或多個。?
是零或一個,而+
是一個或多個 - 可以使用括在括號中的正規表示式:
/about/(.*)
與/about/:path*
相同
閱讀 path-to-regexp 文件以取得更多詳細資訊。
小知識:為了向後相容性,Next.js 始終將
/public
視為/public/index
。因此,/public/:path
的比對器將會比對。
條件陳述式
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
export function middleware(request: NextRequest) {
if (request.nextUrl.pathname.startsWith('/about')) {
return NextResponse.rewrite(new URL('/about-2', request.url))
}
if (request.nextUrl.pathname.startsWith('/dashboard')) {
return NextResponse.rewrite(new URL('/dashboard/user', request.url))
}
}
NextResponse
NextResponse
API 允許您
redirect
將傳入的請求重新導向到不同的 URLrewrite
透過顯示給定的 URL 來重寫回應- 設定 API 路由、
getServerSideProps
和rewrite
目標的請求標頭 - 設定回應 Cookie
- 設定回應標頭
若要從中介軟體產生回應,您可以
rewrite
到產生回應的路由 (頁面或 Edge API 路由)- 直接傳回
NextResponse
。請參閱產生回應
使用 Cookie
Cookie 是常規標頭。在 Request
上,它們儲存在 Cookie
標頭中。在 Response
上,它們位於 Set-Cookie
標頭中。Next.js 提供了一種方便的方法,可透過 NextRequest
和 NextResponse
上的 cookies
擴充功能來存取和操作這些 Cookie。
- 對於傳入的請求,
cookies
隨附以下方法:get
、getAll
、set
和delete
Cookie。您可以使用has
檢查 Cookie 是否存在,或使用clear
移除所有 Cookie。 - 對於傳出的回應,
cookies
具有以下方法:get
、getAll
、set
和delete
。
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
export function middleware(request: NextRequest) {
// Assume a "Cookie:nextjs=fast" header to be present on the incoming request
// Getting cookies from the request using the `RequestCookies` API
let cookie = request.cookies.get('nextjs')
console.log(cookie) // => { name: 'nextjs', value: 'fast', Path: '/' }
const allCookies = request.cookies.getAll()
console.log(allCookies) // => [{ name: 'nextjs', value: 'fast' }]
request.cookies.has('nextjs') // => true
request.cookies.delete('nextjs')
request.cookies.has('nextjs') // => false
// Setting cookies on the response using the `ResponseCookies` API
const response = NextResponse.next()
response.cookies.set('vercel', 'fast')
response.cookies.set({
name: 'vercel',
value: 'fast',
path: '/',
})
cookie = response.cookies.get('vercel')
console.log(cookie) // => { name: 'vercel', value: 'fast', Path: '/' }
// The outgoing response will have a `Set-Cookie:vercel=fast;path=/` header.
return response
}
設定標頭
您可以使用 NextResponse
API 設定請求和回應標頭(設定請求標頭自 Next.js v13.0.0 起可用)。
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
export function middleware(request: NextRequest) {
// Clone the request headers and set a new header `x-hello-from-middleware1`
const requestHeaders = new Headers(request.headers)
requestHeaders.set('x-hello-from-middleware1', 'hello')
// You can also set request headers in NextResponse.next
const response = NextResponse.next({
request: {
// New request headers
headers: requestHeaders,
},
})
// Set a new response header `x-hello-from-middleware2`
response.headers.set('x-hello-from-middleware2', 'hello')
return response
}
小知識:避免設定大型標頭,因為它可能會根據您的後端 Web 伺服器設定導致 431 Request Header Fields Too Large 錯誤。
CORS
您可以在中介軟體中設定 CORS 標頭以允許跨來源請求,包括簡單 和 預檢 請求。
import { NextRequest, NextResponse } from 'next/server'
const allowedOrigins = ['https://acme.com', 'https://my-app.org']
const corsOptions = {
'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
'Access-Control-Allow-Headers': 'Content-Type, Authorization',
}
export function middleware(request: NextRequest) {
// Check the origin from the request
const origin = request.headers.get('origin') ?? ''
const isAllowedOrigin = allowedOrigins.includes(origin)
// Handle preflighted requests
const isPreflight = request.method === 'OPTIONS'
if (isPreflight) {
const preflightHeaders = {
...(isAllowedOrigin && { 'Access-Control-Allow-Origin': origin }),
...corsOptions,
}
return NextResponse.json({}, { headers: preflightHeaders })
}
// Handle simple requests
const response = NextResponse.next()
if (isAllowedOrigin) {
response.headers.set('Access-Control-Allow-Origin', origin)
}
Object.entries(corsOptions).forEach(([key, value]) => {
response.headers.set(key, value)
})
return response
}
export const config = {
matcher: '/api/:path*',
}
產生回應
您可以透過傳回 Response
或 NextResponse
實例,直接從中介軟體回應。(自 Next.js v13.1.0 起可用)
import type { NextRequest } from 'next/server'
import { isAuthenticated } from '@lib/auth'
// Limit the middleware to paths starting with `/api/`
export const config = {
matcher: '/api/:function*',
}
export function middleware(request: NextRequest) {
// Call our authentication function to check the request
if (!isAuthenticated(request)) {
// Respond with JSON indicating an error message
return Response.json(
{ success: false, message: 'authentication failed' },
{ status: 401 }
)
}
}
waitUntil
和 NextFetchEvent
NextFetchEvent
物件擴充了原生 FetchEvent
物件,並包含 waitUntil()
方法。
waitUntil()
方法接受 Promise 作為引數,並擴展中介軟體的生命週期,直到 Promise 解決。這對於在背景中執行工作很有用。
import { NextResponse } from 'next/server'
import type { NextFetchEvent, NextRequest } from 'next/server'
export function middleware(req: NextRequest, event: NextFetchEvent) {
event.waitUntil(
fetch('https://my-analytics-platform.com', {
method: 'POST',
body: JSON.stringify({ pathname: req.nextUrl.pathname }),
})
)
return NextResponse.next()
}
進階中介軟體標誌
在 Next.js 的 v13.1
中,為中介軟體引入了兩個額外標誌 skipMiddlewareUrlNormalize
和 skipTrailingSlashRedirect
,以處理進階使用案例。
skipTrailingSlashRedirect
停用 Next.js 重新導向,以新增或移除尾部斜線。這允許中介軟體內的自訂處理,以針對某些路徑維護尾部斜線,但對其他路徑則不維護,這可以使增量遷移更容易。
module.exports = {
skipTrailingSlashRedirect: true,
}
const legacyPrefixes = ['/docs', '/blog']
export default async function middleware(req) {
const { pathname } = req.nextUrl
if (legacyPrefixes.some((prefix) => pathname.startsWith(prefix))) {
return NextResponse.next()
}
// apply trailing slash handling
if (
!pathname.endsWith('/') &&
!pathname.match(/((?!\.well-known(?:\/.*)?)(?:[^/]+\/)*[^/]+\.\w+)/)
) {
return NextResponse.redirect(
new URL(`${req.nextUrl.pathname}/`, req.nextUrl)
)
}
}
skipMiddlewareUrlNormalize
允許停用 Next.js 中的 URL 正規化,以使直接訪問和客戶端轉換的處理方式相同。在某些進階情況下,此選項透過使用原始 URL 提供完全控制。
module.exports = {
skipMiddlewareUrlNormalize: true,
}
export default async function middleware(req) {
const { pathname } = req.nextUrl
// GET /_next/data/build-id/hello.json
console.log(pathname)
// with the flag this now /_next/data/build-id/hello.json
// without the flag this would be normalized to /hello
}
單元測試 (實驗性功能)
從 Next.js 15.1 開始,next/experimental/testing/server
套件包含公用程式,可協助單元測試中介軟體檔案。單元測試中介軟體可以協助確保它僅在所需的路徑上執行,並且自訂路由邏輯在程式碼到達生產環境之前按預期工作。
unstable_doesMiddlewareMatch
函式可用於斷言中介軟體是否將針對提供的 URL、標頭和 Cookie 執行。
import { unstable_doesMiddlewareMatch } from 'next/experimental/testing/server'
expect(
unstable_doesMiddlewareMatch({
config,
nextConfig,
url: '/test',
})
).toEqual(false)
也可以測試整個中介軟體函式。
import { isRewrite, getRewrittenUrl } from 'next/experimental/testing/server'
const request = new NextRequest('https://nextjs.dev.org.tw/docs')
const response = await middleware(request)
expect(isRewrite(response)).toEqual(true)
expect(getRewrittenUrl(response)).toEqual('https://other-domain.com/docs')
// getRedirectUrl could also be used if the response were a redirect
執行階段
中介軟體預設為使用 Edge 執行階段。從 v15.2 (canary) 開始,我們實驗性地支援使用 Node.js 執行階段。若要啟用,請將標誌新增至您的 next.config
檔案
import type { NextConfig } from 'next'
const nextConfig: NextConfig = {
experimental: {
nodeMiddleware: true,
},
}
export default nextConfig
然後在您的中介軟體檔案中,在 config
物件中將執行階段設定為 nodejs
export const config = {
runtime: 'nodejs',
}
注意:此功能尚不建議用於生產環境。因此,除非您使用的是 next@canary 版本而不是穩定版本,否則 Next.js 將會擲回錯誤。
版本歷史
這有幫助嗎?