ミドルウェア
ミドルウェアを使用すると、リクエストが完了する前にコードを実行できます。受信したリクエストに基づいて、レスポンスを書き換えたり、リダイレクトしたり、リクエストやレスポンスヘッダーを変更したり、直接応答したりできます。
ミドルウェアはキャッシュされたコンテンツやルートがマッチする前に実行されます。詳細はパスマッチングを参照してください。
ユースケース
ミドルウェアをアプリケーションに統合することで、パフォーマンス、セキュリティ、ユーザーエクスペリエンスの大幅な改善が可能です。ミドルウェアが特に効果的な一般的なシナリオは次のとおりです:
- 認証と認可:特定のページやAPIルートへのアクセスを許可する前に、ユーザーIDを確認しセッションクッキーをチェック
- サーバーサイドリダイレクト:特定の条件(ロケール、ユーザーロールなど)に基づいてサーバーレベルでユーザーをリダイレクト
- パス書き換え:A/Bテスト、機能ロールアウト、レガシーパスをサポートするため、リクエストプロパティに基づいてAPIルートやページへのパスを動的に書き換え
- ボット検出:ボットトラフィックを検出してブロックし、リソースを保護
- ロギングと分析:ページやAPIによる処理前にリクエストデータをキャプチャして分析
- フィーチャーフラグ:機能の動的な有効化/無効化によりシームレスな機能ロールアウトやテストを実現
ミドルウェアが最適なアプローチでない状況を認識することも同様に重要です。注意が必要なシナリオは次のとおりです:
- 複雑なデータフェッチと操作:ミドルウェアは直接的なデータフェッチや操作を目的としていないため、Route Handlersやサーバーサイドユーティリティ内で行うべき
- 重い計算タスク:ミドルウェアは軽量で迅速に応答する必要があり、ページ読み込みの遅延を引き起こす可能性があるため、重い計算タスクや長時間実行プロセスは専用のRoute Handlers内で行うべき
- 大規模なセッション管理:基本的なセッションタスクはミドルウェアで管理可能だが、大規模なセッション管理は専用の認証サービスやRoute Handlers内で管理すべき
- 直接的なデータベース操作:ミドルウェア内での直接的なデータベース操作は推奨されず、Route Handlersやサーバーサイドユーティリティ内で行うべき
規約
プロジェクトのルートにmiddleware.ts
(または.js
)ファイルを作成してミドルウェアを定義します。例えば、pages
やapp
と同じレベル、またはsrc
ディレクトリ内(該当する場合)に配置します。
注記: プロジェクトごとに1つの
middleware.ts
ファイルのみがサポートされますが、ミドルウェアロジックをモジュール化して整理することは可能です。ミドルウェア機能を個別の.ts
または.js
ファイルに分割し、メインのmiddleware.ts
ファイルにインポートできます。これにより、ルート固有のミドルウェアをクリーンに管理しつつ、middleware.ts
で一元管理できます。単一のミドルウェアファイルを強制することで、設定が簡素化され、潜在的な競合が防止され、複数のミドルウェアレイヤーを回避することでパフォーマンスが最適化されます。
例
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
// 内部で`await`を使用する場合はこの関数を`async`にできます
export function middleware(request: NextRequest) {
return NextResponse.redirect(new URL('/home', request.url))
}
// 詳細は「パスマッチング」を参照
export const config = {
matcher: '/about/:path*',
}
import { NextResponse } from 'next/server'
// 内部で`await`を使用する場合はこの関数を`async`にできます
export function middleware(request) {
return NextResponse.redirect(new URL('/home', request.url))
}
// 詳細は「パスマッチング」を参照
export const config = {
matcher: '/about/:path*',
}
パスマッチング
ミドルウェアはプロジェクト内のすべてのルートに対して呼び出されます。このため、matcherを使用して特定のルートを正確にターゲットまたは除外することが重要です。実行順序は次のとおりです:
next.config.js
のheaders
next.config.js
のredirects
- ミドルウェア(
rewrites
、redirects
など) next.config.js
のbeforeFiles
(rewrites
)- ファイルシステムルート(
public/
、_next/static/
、pages/
、app/
など) next.config.js
のafterFiles
(rewrites
)- ダイナミックルート(
/blog/[slug]
) next.config.js
のfallback
(rewrites
)
ミドルウェアが実行されるパスを定義する方法は2つあります:
Matcher
matcher
を使用すると、ミドルウェアを特定のパスで実行するようにフィルタリングできます。
export const config = {
matcher: '/about/:path*',
}
単一のパスまたは複数のパスを配列構文でマッチできます:
export const config = {
matcher: ['/about/:path*', '/dashboard/:path*'],
}
matcher
設定では完全な正規表現が可能なため、否定先読みや文字マッチングなどのマッチングがサポートされています。特定のパスを除いてすべてをマッチさせる否定先読みの例:
export const config = {
matcher: [
/*
* 以下で始まるリクエストパスを除くすべてのリクエストパスにマッチ:
* - api(APIルート)
* - _next/static(静的ファイル)
* - _next/image(画像最適化ファイル)
* - favicon.ico(ファビコンファイル)
*/
'/((?!api|_next/static|_next/image|favicon.ico).*)',
],
}
missing
またはhas
配列、またはその組み合わせを使用して、特定のリクエストに対してミドルウェアをバイパスすることもできます:
export const config = {
matcher: [
/*
* 以下で始まるリクエストパスを除くすべてのリクエストパスにマッチ:
* - api(APIルート)
* - _next/static(静的ファイル)
* - _next/image(画像最適化ファイル)
* - favicon.ico(ファビコンファイル)
*/
{
source: '/((?!api|_next/static|_next/image|favicon.ico).*)',
missing: [
{ type: 'header', key: 'next-router-prefetch' },
{ type: 'header', key: 'purpose', value: 'prefetch' },
],
},
{
source: '/((?!api|_next/static|_next/image|favicon.ico).*)',
has: [
{ type: 'header', key: 'next-router-prefetch' },
{ type: 'header', key: 'purpose', value: 'prefetch' },
],
},
{
source: '/((?!api|_next/static|_next/image|favicon.ico).*)',
has: [{ type: 'header', key: 'x-present' }],
missing: [{ type: 'header', key: 'x-missing', value: 'prefetch' }],
},
],
}
知っておくと良い:
matcher
の値はビルド時に静的に解析できるように定数である必要があります。変数などの動的な値は無視されます。
設定されたmatcher:
/
で始まる必要がある- 名前付きパラメータを含めることができる:
/about/:path
は/about/a
と/about/b
にマッチするが/about/a/c
にはマッチしない - 名前付きパラメータに修飾子を付けられる(
:
で始まる):/about/:path*
は*
が_ゼロ以上_なので/about/a/b/c
にマッチ。?
は_ゼロまたは1つ_、+
は_1つ以上_ - 括弧で囲まれた正規表現を使用できる:
/about/(.*)
は/about/:path*
と同じ
詳細はpath-to-regexpドキュメントを参照してください。
知っておくと良い: 後方互換性のため、Next.jsは常に
/public
を/public/index
として扱います。したがって、/public/:path
のmatcherはマッチします。
条件文
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
export function middleware(request: NextRequest) {
if (request.nextUrl.pathname.startsWith('/about')) {
return NextResponse.rewrite(new URL('/about-2', request.url))
}
if (request.nextUrl.pathname.startsWith('/dashboard')) {
return NextResponse.rewrite(new URL('/dashboard/user', request.url))
}
}
import { NextResponse } from 'next/server'
export function middleware(request) {
if (request.nextUrl.pathname.startsWith('/about')) {
return NextResponse.rewrite(new URL('/about-2', request.url))
}
if (request.nextUrl.pathname.startsWith('/dashboard')) {
return NextResponse.rewrite(new URL('/dashboard/user', request.url))
}
}
NextResponse
NextResponse
APIを使用すると、次のことが可能です:
- 受信リクエストを別のURLに
redirect
- 指定されたURLを表示してレスポンスを
rewrite
- APIルート、
getServerSideProps
、rewrite
先のリクエストヘッダーを設定 - レスポンスクッキーを設定
- レスポンスヘッダーを設定
ミドルウェアからレスポンスを生成するには、次のいずれかを行います:
- レスポンスを生成するルート(ページまたはRoute Handler)に
rewrite
- 直接
NextResponse
を返す。レスポンスの生成を参照
クッキーの使用
クッキーは通常のヘッダーです。Request
ではCookie
ヘッダーに、Response
ではSet-Cookie
ヘッダーに格納されます。Next.jsでは、NextRequest
とNextResponse
のcookies
拡張機能を通じて、これらのクッキーに便利にアクセスして操作できます。
- 受信リクエストの場合、
cookies
には次のメソッドがあります:get
、getAll
、set
、delete
。has
でクッキーの存在を確認したり、clear
ですべてのクッキーを削除したりできます。 - 送信レスポンスの場合、
cookies
には次のメソッドがあります:get
、getAll
、set
、delete
。
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
export function middleware(request: NextRequest) {
// 受信リクエストに「Cookie:nextjs=fast」ヘッダーがあると仮定
// `RequestCookies` APIを使用してリクエストからクッキーを取得
let cookie = request.cookies.get('nextjs')
console.log(cookie) // => { name: 'nextjs', value: 'fast', Path: '/' }
const allCookies = request.cookies.getAll()
console.log(allCookies) // => [{ name: 'nextjs', value: 'fast' }]
request.cookies.has('nextjs') // => true
request.cookies.delete('nextjs')
request.cookies.has('nextjs') // => false
// `ResponseCookies` APIを使用してレスポンスにクッキーを設定
const response = NextResponse.next()
response.cookies.set('vercel', 'fast')
response.cookies.set({
name: 'vercel',
value: 'fast',
path: '/',
})
cookie = response.cookies.get('vercel')
console.log(cookie) // => { name: 'vercel', value: 'fast', Path: '/' }
// 送信レスポンスには`Set-Cookie:vercel=fast;path=/`ヘッダーが含まれます。
return response
}
import { NextResponse } from 'next/server'
export function middleware(request) {
// 受信リクエストに「Cookie:nextjs=fast」ヘッダーがあると仮定
// `RequestCookies` APIを使用してリクエストからクッキーを取得
let cookie = request.cookies.get('nextjs')
console.log(cookie) // => { name: 'nextjs', value: 'fast', Path: '/' }
const allCookies = request.cookies.getAll()
console.log(allCookies) // => [{ name: 'nextjs', value: 'fast' }]
request.cookies.has('nextjs') // => true
request.cookies.delete('nextjs')
request.cookies.has('nextjs') // => false
// `ResponseCookies` APIを使用してレスポンスにクッキーを設定
const response = NextResponse.next()
response.cookies.set('vercel', 'fast')
response.cookies.set({
name: 'vercel',
value: 'fast',
path: '/',
})
cookie = response.cookies.get('vercel')
console.log(cookie) // => { name: 'vercel', value: 'fast', Path: '/' }
// 送信レスポンスには`Set-Cookie:vercel=fast;path=/test`ヘッダーが含まれます。
return response
}
ヘッダーの設定
NextResponse
APIを使用してリクエストとレスポンスのヘッダーを設定できます(_リクエスト_ヘッダーの設定はNext.js v13.0.0以降で利用可能)。
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
export function middleware(request: NextRequest) {
// リクエストヘッダーをクローンし、新しいヘッダー`x-hello-from-middleware1`を設定
const requestHeaders = new Headers(request.headers)
requestHeaders.set('x-hello-from-middleware1', 'hello')
// NextResponse.rewriteでもリクエストヘッダーを設定可能
const response = NextResponse.next({
request: {
// 新しいリクエストヘッダー
headers: requestHeaders,
},
})
// 新しいレスポンスヘッダー`x-hello-from-middleware2`を設定
response.headers.set('x-hello-from-middleware2', 'hello')
return response
}
import { NextResponse } from 'next/server'
export function middleware(request) {
// リクエストヘッダーをクローンし、新しいヘッダー`x-hello-from-middleware1`を設定
const requestHeaders = new Headers(request.headers)
requestHeaders.set('x-hello-from-middleware1', 'hello')
// NextResponse.rewriteでもリクエストヘッダーを設定可能
const response = NextResponse.next({
request: {
// 新しいリクエストヘッダー
headers: requestHeaders,
},
})
// 新しいレスポンスヘッダー`x-hello-from-middleware2`を設定
response.headers.set('x-hello-from-middleware2', 'hello')
return response
}
知っておくと良い: バックエンドWebサーバーの設定によっては、大きなヘッダーを設定すると431 Request Header Fields Too Largeエラーが発生する可能性があるため、避けてください。
CORS
ミドルウェアでCORSヘッダーを設定することで、シンプルリクエストやプリフライトリクエストを含むクロスオリジンリクエストを許可できます。
import { NextRequest, NextResponse } from 'next/server'
const allowedOrigins = ['https://acme.com', 'https://my-app.org']
const corsOptions = {
'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
'Access-Control-Allow-Headers': 'Content-Type, Authorization',
}
export function middleware(request: NextRequest) {
// リクエストからオリジンをチェック
const origin = request.headers.get('origin') ?? ''
const isAllowedOrigin = allowedOrigins.includes(origin)
// プリフライトリクエストを処理
const isPreflight = request.method === 'OPTIONS'
if (isPreflight) {
const preflightHeaders = {
...(isAllowedOrigin && { 'Access-Control-Allow-Origin': origin }),
...corsOptions,
}
return NextResponse.json({}, { headers: preflightHeaders })
}
// シンプルリクエストを処理
const response = NextResponse.next()
if (isAllowedOrigin) {
response.headers.set('Access-Control-Allow-Origin', origin)
}
Object.entries(corsOptions).forEach(([key, value]) => {
response.headers.set(key, value)
})
return response
}
export const config = {
matcher: '/api/:path*',
}
import { NextResponse } from 'next/server'
const allowedOrigins = ['https://acme.com', 'https://my-app.org']
const corsOptions = {
'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
'Access-Control-Allow-Headers': 'Content-Type, Authorization',
}
export function middleware(request) {
// リクエストからオリジンをチェック
const origin = request.headers.get('origin') ?? ''
const isAllowedOrigin = allowedOrigins.includes(origin)
// プリフライトリクエストを処理
const isPreflight = request.method === 'OPTIONS'
if (isPreflight) {
const preflightHeaders = {
...(isAllowedOrigin && { 'Access-Control-Allow-Origin': origin }),
...corsOptions,
}
return NextResponse.json({}, { headers: preflightHeaders })
}
// シンプルリクエストを処理
const response = NextResponse.next()
if (isAllowedOrigin) {
response.headers.set('Access-Control-Allow-Origin', origin)
}
Object.entries(corsOptions).forEach(([key, value]) => {
response.headers.set(key, value)
})
return response
}
export const config = {
matcher: '/api/:path*',
}
補足: Route Handlersで個々のルートに対してCORSヘッダーを設定できます。
レスポンスの生成
ミドルウェアから直接Response
またはNextResponse
インスタンスを返すことでレスポンスを生成できます(この機能はNext.js v13.1.0以降で利用可能)。
import { NextRequest } from 'next/server'
import { isAuthenticated } from '@lib/auth'
// ミドルウェアを`/api/`で始まるパスに限定
export const config = {
matcher: '/api/:function*',
}
export function middleware(request: NextRequest) {
// 認証関数を呼び出してリクエストをチェック
if (!isAuthenticated(request)) {
// エラーメッセージを示すJSONで応答
return Response.json(
{ success: false, message: '認証に失敗しました' },
{ status: 401 }
)
}
}
import { isAuthenticated } from '@lib/auth'
// ミドルウェアを`/api/`で始まるパスに限定
export const config = {
matcher: '/api/:function*',
}
export function middleware(request) {
// 認証関数を呼び出してリクエストをチェック
if (!isAuthenticated(request)) {
// エラーメッセージを示すJSONで応答
return Response.json(
{ success: false, message: '認証に失敗しました' },
{ status: 401 }
)
}
}
waitUntil
とNextFetchEvent
NextFetchEvent
オブジェクトはネイティブのFetchEvent
オブジェクトを拡張し、waitUntil()
メソッドを含みます。
waitUntil()
メソッドはPromiseを引数に取り、Promiseが解決するまでミドルウェアの寿命を延長します。これはバックグラウンドで作業を実行するのに便利です。
import { NextResponse } from 'next/server'
import type { NextFetchEvent, NextRequest } from 'next/server'
export function middleware(req: NextRequest, event: NextFetchEvent) {
event.waitUntil(
fetch('https://my-analytics-platform.com', {
method: 'POST',
body: JSON.stringify({ pathname: req.nextUrl.pathname }),
})
)
return NextResponse.next()
}
高度なミドルウェアフラグ
Next.jsのv13.1
では、高度なユースケースに対応するため、skipMiddlewareUrlNormalize
とskipTrailingSlashRedirect
の2つの追加フラグがミドルウェアに導入されました。
skipTrailingSlashRedirect
は、末尾のスラッシュを追加または削除するNext.jsのリダイレクトを無効にします。これにより、ミドルウェア内でカスタム処理を行い、一部のパスでは末尾のスラッシュを維持し、他のパスでは維持しないようにできます。これは段階的な移行を容易にします。
module.exports = {
skipTrailingSlashRedirect: true,
}
const legacyPrefixes = ['/docs', '/blog']
export default async function middleware(req) {
const { pathname } = req.nextUrl
if (legacyPrefixes.some((prefix) => pathname.startsWith(prefix))) {
return NextResponse.next()
}
// 末尾スラッシュ処理を適用
if (
!pathname.endsWith('/') &&
!pathname.match(/((?!\.well-known(?:\/.*)?)(?:[^/]+\/)*[^/]+\.\w+)/)
) {
req.nextUrl.pathname += '/'
return NextResponse.redirect(req.nextUrl)
}
}
skipMiddlewareUrlNormalize
は、Next.jsのURL正規化を無効にし、直接訪問とクライアント遷移を同じように処理できるようにします。高度なケースでは、このオプションにより元のURLを使用して完全な制御が可能になります。
module.exports = {
skipMiddlewareUrlNormalize: true,
}
export default async function middleware(req) {
const { pathname } = req.nextUrl
// GET /_next/data/build-id/hello.json
console.log(pathname)
// このフラグがある場合 /_next/data/build-id/hello.json
// フラグがない場合 正規化されて /hello になる
}
ランタイム
ミドルウェアは現在Edgeランタイムのみをサポートしています。Node.jsランタイムは使用できません。
バージョン履歴
バージョン | 変更点 |
---|---|
v13.1.0 | 高度なミドルウェアフラグが追加 |
v13.0.0 | ミドルウェアがリクエストヘッダー、レスポンスヘッダーの変更とレスポンス送信が可能に |
v12.2.0 | ミドルウェアが安定版に、アップグレードガイドを参照 |
v12.0.9 | Edgeランタイムで絶対URLを強制 (PR) |
v12.0.0 | ミドルウェア(ベータ)追加 |