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

中介軟體升級指南

我們致力於改進中介軟體以使其達到正式版本 (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. 新的使用者代理程式輔助工具
  5. 不再有頁面匹配資料
  6. 在內部 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*'],
}

匹配器設定也允許完整的正規表達式,因此支援像否定前瞻斷言或字元匹配等匹配方式。這裡可以看到一個使用否定前瞻斷言來匹配除特定路徑之外所有路徑的範例。

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
  }
}

無回應主體

變更摘要 說明 設定 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)
}

邊緣 API 路由

如果您先前使用中介軟體將標頭轉發到外部 API,現在您可以使用邊緣 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

說明 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
}

新的使用者代理程式輔助工具

變更摘要 說明 如何升級

  • 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 來檢查中間件是否正在針對特定頁面匹配進行調用。

說明 如何升級

使用 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 請求上執行中間件

變更摘要 說明 無回應主體,以了解如何遷移到使用 rewrite/redirect 的範例。