中介軟體 (Middleware)

中介軟體 (Middleware) 允許您在請求完成前執行程式碼。然後根據傳入的請求,您可以透過重寫、重新導向、修改請求或回應標頭,或直接回應來修改回應。

中介軟體會在快取內容和路由匹配之前執行。詳情請參閱 路徑匹配

使用情境

中介軟體 (Middleware) 適用於以下常見場景:

  • 讀取傳入請求的部分內容後快速重新導向
  • 根據 A/B 測試或實驗重寫到不同頁面
  • 修改所有頁面或部分頁面的標頭

中介軟體 (Middleware) 適合用於:

  • 緩慢的資料獲取
  • 會話管理

慣例

在專案根目錄中使用 middleware.ts (或 .js) 檔案來定義中介軟體 (Middleware)。例如,與 pagesapp 同層級,或在適用的情況下放在 src 目錄內。

注意:雖然每個專案僅支援一個 middleware.ts 檔案,但您仍可以模組化組織中介軟體邏輯。將中介軟體功能拆分到獨立的 .ts.js 檔案中,並在主要的 middleware.ts 檔案中導入它們。這樣可以更清晰地管理特定路由的中介軟體,並在 middleware.ts 中集中控制。透過強制使用單一中介軟體檔案,簡化了配置,避免了潛在衝突,並透過避免多層中介軟體來優化效能。

範例

import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'

// 如果內部使用 `await`,此函式可標記為 `async`
export function middleware(request: NextRequest) {
  return NextResponse.redirect(new URL('/home', request.url))
}

// 詳情請參閱下方的「路徑匹配」
export const config = {
  matcher: '/about/:path*',
}

路徑匹配

中介軟體 (Middleware) 會為 專案中的每個路由 調用。因此,使用匹配器 (matcher) 精確定位或排除特定路由至關重要。以下是執行順序:

  1. next.config.js 中的 headers
  2. next.config.js 中的 redirects
  3. 中介軟體 (Middleware) (rewritesredirects 等)
  4. next.config.js 中的 beforeFiles (rewrites)
  5. 檔案系統路由 (public/_next/static/pages/app/ 等)
  6. next.config.js 中的 afterFiles (rewrites)
  7. 動態路由 (/blog/[slug])
  8. next.config.js 中的 fallback (rewrites)

有兩種方式可以定義中介軟體 (Middleware) 將執行的路徑:

  1. 自訂匹配器配置
  2. 條件語句

匹配器 (Matcher)

matcher 允許您過濾中介軟體 (Middleware) 以在特定路徑上執行。

middleware.js
export const config = {
  matcher: '/about/:path*',
}

您可以使用陣列語法匹配單一路徑或多個路徑:

middleware.js
export const config = {
  matcher: ['/about/:path*', '/dashboard/:path*'],
}

matcher 配置支援完整的正則表達式,因此支援如負向先行斷言或字元匹配等功能。以下是一個負向先行斷言的範例,用於匹配除特定路徑外的所有路徑:

middleware.js
export const config = {
  matcher: [
    /*
     * 匹配所有請求路徑,除了以下開頭的路徑:
     * - api (API 路由)
     * - _next/static (靜態檔案)
     * - _next/image (圖片優化檔案)
     * - favicon.ico、sitemap.xml、robots.txt (中繼資料檔案)
     */
    '/((?!api|_next/static|_next/image|favicon.ico|sitemap.xml|robots.txt).*)',
  ],
}

您也可以透過使用 missinghas 陣列,或兩者的組合,來繞過某些請求的中介軟體 (Middleware):

