ミドルウェアアップグレードガイド
GA (General Availability) 向けにミドルウェアの改善を進めるにあたり、皆様からのフィードバックに基づいて、ミドルウェアの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
) にアップグレードする際には、このアップグレードガイドに従ってミドルウェアを更新する必要があります。
破壊的変更
ネストされたミドルウェアの廃止
変更点の概要
pages
フォルダの横に単一のミドルウェアファイルを定義する- ファイルにアンダースコアを付ける必要なし
- エクスポートされた設定オブジェクトを使用して、マッチングルートを定義するためにカスタムマッチャーを使用できる
説明
以前は、pages
ディレクトリ内の任意のレベルに_middleware.ts
ファイルを作成できました。ミドルウェアの実行は、そのファイルが作成されたファイルパスに基づいていました。
お客様からのフィードバックに基づいて、このAPIを単一のルートミドルウェアに置き換えました。これにより、以下の改善がもたらされます。
- 低遅延での高速実行: ネストされたミドルウェアでは、単一のリクエストが複数のミドルウェア関数を呼び出す可能性がありました。単一のミドルウェアは単一の関数実行を意味し、より効率的です。
- 低コスト: ミドルウェアの使用は呼び出しごとに課金されます。ネストされたミドルウェアを使用すると、単一のリクエストが複数のミドルウェア関数を呼び出す可能性があり、リクエストごとに複数のミドルウェア料金が発生しました。単一のミドルウェアはリクエストごとに単一の呼び出しを意味し、より費用対効果が高いです。
- ルート以外のものでもミドルウェアを簡単にフィルタリングできる: ネストされたミドルウェアでは、ミドルウェアファイルは
pages
ディレクトリに配置され、ミドルウェアはリクエストパスに基づいて実行されました。単一のルートミドルウェアに移行することで、リクエストパスに基づいてコードを実行できるだけでなく、cookies
やリクエストヘッダーの有無など、他の条件に基づいてミドルウェアをより便利に実行できるようになりました。 - 確定的な実行順序: ネストされたミドルウェアでは、単一のリクエストが複数のミドルウェア関数にマッチする可能性がありました。例えば、
/dashboard/users/*
へのリクエストは、/dashboard/users/_middleware.ts
と/dashboard/_middleware.js
の両方で定義されたミドルウェアを呼び出します。しかし、その実行順序は推測が困難です。単一のルートミドルウェアに移行することで、実行順序がより明確に定義されます。 - Next.js Layouts (RFC) をサポート: 単一のルートミドルウェアへの移行は、Next.jsの新しいLayouts (RFC) のサポートに役立ちます。
アップグレード方法
アプリケーション内で単一のミドルウェアファイルを宣言する必要があります。このファイルは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
を使用して応答を処理するページ/APIに移行する
説明
クライアントサイドとサーバーサイドのナビゲーションの違いを尊重し、開発者が安全でないミドルウェアを構築しないようにするために、ミドルウェアでレスポンスボディを送信する機能を削除します。これにより、ミドルウェアはrewrite
、redirect
、または受信リクエストの変更 (例: クッキーの設定) のみに使用されるようになります。
以下のパターンは機能しなくなります
new Response('a text value')
new Response(streamOrBuffer)
new Response(JSON.stringify(obj), { headers: 'application/json' })
NextResponse.json()
アップグレード方法
ミドルウェアが応答(認証など)に使用されるケースでは、認証エラー、ログインフォーム、またはAPIルートを表示するページへのrewrite
/redirect
の使用に移行する必要があります。
変更前
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 Routes
以前にミドルウェアを使用して外部APIにヘッダーを転送していた場合、現在はEdge API Routesを使用できます
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 |
説明
ベータ版のフィードバックに基づき、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ヘルパー
変更点の概要
- リクエストオブジェクトからユーザーエージェントにアクセスできなくなりました
- ミドルウェアのサイズを
17kb
削減するために、新しいuserAgent
ヘルパーを追加しました
説明
ミドルウェアのサイズを削減するため、リクエストオブジェクトからユーザーエージェントを抽出し、新しいヘルパー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
を通じて公開されます。
ページおよびアセットのマッチングをより正確にするため、現在、Web標準の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
リクエストに対してミドルウェアは実行されませんでした。
ミドルウェアが認証に使用されるケースでは、認証エラー、ログインフォーム、またはAPIルートを表示するページへのrewrite
/redirect
の使用に移行する必要があります。
rewrite
/redirect
の使用に移行する方法の例については、レスポンスボディなしを参照してください。
この情報は役に立ちましたか?