Middleware
Middlewareを使用すると、リクエストが完了する前にコードを実行できます。そして、受信リクエストに基づいて、書き換え、リダイレクト、リクエストまたはレスポンスヘッダーの変更、または直接レスポンスを返すことでレスポンスを変更できます。
Middlewareはキャッシュされたコンテンツやルートがマッチングされる前に実行されます。詳細についてはパスのマッチングを参照してください。
ユースケース
アプリケーションにMiddlewareを組み込むことで、パフォーマンス、セキュリティ、ユーザーエクスペリエンスが大幅に向上する可能性があります。Middlewareが特に効果的ないくつかの一般的なシナリオには以下があります:
- 認証と認可:特定のページやAPIルートへのアクセスを許可する前に、ユーザーのIDを確認しセッションCookieをチェックします。
- サーバーサイドリダイレクト:特定の条件(ロケール、ユーザーロールなど)に基づいて、サーバーレベルでユーザーをリダイレクトします。
- パスの書き換え:リクエストのプロパティに基づいてパスをAPIルートやページに動的に書き換えることで、A/Bテスト、機能のロールアウト、レガシーパスをサポートします。
- ボット検出:ボットトラフィックを検出してブロックすることで、リソースを保護します。
- ロギングと分析:ページやAPIによる処理の前に、リクエストデータをキャプチャして分析し、インサイトを得ます。
- 機能フラグ:機能のスムーズなロールアウトやテストのために、機能を動的に有効または無効にします。
middlewareが最適なアプローチではない状況を認識することも同様に重要です。注意すべきいくつかのシナリオを以下に示します:
- 複雑なデータ取得と操作:MiddlewareはデータのFetchや操作を直接行うように設計されていないため、これらはRoute Handlerやサーバーサイドユーティリティ内で行うべきです。
- 重い計算タスク:Middlewareは軽量で迅速に応答する必要があり、そうでなければページの読み込みが遅延する可能性があります。重い計算タスクや長時間実行されるプロセスは、専用のRoute Handler内で行うべきです。
- 広範なセッション管理:Middlewareは基本的なセッションタスクを管理できますが、広範なセッション管理は専用の認証サービスまたはRoute Handler内で管理すべきです。
- データベースへの直接操作:Middleware内で直接データベース操作を実行することはお勧めしません。データベースとのやり取りはRoute Handlerやサーバーサイドユーティリティ内で行うべきです。
規則
プロジェクトのルートにmiddleware.ts
(または.js
)ファイルを使用してMiddlewareを定義します。例えば、pages
やapp
と同じレベル、または該当する場合はsrc
内に配置します。
注意: プロジェクトごとに1つの
middleware.ts
ファイルしかサポートされていませんが、middlewareのロジックをモジュール式に整理することは可能です。middlewareの機能を別々の.ts
または.js
ファイルに分割し、メインのmiddleware.ts
ファイルにインポートします。これにより、ルート固有のmiddlewareをより整理された形で管理でき、集中制御のためにmiddleware.ts
にまとめることができます。単一のmiddlewareファイルを強制することで、設定が簡素化され、潜在的な競合を防ぎ、複数のmiddlewareレイヤーを回避することでパフォーマンスが最適化されます。
例
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*',
}
パスのマッチング
Middlewareはプロジェクト内のすべてのルートに対して呼び出されます。これを考慮すると、matcherを使用して特定のルートを正確にターゲットにするか除外することが重要です。以下は実行順序です:
next.config.js
のheaders
next.config.js
のredirects
- Middleware(
rewrites
、redirects
など) next.config.js
のbeforeFiles
(rewrites
)- ファイルシステムルート(
public/
、_next/static/
、pages/
、app/
など) next.config.js
のafterFiles
(rewrites
)- 動的ルート(
/blog/[slug]
) next.config.js
のfallback
(rewrites
)
Middlewareを実行するパスを定義するには2つの方法があります:
Matcher
matcher
を使うことで、特定のパスでMiddlewareを実行するようにフィルタリングできます。
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、sitemap.xml、robots.txt(メタデータファイル)
*/
'/((?!api|_next/static|_next/image|favicon.ico|sitemap.xml|robots.txt).*)',
],
}
missing
またはhas
配列、あるいはその両方を使用して、特定のリクエストに対してMiddlewareをバイパスすることもできます:
export const config = {
matcher: [
/*
* 以下で始まるリクエストパス以外のすべてにマッチします:
* - api(APIルート)
* - _next/static(静的ファイル)
* - _next/image(画像最適化ファイル)
* - favicon.ico、sitemap.xml、robots.txt(メタデータファイル)
*/
{
source:
'/((?!api|_next/static|_next/image|favicon.ico|sitemap.xml|robots.txt).*)',
missing: [
{ type: 'header', key: 'next-router-prefetch' },
{ type: 'header', key: 'purpose', value: 'prefetch' },
],
},
{
source:
'/((?!api|_next/static|_next/image|favicon.ico|sitemap.xml|robots.txt).*)',
has: [
{ type: 'header', key: 'next-router-prefetch' },
{ type: 'header', key: 'purpose', value: 'prefetch' },
],
},
{
source:
'/((?!api|_next/static|_next/image|favicon.ico|sitemap.xml|robots.txt).*)',
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
にマッチします。これは*
が_0回以上_であるためです。?
は_0回または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))
}
}
NextResponse
NextResponse
APIを使用すると以下のことができます:
- 受信リクエストを別のURLに
redirect
する - 指定されたURLを表示することでレスポンスを
rewrite
する - APIルート、
getServerSideProps
、およびrewrite
先のリクエストヘッダーを設定する - レスポンスCookieを設定する
- レスポンスヘッダーを設定する
Middlewareからレスポンスを生成するには、以下の方法があります:
- レスポンスを生成するルート(PageまたはRoute Handler)に
rewrite
する NextResponse
を直接返す。レスポンスの生成を参照
Cookieの使用
Cookieは通常のヘッダーです。Request
ではCookie
ヘッダーに、Response
ではSet-Cookie
ヘッダーに格納されています。Next.jsでは、NextRequest
とNextResponse
のcookies
拡張機能を通じて、これらのCookieにアクセスし操作する便利な方法を提供しています。
- 受信リクエストの場合、
cookies
には以下のメソッドがあります:get
、getAll
、set
、delete
。has
でCookieの存在を確認したり、clear
ですべてのCookieを削除したりできます。 - 送信レスポンスの場合、
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を使用してリクエストからCookieを取得
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を使用してレスポンスにCookieを設定
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
}
ヘッダーの設定
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.nextでもリクエストヘッダーを設定できます
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
Middlewareで CORS ヘッダーを設定すると、simpleおよびpreflightedリクエストを含むクロスオリジンリクエストを許可できます。
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*',
}
補足: 個々のルートのCORSヘッダーはRoute Handlersで設定できます。
レスポンスの生成
Response
またはNextResponse
インスタンスを返すことで、Middlewareから直接レスポンスできます(これはNext.js v13.1.0以降で利用可能)。
import type { 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: 'authentication failed' },
{ status: 401 }
)
}
}
waitUntil
とNextFetchEvent
NextFetchEvent
オブジェクトは、ネイティブのFetchEvent
オブジェクトを拡張し、waitUntil()
メソッドを含みます。
waitUntil()
メソッドは引数としてプロミスを取り、プロミスが解決するまでMiddlewareの寿命を延長します。これはバックグラウンドで作業を実行するのに役立ちます。
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()
}
高度なMiddlewareフラグ
Next.jsのv13.1
では、高度なユースケースを処理するための2つの追加フラグ、skipMiddlewareUrlNormalize
とskipTrailingSlashRedirect
が導入されました。
skipTrailingSlashRedirect
は末尾のスラッシュの追加または削除に関するNext.jsのリダイレクトを無効にします。これにより、一部のパスでは末尾のスラッシュを維持し、他のパスでは維持しないようにmiddleware内でカスタム処理ができ、段階的な移行が容易になります。
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+)/)
) {
return NextResponse.redirect(
new URL(`${req.nextUrl.pathname}/`, 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に正規化される
}
ユニットテスト(実験的)
Next.js 15.1から、next/experimental/testing/server
パッケージにはmiddlewareファイルのユニットテストを支援するユーティリティが含まれています。middlewareのユニットテストは、コードが本番環境に到達する前に、望ましいパスでのみ実行されることや、カスタムルーティングロジックが意図したとおりに機能することを確認するのに役立ちます。
unstable_doesMiddlewareMatch
関数は、提供されたURL、ヘッダー、クッキーに対してmiddlewareが実行されるかどうかを確認するために使用できます。
import { unstable_doesMiddlewareMatch } from 'next/experimental/testing/server'
expect(
unstable_doesMiddlewareMatch({
config,
nextConfig,
url: '/test',
})
).toEqual(false)
middleware関数全体もテストできます。
import { isRewrite, getRewrittenUrl } from 'next/experimental/testing/server'
const request = new NextRequest('https://nextjs.org/docs')
const response = await middleware(request)
expect(isRewrite(response)).toEqual(true)
expect(getRewrittenUrl(response)).toEqual('https://other-domain.com/docs')
// レスポンスがリダイレクトの場合はgetRedirectUrlも使用できます
ランタイム
ミドルウェアはデフォルトでEdgeランタイムを使用します。v15.2(canary)以降、Node.jsランタイムを使用するための実験的サポートが追加されました。これを有効にするには、next.config
ファイルにフラグを追加してください:
import type { NextConfig } from 'next'
const nextConfig: NextConfig = {
experimental: {
nodeMiddleware: true,
},
}
export default nextConfig
次に、ミドルウェアファイルのconfig
オブジェクトでランタイムをnodejs
に設定します:
export const config = {
runtime: 'nodejs',
}
注意: この機能はまだ本番環境での使用は推奨されていません。そのため、安定版ではなくnext@canaryリリースを使用していない場合、Next.jsはエラーをスローします。
バージョン履歴
バージョン | 変更内容 |
---|---|
導入時期:v15.2.0 | ミドルウェアがNode.jsランタイムを使用可能に(実験的) |
導入時期:v13.1.0 | 高度なミドルウェアフラグが追加 |
導入時期:v13.0.0 | ミドルウェアがリクエストヘッダー、レスポンスヘッダーの変更とレスポンスの送信をサポート |
導入時期:v12.2.0 | ミドルウェアが安定版に、アップグレードガイドを参照 |
導入時期:v12.0.9 | Edgeランタイムでの絶対URLの強制(PR) |
導入時期:v12.0.0 | ミドルウェア(ベータ版)追加 |