ミドルウェアアップグレードガイド
ミドルウェアの一般提供 (GA) に向けた改善作業の一環として、皆様からのフィードバックに基づき、ミドルウェア API (およびアプリケーションでのミドルウェアの定義方法) に変更を加えました。
このアップグレードガイドでは、変更内容、変更理由、既存のミドルウェアを新しい API に移行する方法について説明します。このガイドは、以下の条件に当てはまる Next.js 開発者を対象としています。
- 現在ベータ版の 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
) にアップグレードする場合は、このアップグレードガイドに従ってミドルウェアを更新する必要があります。
破壊的変更
- ネストされたミドルウェアなし
- レスポンスボディなし
- Cookies API の改訂
- 新しいユーザーエージェントヘルパー
- ページ一致データはもうありません
- 内部 Next.js リクエストでミドルウェアを実行する
ネストされたミドルウェアなし
変更の概要
以前は、任意のレベルの pages
ディレクトリの下に _middleware.ts
ファイルを作成できました。ミドルウェアの実行は、作成されたファイルパスに基づいていました。
お客様からのフィードバックに基づき、この API を単一のルートミドルウェアに置き換えました。これにより、以下の改善が実現しました。
- より高速な実行と低レイテンシ: ネストされたミドルウェアでは、1つのリクエストで複数のミドルウェア関数が呼び出される可能性がありました。単一のミドルウェアは単一の関数実行を意味し、より効率的です。
- 低コスト: ミドルウェアの使用量は呼び出し回数に応じて課金されます。ネストされたミドルウェアを使用すると、1つのリクエストで複数のミドルウェア関数が呼び出される可能性があり、リクエストごとに複数のミドルウェア料金が発生します。単一のミドルウェアは、リクエストごとに1回の呼び出しを意味し、費用対効果が高くなります。
- ミドルウェアはルート以外の条件でも簡単にフィルタリングできます: ネストされたミドルウェアでは、ミドルウェアファイルは
pages
ディレクトリに配置され、リクエストパスに基づいてミドルウェアが実行されました。単一のルートミドルウェアに移行することで、リクエストパスに基づいてコードを実行できますが、cookie
やリクエストヘッダーの存在など、他の条件に基づいてミドルウェアをより便利に実行できるようになりました。 - 決定論的な実行順序: ネストされたミドルウェアでは、1つのリクエストが複数のミドルウェア関数に一致する可能性がありました。たとえば、
/dashboard/users/*
へのリクエストは、/dashboard/users/_middleware.ts
と/dashboard/_middleware.js
の両方で定義されたミドルウェアを呼び出します。しかし、実行順序を理解するのは困難です。単一のルートミドルウェアに移行することで、実行順序がより明確に定義されます。 - Next.jsレイアウト(RFC)をサポート: 単一のルートミドルウェアに移行することで、Next.jsの新しいレイアウト(RFC)のサポートに役立ちます。
アップグレード方法
アプリケーションには、1つのミドルウェアファイルを宣言する必要があります。これは、pages
ディレクトリの隣に配置し、_
プレフィックスなしで名前を付ける必要があります。ミドルウェアファイルの拡張子は、.ts
または.js
のいずれかを使用できます。
ミドルウェアは、アプリのすべてのルートに対して呼び出され、カスタムマッチャーを使用して一致フィルタを定義できます。以下は、/about/*
と/dashboard/:path*
に対してトリガーされるミドルウェアの例です。カスタムマッチャーは、エクスポートされた設定オブジェクトで定義されます。
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*'],
}
マッチャー設定では、完全な正規表現も使用できるため、否定先読みや文字一致などのマッチングがサポートされます。特定のパスを除くすべてに一致する否定先読みの例を次に示します。
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).*)',
],
}
設定オプションはすべてのリクエストで呼び出されないため推奨されますが、条件ステートメントを使用して、特定のパスに一致する場合にのみミドルウェアを実行することもできます。条件式を使用する利点の1つは、ミドルウェアが実行される場合の明示的な順序を定義できることです。次の例は、以前にネストされていた2つのミドルウェアをマージする方法を示しています。
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
、または受信リクエストの変更(例:Cookieの設定)にのみ使用されるようになります。
次のパターンは機能しなくなります。
new Response('a text value')
new Response(streamOrBuffer)
new Response(JSON.stringify(obj), { headers: 'application/json' })
NextResponse.json()
アップグレード方法
ミドルウェアが応答に使用される場合(認証など)、認証エラー、ログインフォーム、またはAPIルートを表示するページにrewrite
/ redirect
を使用するように移行する必要があります。
変更前 pages/_middleware.tsimport { 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()
}
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',
})
}
Cookie APIの改訂 変更の概要 追加 削除 cookies.set
cookie
cookies.delete
clearCookie
cookies.getWithOptions
cookies
追加 | 削除 |
---|---|
cookies.set | cookie |
cookies.delete | clearCookie |
cookies.getWithOptions | cookies |