跳到內容

proxy.js

注意middleware 檔案約定已廢棄並已重新命名為 proxy。有關更多詳細資訊,請參閱遷移到代理

proxy.js|ts 檔案用於編寫 代理 並在請求完成之前在伺服器上執行程式碼。然後,根據傳入請求,您可以透過重寫、重定向、修改請求或響應頭,或直接響應來修改響應。

代理在路由渲染之前執行。它對於實現自定義伺服器端邏輯(如身份驗證、日誌記錄或處理重定向)特別有用。

須知:

代理旨在獨立於您的渲染程式碼呼叫,並且在最佳化情況下部署到您的 CDN 以實現快速重定向/重寫處理,您不應嘗試依賴共享模組或全域性變數。

要將資訊從代理傳遞到您的應用程式,請使用請求頭Cookie重寫重定向或 URL。

在專案根目錄或 src 目錄中(如果適用)建立一個 proxy.ts(或 .js)檔案,使其與 pagesapp 位於同一級別。

如果您已自定義 pageExtensions,例如將其設定為 .page.ts.page.js,則相應地將您的檔案命名為 proxy.page.tsproxy.page.js

proxy.ts
import { NextResponse, NextRequest } from 'next/server'
 
// This function can be marked `async` if using `await` inside
export function proxy(request: NextRequest) {
  return NextResponse.redirect(new URL('/home', request.url))
}
 
export const config = {
  matcher: '/about/:path*',
}

匯出

代理函式

檔案必須匯出一個單獨的函式,無論是預設匯出還是命名為 proxy。請注意,不支援來自同一檔案的多個代理。

proxy.js
// Example of default export
export default function proxy(request) {
  // Proxy logic
}

配置物件(可選)

可選地,配置物件可以與代理函式一起匯出。此物件包含匹配器以指定代理適用的路徑。

匹配器

matcher 選項允許您指定代理執行的特定路徑。您可以透過多種方式指定這些路徑

  • 對於單個路徑:直接使用字串定義路徑,例如 '/about'
  • 對於多個路徑:使用陣列列出多個路徑,例如 matcher: ['/about', '/contact'],它將代理應用於 /about/contact
proxy.js
export const config = {
  matcher: ['/about/:path*', '/dashboard/:path*'],
}

此外,matcher 選項支援透過正則表示式進行復雜的路徑規範,例如 matcher: ['/((?!api|_next/static|_next/image|.*\\.png$).*)'],從而能夠精確控制要包含或排除的路徑。

matcher 選項接受一個包含以下鍵的物件陣列

  • source:用於匹配請求路徑的路徑或模式。它可以是用於直接路徑匹配的字串,也可以是用於更復雜匹配的模式。
  • regexp(可選):一個正則表示式字串,用於根據源路徑微調匹配。它提供對要包含或排除的路徑的額外控制。
  • locale(可選):一個布林值,當設定為 false 時,在路徑匹配中忽略基於區域設定的路由。
  • has(可選):根據特定請求元素(如請求頭、查詢引數或 Cookie)的存在來指定條件。
  • missing(可選):側重於某些請求元素(如缺失的請求頭或 Cookie)不存在的條件。
proxy.js
export const config = {
  matcher: [
    {
      source: '/api/*',
      regexp: '^/api/(.*)',
      locale: false,
      has: [
        { type: 'header', key: 'Authorization', value: 'Bearer Token' },
        { type: 'query', key: 'userId', value: '123' },
      ],
      missing: [{ type: 'cookie', key: 'session', value: 'active' }],
    },
  ],
}

配置的匹配器

  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 文件的詳細資訊。

須知:

  • matcher 值必須是常量,以便在構建時進行靜態分析。動態值(例如變數)將被忽略。
  • 為了向後相容,Next.js 始終將 /public 視為 /public/index。因此,/public/:path 的匹配器將匹配。

引數

request

定義代理時,預設匯出函式接受一個引數 request。此引數是 NextRequest 的例項,表示傳入的 HTTP 請求。

proxy.ts
import type { NextRequest } from 'next/server'
 
export function proxy(request: NextRequest) {
  // Proxy logic goes here
}

須知:

  • NextRequest 是一種型別,表示 Next.js 代理中的傳入 HTTP 請求,而 NextResponse 是一個用於操作和傳送 HTTP 響應的類。

