跳到內容
文件錯誤中介軟體升級指南

中介軟體升級指南

為了改進中介軟體以達到正式發佈 (GA) 的目標,我們根據您的意見回饋,對中介軟體 API(以及您在應用程式中定義中介軟體的方式)進行了一些變更。

本升級指南將協助您瞭解這些變更、變更原因,以及如何將現有的中介軟體遷移到新的 API。本指南適用於 Next.js 開發人員:

  • 目前使用 beta 版 Next.js 中介軟體功能
  • 選擇升級到下一個 Next.js 穩定版本 (v12.2)

您可以使用最新版本立即開始升級您的中介軟體使用方式 (npm i next@latest)。

注意:本指南中描述的這些變更包含在 Next.js 12.2 中。您可以保留目前的網站結構,包括巢狀中介軟體,直到您移至 12.2(或 Next.js 的 canary 組建)。

如果您已設定 ESLint,您需要執行 npm i eslint-config-next@latest --save-dev 來升級您的 ESLint 設定,以確保使用的版本與 Next.js 版本相同。您可能也需要重新啟動 VSCode 才能使變更生效。

在 Vercel 上使用 Next.js 中介軟體

如果您在 Vercel 上使用 Next.js,您現有的使用中介軟體的部署將繼續運作,您可以繼續使用中介軟體部署您的網站。當您將網站升級到下一個 Next.js 穩定版本 (v12.2) 時,您需要遵循本升級指南來更新您的中介軟體。

重大變更

  1. 無巢狀中介軟體
  2. 無回應主體
  3. Cookies API 已修改
  4. 新的 User-Agent 輔助程式
  5. 不再有頁面比對資料
  6. 在內部 Next.js 請求上執行中介軟體

無巢狀中介軟體

變更摘要

  • 在您的 pages 資料夾旁邊定義單一中介軟體檔案
  • 無需在檔案名稱前加上底線
  • 可以使用自訂比對器,透過匯出的 config 物件來定義比對路由

說明

先前,您可以在 pages 目錄下的任何層級建立 _middleware.ts 檔案。中介軟體的執行是根據建立檔案的路徑。

根據客戶意見回饋,我們已將此 API 替換為單一根中介軟體,此中介軟體提供以下改進:

  • 更快的執行速度和更低的延遲:使用巢狀中介軟體,單一請求可能會調用多個中介軟體函式。單一中介軟體表示單一函式執行,效率更高。
  • 成本更低:中介軟體的使用量是按調用次數計費。使用巢狀中介軟體,單一請求可能會調用多個中介軟體函式,表示每個請求會產生多個中介軟體費用。單一中介軟體表示每個請求單次調用,更符合成本效益。
  • 中介軟體可以方便地篩選路由以外的事物:使用巢狀中介軟體,中介軟體檔案位於 pages 目錄中,中介軟體是根據請求路徑執行的。透過移至單一根中介軟體,您仍然可以根據請求路徑執行程式碼,但現在您可以更方便地根據其他條件(例如 cookies 或請求標頭是否存在)執行中介軟體。
  • 確定性的執行順序:使用巢狀中介軟體,單一請求可能會比對多個中介軟體函式。例如,對 /dashboard/users/* 的請求會調用在 /dashboard/users/_middleware.ts/dashboard/_middleware.js 中定義的中介軟體。但是,執行順序很難推斷。移至單一根中介軟體更明確地定義了執行順序。
  • 支援 Next.js 版面配置 (RFC):移至單一根中介軟體有助於支援新的 Next.js 中的版面配置 (RFC)

如何升級

您應該在應用程式中宣告單一中介軟體檔案,該檔案應位於 pages 目錄旁邊,並且命名時不帶 _ 字首。您的中介軟體檔案仍然可以具有 .ts.js 副檔名。

中介軟體將針對應用程式中的每個路由調用,並且可以使用自訂比對器來定義比對篩選器。以下範例是一個中介軟體,它會針對 /about/*/dashboard/:path* 觸發,自訂比對器是在匯出的 config 物件中定義的

middleware.ts
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
 
export function middleware(request: NextRequest) {
  return NextResponse.rewrite(new URL('/about-2', request.url))
}
 
// Supports both a single string value or an array of matchers
export const config = {
  matcher: ['/about/:path*', '/dashboard/:path*'],
}

