Middleware
Middlewareを使用すると、リクエストが完了する前にコードを実行できます。その後、受信したリクエストに基づいて、リライティング、リダイレクト、リクエストまたはレスポンスヘッダーの変更、または直接応答することによって、レスポンスを変更できます。
Middlewareは、キャッシュ済みコンテンツやルートが一致する前に実行されます。より詳細については、Matching Pathsを参照してください。
使用例
Middlewareをアプリケーションに統合することで、パフォーマンス、セキュリティ、ユーザーエクスペリエンスが大幅に向上します。Middlewareが特に効果的な一般的なシナリオには以下があります:
- 認証と認可:特定のページやAPIルートへのアクセスを許可する前に、ユーザーの身分確認とセッションcookieの確認を行います
- サーバーサイドリダイレクト:特定の条件(例:ロケール、ユーザーロール)に基づいて、サーバーレベルでユーザーをリダイレクトします
- パスのリライティング:A/Bテスト、機能のロールアウト、またはレガシーパスをサポートするために、リクエストプロパティに基づいてAPIルートやページへのパスを動的にリライティングします
- ボット検知:ボットトラフィックを検出しブロックすることで、リソースを保護します
- ロギングと分析:ページやAPIによって処理される前にリクエストデータをキャプチャし、分析します
- 機能フラグ:シームレスな機能ロールアウトやテストのために、機能を動的に有効または無効にします
middlewareが最適なアプローチではない状況を認識することも同様に重要です。注意が必要なシナリオを示します:
- 複雑なデータのフェッチと操作:middlewareは直接データのフェッチや操作を目的としていません。これはRoute Handlersやサーバーサイドのユーティリティで行うべきです。
- 重い計算タスク:Middlewareは軽量で、迅速に応答する必要があります。重い計算タスクや長時間のプロセスは専用のRoute Handlersで行うべきです。
- 広範なセッション管理:Middlewareは基本的なセッションタスクを管理できますが、広範なセッション管理は専用の認証サービスやRoute Handlersで管理すべきです。
- 直接的なデータベース操作:Middleware内での直接なデータベース操作は推奨されません。データベースのやり取りはRoute Handlersまたはサーバーサイドユーティリティで行うべきです。
規約
プロジェクトのrootにあるmiddleware.ts
(または.js
)ファイルを使用してMiddlewareを定義します。たとえば、pages
やapp
と同じレベル、または該当する場合はsrc
の中に配置します。
Note: プロジェクトごとにサポートされている
middleware.ts
ファイルは一つだけですが、それでもモジュール的にmiddlewareのロジックを整理することができます。Middlewareの機能を個別の.ts
または.js
ファイルに分けて、それをメインのmiddleware.ts
ファイルにインポートします。これにより、ルート固有のmiddlewareを整理し、middleware.ts
に集約して中央管理をすることができます。単一のmiddlewareファイルを強制することで、設定を簡素化し、潜在的な競合を防止し、多数のmiddlewareレイヤーを避けることでパフォーマンスを最適化します。
例
- TypeScript
- JavaScript
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))
}
// 詳細は以下の「Matching Paths」を参照
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))
}
// 詳細は以下の「Matching Paths」を参照
export const config = {
matcher: '/about/:path*',
}
パスの一致
Middlewareはプロジェクト内のすべてのルートに対して 呼び出されます。これを踏まえて、マッチャーを使用して特定のルートを正確にターゲットにしたり、除外することが重要です。次は実行順序です:
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
)- Dynamic Routes(
/blog/[slug]
) next.config.js
のfallback
(rewrites
)
Middlewareを実行するパスを定義するには、次の2つの方法があります:
マッチャー
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' }],
},
],
}
Good to know:
matcher
の値は定数である必要があるため、ビルド時に静的解析されます。変数のような動的な値は無視されます。
設定されたマッチャー:
/
で始まらなければなりません- 名前付きパラメーターを含めることができます:
/about/:path
は/about/a
と/about/b
にマッチしますが、/about/a/c
にはマッチしません - 名前付きパラメーターに修飾子を付けることができます(
:
で始まる):/about/:path*
は/about/a/b/c
にマッチします。*
はゼロまたはそれ以上を、?
はゼロまたは1を、+
は1以上を表します - 括弧に囲まれた正規表現を使用できます:
/about/(.*)
は/about/:path*
と同じです
詳細はpath-to-regexpのドキュメントを参照してください。
Good to know: 後方互換性のために、Next.jsは常に
/public
を/public/index
として考慮します。したがって、/public/:path
のマッチャーはマッチします。
条件文
- TypeScript
- JavaScript
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
の宛先のためにリクエストヘッダーを設定する - レスポンスcookieを設定する
- レスポンスヘッダーを設定する
Middlewareからレスポンスを生成するには、以下の手順を実行します:
- レスポンスを生成するルート(PageまたはRoute Handler)に
rewrite
する NextResponse
を直接返す 詳細はProducing a Responseを参照してください
Cookieの使用
cookieは通常のヘッダーです。Request
では、Cookie
ヘッダーに格納されます。Response
では、Set-Cookie
ヘッダーに格納されます。Next.jsはこれらのcookieにアクセスし、操作するための便利な方法をNextRequest
およびNextResponse
上のcookies
拡張機能を通じて提供しています。
- 受信リクエストの場合、
cookies
には次のメソッドがあります:get
、getAll
、set
、delete
cookies。cookieの存在をhas
で確認したり、clear
を使ってすべてのcookieを削除します。 - 送信レスポンスの場合、
cookies
に次のメソッドが備わっています:get
、getAll
、set
、delete
。
- TypeScript
- JavaScript
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
}
import { NextResponse } from 'next/server'
export function middleware(request) {
// 着信リクエストに「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=/test`ヘッダーが付きます。
return response
}
ヘッダーの設定
NextResponse
APIを使用してリクエストヘッダーとレスポンスヘッダーを設定できます(リクエストヘッダーの設定はNext.js v13.0.0から利用可能です)。
- TypeScript
- JavaScript
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
}
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.nextでリクエストヘッダーを設定することもできます
const response = NextResponse.next({
request: {
// 新しいリクエストヘッダー
headers: requestHeaders,
},
})
// 新しいレスポンスヘッダー `x-hello-from-middleware2` を設定
response.headers.set('x-hello-from-middleware2', 'hello')
return response
}
Good to know: 大きなヘッダーを設定すると、バックエンドのWebサーバー設定によっては 431 Request Header Fields Too Large エラーが発生する可能性があるため、避けるべきです。
CORS
Middleware内でCORSヘッダーを設定して、(simpleおよびpreflightedリクエストを含む)クロスオリジンリクエストを許可できます。
- TypeScript
- JavaScript
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)
// preflighted リクエストを処理
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)
// preflighted リクエストを処理
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*',
}
Good to know: 個々のルートに対して Route HandlersでCORSヘッダーを設定することができます。
レスポンスを生成する
Middleware から直接 Response
または NextResponse
インスタンスを返すことでレスポンスを返すことができます。 (これはNext.js v13.1.0から利用可能です)
- TypeScript
- JavaScript
import type { NextRequest } from 'next/server'
import { isAuthenticated } from '@lib/auth'
// Middlewareで`/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 }
)
}
}
import { isAuthenticated } from '@lib/auth'
// Middlewareで`/api/`で始まるパスを制限します。
export const config = {
matcher: '/api/:function*',
}
export function middleware(request) {
// リクエストをチェックするために認証関数を呼び出します
if (!isAuthenticated(request)) {
// エラーメッセージを示すJSONで応答します
return Response.json(
{ success: false, message: 'authentication failed' },
{ status: 401 }
)
}
}
waitUntil
と NextFetchEvent
NextFetchEvent
オブジェクトはネイティブのFetchEvent
オブジェクトを拡張し、waitUntil()
メソッドを含んでいます。
waitUntil()
メソッドは、ミドルウェアのライフタイムを指定した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
で、ミドルウェア用に2つの追加フラグ skipMiddlewareUrlNormalize
と skipTrailingSlashRedirect
が導入され、高度なユースケースに対応するようになりました。
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+)/)
) {
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
パッケージには、ミドルウェアファイルをユニットテストするのに役立つユーティリティが含まれています。ミドルウェアの単体テストは、意図したパスでのみ実行され、カスタムルーティングロジックがコードが本番環境に到達する前に意図通りに動作することを確実にするのに役立ちます。
unstable_doesMiddlewareMatch
関数を使用すると、ミドルウェアが指定されたURL、ヘッダ、cookieで実行されるかどうかをアサートすることができます。
import { unstable_doesMiddlewareMatch } from 'next/experimental/testing/server'
expect(
unstable_doesMiddlewareMatch({
config,
nextConfig,
url: '/test',
})
).toEqual(false)
ミドルウェア関数全体もテストすることができます。
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を使用することもできます
ランタイム
Middlewareは現在、Edge runtimeと互換性のあるAPIのみをサポートしています。Node.jsに特有のAPIは未対応です。
バージョン履歴
Version | 変更点 |
---|---|
v13.1.0 | 高度なミドルウェアフラグが追加されました |
v13.0.0 | ミドルウェアはリクエストヘッダー、レスポンスヘッダーの変更、および応答の送信が可能になりました |
v12.2.0 | ミドルウェアが安定しました。 アップグレードガイドを参照してください |
v12.0.9 | Edge Runtimeで絶対URLを強制するようになりました (PR) |
v12.0.0 | Middleware (ベータ版)が追加されました |