NextResponse

NextResponse API 允許您

  • 將傳入請求 redirect(重定向)到不同的 URL
  • 透過顯示給定 URL rewrite(重寫)響應
  • 為 API 路由、getServerSidePropsrewrite 目的地設定請求頭
  • 設定響應 Cookie
  • 設定響應頭

要從代理生成響應,您可以

  1. rewrite 到生成響應的路由(頁面路由處理程式
  2. 直接返回 NextResponse。參見生成響應

須知:對於重定向,您也可以使用 Response.redirect 而不是 NextResponse.redirect

執行順序

代理將針對**專案中的每個路由**呼叫。鑑於此,使用匹配器精確地定位或排除特定路由至關重要。以下是執行順序

  1. next.config.js 中的 headers
  2. next.config.js 中的 redirects
  3. 代理(rewritesredirects 等)
  4. next.config.js 中的 beforeFilesrewrites
  5. 檔案系統路由(public/_next/static/pages/app/ 等)
  6. next.config.js 中的 afterFilesrewrites
  7. 動態路由(/blog/[slug]
  8. next.config.js 中的 fallbackrewrites

執行時

代理預設使用 Node.js 執行時。runtime 配置選項在代理檔案中不可用。在代理中設定 runtime 配置選項將丟擲錯誤。

高階代理標誌

在 Next.js v13.1 中,為代理引入了兩個額外的標誌,skipMiddlewareUrlNormalizeskipTrailingSlashRedirect,以處理高階用例。

skipTrailingSlashRedirect 停用 Next.js 新增或刪除尾部斜槓的重定向。這允許在代理內部進行自定義處理,以保持某些路徑的尾部斜槓而不保持其他路徑,這可以使增量遷移更容易。

next.config.js
module.exports = {
  skipTrailingSlashRedirect: true,
}
proxy.js
const legacyPrefixes = ['/docs', '/blog']
 
export default async function proxy(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 提供完全控制。

next.config.js
module.exports = {
  skipMiddlewareUrlNormalize: true,
}
proxy.js
export default async function proxy(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
}

示例

條件語句

proxy.ts
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
 
export function proxy(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))
  }
}

使用 Cookies

Cookies 是常規的請求頭。在 Request 上,它們儲存在 Cookie 請求頭中。在 Response 上,它們儲存在 Set-Cookie 請求頭中。Next.js 提供了一種便捷的方式,透過 NextRequestNextResponse 上的 cookies 擴充套件來訪問和操作這些 Cookie。

  1. 對於傳入請求,cookies 附帶以下方法:getgetAllsetdelete cookies。您可以使用 has 檢查 Cookie 是否存在,或使用 clear 刪除所有 Cookie。
  2. 對於傳出響應,cookies 具有以下方法:getgetAllsetdelete
proxy.ts
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
 
export function proxy(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
}

設定 Headers

您可以使用 NextResponse API 設定請求和響應頭(從 Next.js v13.0.0 開始支援設定*請求*頭)。

proxy.ts
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
 
export function proxy(request: NextRequest) {
  // Clone the request headers and set a new header `x-hello-from-proxy1`
  const requestHeaders = new Headers(request.headers)
  requestHeaders.set('x-hello-from-proxy1', '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-proxy2`
  response.headers.set('x-hello-from-proxy2', 'hello')
  return response
}

請注意,此程式碼片段使用

  • NextResponse.next({ request: { headers: requestHeaders } }) 使 requestHeaders 可用於上游
  • 而非 NextResponse.next({ headers: requestHeaders }),後者使 requestHeaders 可用於客戶端

代理中的 NextResponse 請求頭中瞭解更多。

須知:避免設定過大的請求頭,否則可能會根據您的後端 Web 伺服器配置導致 431 請求頭欄位過大 錯誤。

CORS

您可以在代理中設定 CORS 頭,以允許跨域請求,包括 簡單請求預檢請求

proxy.ts
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 proxy(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*',
}

須知: 您可以在路由處理程式中為單個路由配置 CORS 頭。

生成響應

您可以直接從代理返回 ResponseNextResponse 例項來響應。(此功能自 Next.js v13.1.0 起可用)

proxy.ts
import type { NextRequest } from 'next/server'
import { isAuthenticated } from '@lib/auth'
 
// Limit the proxy to paths starting with `/api/`
export const config = {
  matcher: '/api/:function*',
}
 
export function proxy(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 }
    )
  }
}