比對器 config 也允許完整的 regex,因此支援負向先行斷言或字元比對等比對方式。負向先行斷言的比對範例(比對除特定路徑以外的所有路徑)可以在此處看到

middleware.ts
export const config = {
  matcher: [
    /*
     * Match all request paths except for the ones starting with:
     * - api (API routes)
     * - _next/static (static files)
     * - favicon.ico (favicon file)
     */
    '/((?!api|_next/static|favicon.ico).*)',
  ],
}

雖然 config 選項是首選,因為它不會在每個請求上調用,但您也可以使用條件陳述式,僅在符合特定路徑時執行中介軟體。使用條件陳述式的一個優點是定義中介軟體執行時的明確順序。以下範例說明如何合併兩個先前巢狀的中介軟體

middleware.ts
import type { NextRequest } from 'next/server'
 
export function middleware(request: NextRequest) {
  if (request.nextUrl.pathname.startsWith('/about')) {
    // This logic is only applied to /about
  }
 
  if (request.nextUrl.pathname.startsWith('/dashboard')) {
    // This logic is only applied to /dashboard
  }
}

無回應主體

變更摘要

  • 中介軟體不再能產生回應主體
  • 如果您的中介軟體確實回應了主體,則會擲回執行階段錯誤
  • 遷移為使用 rewrite/redirect 來處理回應的頁面/API

說明

為了尊重用戶端和伺服器端導航的差異,並協助確保開發人員不會建置不安全的中介軟體,我們正在移除在中介軟體中傳送回應主體的功能。這可確保中介軟體僅用於 rewriteredirect 或修改傳入的請求(例如 設定 cookies)。

以下模式將不再運作

new Response('a text value')
new Response(streamOrBuffer)
new Response(JSON.stringify(obj), { headers: 'application/json' })
NextResponse.json()

如何升級

對於使用中介軟體來回應(例如授權)的情況,您應該遷移為使用 rewrite/redirect 到顯示授權錯誤、登入表單或 API 路由的頁面。

之前

pages/_middleware.ts
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
import { isAuthValid } from './lib/auth'
 
export function middleware(request: NextRequest) {
  // Example function to validate auth
  if (isAuthValid(request)) {
    return NextResponse.next()
  }
 
  return NextResponse.json({ message: 'Auth required' }, { status: 401 })
}

之後

middleware.ts
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
import { isAuthValid } from './lib/auth'
 
export function middleware(request: NextRequest) {
  // Example function to validate auth
  if (isAuthValid(request)) {
    return NextResponse.next()
  }
 
  const loginUrl = new URL('/login', request.url)
  loginUrl.searchParams.set('from', request.nextUrl.pathname)
 
  return NextResponse.redirect(loginUrl)
}

Edge API 路由

如果您先前使用中介軟體將標頭轉發到外部 API,您現在可以使用 Edge API 路由

pages/api/proxy.ts
import { type NextRequest } from 'next/server'
 
export const config = {
  runtime: 'edge',
}
 
export default async function handler(req: NextRequest) {
  const authorization = req.cookies.get('authorization')
  return fetch('https://backend-api.com/api/protected', {
    method: req.method,
    headers: {
      authorization,
    },
    redirect: 'manual',
  })
}

Cookies API 已修改

變更摘要

已新增已移除
cookies.setcookie
cookies.deleteclearCookie
cookies.getWithOptionscookies

說明

根據 beta 版意見回饋,我們正在變更 NextRequestNextResponse 中的 Cookies API,使其更符合 get/set 模型。Cookies API 擴充了 Map,包括 entriesvalues 等方法。

如何升級

NextResponse 現在有一個具有 cookies 實例,包含:

  • cookies.delete
  • cookies.set
  • cookies.getWithOptions

以及 Map 中的其他擴充方法。

之前

pages/_middleware.ts
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
 
export function middleware(request: NextRequest) {
  // create an instance of the class to access the public methods. This uses `next()`,
  // you could use `redirect()` or `rewrite()` as well
  let response = NextResponse.next()
  // get the cookies from the request
  let cookieFromRequest = request.cookies['my-cookie']
  // set the `cookie`
  response.cookie('hello', 'world')
  // set the `cookie` with options
  const cookieWithOptions = response.cookie('hello', 'world', {
    path: '/',
    maxAge: 1000 * 60 * 60 * 24 * 7,
    httpOnly: true,
    sameSite: 'strict',
    domain: 'example.com',
  })
  // clear the `cookie`
  response.clearCookie('hello')
 
  return response
}

