中介軟體升級指南
為了改進中介軟體以達到正式發佈 (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
) 時,您需要遵循本升級指南來更新您的中介軟體。
重大變更
無巢狀中介軟體
變更摘要
- 在您的
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 物件中定義的
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,因此支援負向先行斷言或字元比對等比對方式。負向先行斷言的比對範例(比對除特定路徑以外的所有路徑)可以在此處看到
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 選項是首選,因為它不會在每個請求上調用,但您也可以使用條件陳述式,僅在符合特定路徑時執行中介軟體。使用條件陳述式的一個優點是定義中介軟體執行時的明確順序。以下範例說明如何合併兩個先前巢狀的中介軟體
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
說明
為了尊重用戶端和伺服器端導航的差異,並協助確保開發人員不會建置不安全的中介軟體,我們正在移除在中介軟體中傳送回應主體的功能。這可確保中介軟體僅用於 rewrite
、redirect
或修改傳入的請求(例如 設定 cookies)。
以下模式將不再運作
new Response('a text value')
new Response(streamOrBuffer)
new Response(JSON.stringify(obj), { headers: 'application/json' })
NextResponse.json()
如何升級
對於使用中介軟體來回應(例如授權)的情況,您應該遷移為使用 rewrite
/redirect
到顯示授權錯誤、登入表單或 API 路由的頁面。
之前
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 })
}
之後
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 路由
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.set | cookie |
cookies.delete | clearCookie |
cookies.getWithOptions | cookies |
說明
根據 beta 版意見回饋,我們正在變更 NextRequest
和 NextResponse
中的 Cookies API,使其更符合 get
/set
模型。Cookies
API 擴充了 Map,包括 entries 和 values 等方法。
如何升級
NextResponse
現在有一個具有 cookies
實例,包含:
cookies.delete
cookies.set
cookies.getWithOptions
以及 Map
中的其他擴充方法。
之前
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
}
之後
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
輔助程式 - 解構您需要使用的屬性
之前
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)
}
之後
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
來檢查是否針對特定頁面比對調用了中介軟體。
之前
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)
}
}
之後
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
的範例。
這有幫助嗎?