負向匹配

matcher 配置允許使用完整的正則表示式,因此支援負向前瞻或字元匹配等匹配。負向前瞻的示例(匹配除特定路徑之外的所有路徑)可見此處

proxy.js
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).*)',
  ],
}

您還可以透過使用 missinghas 陣列,或兩者的組合來繞過某些請求的代理

proxy.js
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' }],
    },
  ],
}

waitUntilNextFetchEvent

NextFetchEvent 物件擴充套件了原生的 FetchEvent 物件,幷包含 waitUntil() 方法。

waitUntil() 方法接受一個 Promise 作為引數,並延長代理的生命週期,直到 Promise 解決。這對於在後臺執行工作非常有用。

proxy.ts
import { NextResponse } from 'next/server'
import type { NextFetchEvent, NextRequest } from 'next/server'
 
export function proxy(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 15.1 開始,next/experimental/testing/server 包包含用於幫助單元測試代理檔案的實用程式。單元測試代理有助於確保它僅在所需路徑上執行,並且自定義路由邏輯在程式碼投入生產之前按預期工作。

unstable_doesProxyMatch 函式可用於斷言代理是否將針對提供的 URL、請求頭和 Cookie 執行。

import { unstable_doesProxyMatch } from 'next/experimental/testing/server'
 
expect(
  unstable_doesProxyMatch({
    config,
    nextConfig,
    url: '/test',
  })
).toEqual(false)

整個代理函式也可以被測試。

import { isRewrite, getRewrittenUrl } from 'next/experimental/testing/server'
 
const request = new NextRequest('https://nextjs.react.com.tw/docs')
const response = await proxy(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

平臺支援

部署選項支援
Node.js 伺服器
Docker 容器
靜態匯出
介面卡平臺特定

瞭解如何在自託管 Next.js 時配置代理

遷移到代理

為什麼改變

重新命名 middleware 的原因是,“中介軟體”一詞常常與 Express.js 中介軟體混淆,導致對其目的的誤解。此外,中介軟體功能強大,因此可能會鼓勵使用;但是,此功能建議作為最後手段使用。

Next.js 正在向前發展,提供具有更好人體工程學的 API,以便開發人員無需中介軟體即可實現其目標。這就是重新命名 middleware 的原因。

為什麼叫“代理”

“代理”這個名稱闡明瞭中介軟體的功能。“代理”一詞意味著它在應用程式前面有一個網路邊界,這正是中介軟體的行為。此外,中介軟體預設在邊緣執行時執行,它可以更接近客戶端執行,與應用程式的區域分離。這些行為與“代理”一詞更好地契合,並提供了更清晰的功能目的。

如何遷移

我們建議使用者避免依賴中介軟體,除非別無選擇。我們的目標是為他們提供具有更好人體工程學的 API,以便他們可以在沒有中介軟體的情況下實現目標。

“中介軟體”一詞經常讓使用者與 Express.js 中介軟體混淆,這可能會鼓勵濫用。為了澄清我們的方向,我們將檔案約定重新命名為“代理”。這突出表明我們正在擺脫中介軟體,分解其過載功能,並使代理的目的明確。

Next.js 提供了一個 codemod,用於從 middleware.ts 遷移到 proxy.ts。您可以執行以下命令進行遷移

npx @next/codemod@canary middleware-to-proxy .

codemod 將把檔案和函式名稱從 middleware 重新命名為 proxy

// middleware.ts -> proxy.ts
 
- export function middleware() {
+ export function proxy() {

版本歷史

版本更改
v16.0.0中介軟體已棄用並重命名為代理
v15.5.0中介軟體現在可以使用 Node.js 執行時(穩定)
v15.2.0中介軟體現在可以使用 Node.js 執行時(實驗性)
v13.1.0添加了高階中介軟體標誌
v13.0.0中介軟體可以修改請求頭、響應頭併發送響應
v12.2.0中介軟體已穩定,請參閱升級指南
v12.0.9在 Edge Runtime 中強制使用絕對 URL(PR
v12.0.0添加了中介軟體(Beta)