middleware.js
export const config = {
  matcher: [
    /*
     * 匹配所有請求路徑,除了以下開頭的路徑:
     * - api (API 路由)
     * - _next/static (靜態檔案)
     * - _next/image (圖片優化檔案)
     * - favicon.ico、sitemap.xml、robots.txt (中繼資料檔案)
     */
    {
      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 值必須是常數,以便在構建時進行靜態分析。動態值(如變數)將被忽略。

配置的匹配器:

  1. 必須以 / 開頭
  2. 可以包含命名參數:/about/:path 匹配 /about/a/about/b,但不匹配 /about/a/c
  3. 可以在命名參數上使用修飾符(以 : 開頭):/about/:path* 匹配 /about/a/b/c,因為 * 表示 零個或多個? 表示 零個或一個+ 表示 一個或多個
  4. 可以使用括號內的正則表達式:/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 傳入的請求到不同的 URL
  • rewrite 回應以顯示給定的 URL
  • 為 API 路由、getServerSidePropsrewrite 目的地設定請求標頭
  • 設定回應的 cookies
  • 設定回應標頭

要從中介軟體 (Middleware) 產生回應,您可以:

  1. rewrite 到一個產生回應的路由 (頁面路由處理器)
  2. 直接返回 NextResponse。請參閱 產生回應

使用 Cookies

Cookies 是常規的標頭。在 Request 中,它們存儲在 Cookie 標頭中。在 Response 中,它們存儲在 Set-Cookie 標頭中。Next.js 透過 NextRequestNextResponse 上的 cookies 擴展提供了一種方便的方式來存取和操作這些 cookies。

  1. 對於傳入的請求,cookies 提供以下方法:getgetAllsetdelete cookies。您可以使用 has 檢查 cookie 是否存在,或使用 clear 刪除所有 cookies。
  2. 對於傳出的回應,cookies 提供以下方法:getgetAllsetdelete
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'

export function middleware(request: NextRequest) {
  // 假設傳入請求中存在 "Cookie:nextjs=fast" 標頭
  // 使用 `RequestCookies` API 從請求中獲取 cookies
  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

  // 使用 `ResponseCookies` API 在回應上設定 cookies
  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: '/' }
  // 傳出的回應將包含 `Set-Cookie:vercel=fast;path=/` 標頭。

  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) {
  // 克隆請求標頭並設定新標頭 `x-hello-from-middleware1`
  const requestHeaders = new Headers(request.headers)
  requestHeaders.set('x-hello-from-middleware1', 'hello')

  // 您也可以在 NextResponse.next 中設定請求標頭
  const response = NextResponse.next({
    request: {
      // 新的請求標頭
      headers: requestHeaders,
    },
  })

  // 設定新的回應標頭 `x-hello-from-middleware2`
  response.headers.set('x-hello-from-middleware2', 'hello')
  return response
}

須知:避免設定過大的標頭,因為根據後端網頁伺服器的配置,可能會導致 431 請求標頭欄位過大 錯誤。

CORS

您可以在中介軟體 (Middleware) 中設定 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) {
  // 檢查請求中的來源
  const origin = request.headers.get('origin') ?? ''
  const isAllowedOrigin = allowedOrigins.includes(origin)

  // 處理預檢請求
  const isPreflight = request.method === 'OPTIONS'

  if (isPreflight) {
    const preflightHeaders = {
      ...(isAllowedOrigin && { 'Access-Control-Allow-Origin': origin }),
      ...corsOptions,
    }
    return NextResponse.json({}, { headers: preflightHeaders })
  }

  // 處理簡單請求
  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*',
}

須知:您可以在 路由處理器 中為個別路由配置 CORS 標頭。

產生回應

您可以直接從中介軟體 (Middleware) 回傳 ResponseNextResponse 實例來產生回應。(此功能自 Next.js v13.1.0 起可用)

import type { NextRequest } from 'next/server'
import { isAuthenticated } from '@lib/auth'

// 將中介軟體限制在 `/api/` 開頭的路徑
export const config = {
  matcher: '/api/:function*',
}

export function middleware(request: NextRequest) {
  // 呼叫驗證函式檢查請求
  if (!isAuthenticated(request)) {
    // 回傳 JSON 格式的錯誤訊息
    return Response.json(
      { success: false, message: 'authentication failed' },
      { status: 401 }
    )
  }
}

waitUntilNextFetchEvent

NextFetchEvent 物件繼承了原生的 FetchEvent 物件,並包含 waitUntil() 方法。

waitUntil() 方法接收一個 Promise 作為參數,並延長中介軟體的生命週期直到該 Promise 完成。這對於在背景執行工作非常有用。

middleware.ts
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 版本中,新增了兩個中介軟體旗標 skipMiddlewareUrlNormalizeskipTrailingSlashRedirect 來處理進階使用情境。

skipTrailingSlashRedirect 會停用 Next.js 對於新增或移除尾部斜線的自動重新導向。這允許在中介軟體中自訂處理方式,針對某些路徑保留尾部斜線而其他路徑則不保留,這對於漸進式遷移特別有用。

next.config.js
module.exports = {
  skipTrailingSlashRedirect: true,
}
middleware.js
const legacyPrefixes = ['/docs', '/blog']

export default async function middleware(req) {
  const { pathname } = req.nextUrl

  if (legacyPrefixes.some((prefix) => pathname.startsWith(prefix))) {
    return NextResponse.next()
  }

  // 套用尾部斜線處理
  if (
    !pathname.endsWith('/') &&
    !pathname.match(/((?!\.well-known(?:\/.*)?)(?:[^/]+\/)*[^/]+\.\w+)/)
  ) {
    return NextResponse.redirect(
      new URL(`${req.nextUrl.pathname}/`, req.nextUrl)
    )
  }
}

skipMiddlewareUrlNormalize 允許停用 Next.js 的 URL 標準化處理,讓直接訪問與客戶端轉換的行為保持一致。在某些進階情境中,此選項可透過使用原始 URL 來提供完整控制權。

next.config.js
module.exports = {
  skipMiddlewareUrlNormalize: true,
}
middleware.js
export default async function middleware(req) {
  const { pathname } = req.nextUrl

  // GET /_next/data/build-id/hello.json

  console.log(pathname)
  // 啟用旗標後會顯示 /_next/data/build-id/hello.json
  // 未啟用旗標時會被標準化為 /hello
}

單元測試 (實驗性功能)

自 Next.js 15.1 起,next/experimental/testing/server 套件包含了協助測試中介軟體檔案的實用工具。單元測試中介軟體有助於確保它僅在預期的路徑上執行,且自訂路由邏輯在程式碼上線前能如預期運作。

unstable_doesMiddlewareMatch 函式可用於斷言中介軟體是否會針對提供的 URL、標頭和 cookies 執行。

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.org/docs')
const response = await middleware(request)
expect(isRewrite(response)).toEqual(true)
expect(getRewrittenUrl(response)).toEqual('https://other-domain.com/docs')
// 若回應是重新導向,也可以使用 getRedirectUrl

執行環境

中介軟體預設使用 Edge 執行環境 (Edge Runtime)。自 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 將會拋出錯誤。

平台支援

部署選項是否支援
Node.js 伺服器
Docker 容器
靜態匯出
轉接器 (Adapters)依平台而定

瞭解如何在使用 Next.js 自架時設定中介軟體

版本歷史

版本變更
v15.2.0中介軟體現在可使用 Node.js 執行環境 (實驗性功能)
v13.1.0新增進階中介軟體旗標
v13.0.0中介軟體可修改請求標頭、回應標頭及傳送回應
v12.2.0中介軟體功能穩定,請參閱升級指南
v12.0.9在 Edge 執行環境中強制使用絕對 URL (PR)
v12.0.0新增中介軟體 (Beta 版)

On this page