之後

middleware.ts
export function middleware() {
  const response = new NextResponse()
 
  // set a cookie
  response.cookies.set('vercel', 'fast')
 
  // set another cookie with options
  response.cookies.set('nextjs', 'awesome', { path: '/test' })
 
  // get all the details of a cookie
  const { value, ...options } = response.cookies.getWithOptions('vercel')
  console.log(value) // => 'fast'
  console.log(options) // => { name: 'vercel', Path: '/test' }
 
  // deleting a cookie will mark it as expired
  response.cookies.delete('vercel')
 
  return response
}

新的 User-Agent 輔助程式

變更摘要

  • 使用者代理程式不再於請求物件上提供
  • 我們新增了一個新的 userAgent 輔助程式,以減少中介軟體大小 17kb

說明

為了協助減少中介軟體的大小,我們已從請求物件中擷取使用者代理程式,並建立了一個新的輔助程式 userAgent

輔助程式是從 next/server 匯入的,可讓您選擇加入使用使用者代理程式。輔助程式可讓您存取與請求物件提供的相同屬性。

如何升級

  • next/server 匯入 userAgent 輔助程式
  • 解構您需要使用的屬性

之前

pages/_middleware.ts
import { NextRequest, NextResponse } from 'next/server'
 
export function middleware(request: NextRequest) {
  const url = request.nextUrl
  const viewport = request.ua.device.type === 'mobile' ? 'mobile' : 'desktop'
  url.searchParams.set('viewport', viewport)
  return NextResponse.rewrite(url)
}

之後

middleware.ts
import { NextRequest, NextResponse, userAgent } from 'next/server'
 
export function middleware(request: NextRequest) {
  const url = request.nextUrl
  const { device } = userAgent(request)
  const viewport = device.type === 'mobile' ? 'mobile' : 'desktop'
  url.searchParams.set('viewport', viewport)
  return NextResponse.rewrite(url)
}

不再有頁面比對資料

變更摘要

  • 使用 URLPattern 來檢查是否針對特定頁面比對調用了中介軟體

說明

目前,中介軟體會根據 Next.js 路由資訊清單(內部設定)估計您是否正在提供頁面的資源。此值會透過 request.page 呈現。

為了使頁面和資源比對更準確,我們現在正在使用網路標準 URLPattern API。

如何升級

使用 URLPattern 來檢查是否針對特定頁面比對調用了中介軟體。

之前

pages/_middleware.ts
import { NextResponse } from 'next/server'
import type { NextRequest, NextFetchEvent } from 'next/server'
 
export function middleware(request: NextRequest, event: NextFetchEvent) {
  const { params } = event.request.page
  const { locale, slug } = params
 
  if (locale && slug) {
    const { search, protocol, host } = request.nextUrl
    const url = new URL(`${protocol}//${locale}.${host}/${slug}${search}`)
    return NextResponse.redirect(url)
  }
}

之後

middleware.ts
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
 
const PATTERNS = [
  [
    new URLPattern({ pathname: '/:locale/:slug' }),
    ({ pathname }) => pathname.groups,
  ],
]
 
const params = (url) => {
  const input = url.split('?')[0]
  let result = {}
 
  for (const [pattern, handler] of PATTERNS) {
    const patternResult = pattern.exec(input)
    if (patternResult !== null && 'pathname' in patternResult) {
      result = handler(patternResult)
      break
    }
  }
  return result
}
 
export function middleware(request: NextRequest) {
  const { locale, slug } = params(request.url)
 
  if (locale && slug) {
    const { search, protocol, host } = request.nextUrl
    const url = new URL(`${protocol}//${locale}.${host}/${slug}${search}`)
    return NextResponse.redirect(url)
  }
}

在內部 Next.js 請求上執行中介軟體

變更摘要

  • 中介軟體將針對所有請求執行,包括 _next

說明

在 Next.js v12.2 之前,中介軟體不會針對 _next 請求執行。

對於使用中介軟體進行授權的情況,您應該遷移為使用 rewrite/redirect 到顯示授權錯誤、登入表單或 API 路由的頁面。

請參閱 無回應主體,以取得如何遷移為使用 rewrite/redirect 的範例。