Middleware
ミドルウェアを使えば、リクエストが完了する前にコードを実行することができる。
そして、送られてきたリクエストに基づいて、
- レスポンスを書き換え
- リダイレクト
- リクエストやレスポンスのヘッダーを変更
- 直接レスポンス
など、レスポンスを変更することができる。
ミドルウェアは、キャッシュされたコンテンツとルートがマッチングされる前に実行される。
詳細はmatching-pathsを参照。
Use Cases
ミドルウェアをアプリケーションに統合することで、
パフォーマンス、セキュリティ、ユーザーエクスペリエンスを大幅に向上させることができる。
ミドルウェアが特に効果的な一般的なシナリオには、次のようなものがある。
認証と認可
特定のページやAPIルートへのアクセスを許可する前に、
ユーザーの身元を確認し、セッションクッキーをチェックする。
サーバーサイドリダイレクト
特定の条件(ロケール、ユーザーの役割など)に基づいて、
サーバーレベルでユーザーをリダイレクトする。
パスの書き換え
リクエストプロパティに基づいて、APIルートやページへのパスを動的に書き換えることで、
A/Bテスト、機能ロールアウト、レガシーパスをサポートします。
Botの検知
ボットトラフィックを検出してブロックすることで、リソースを保護します。
ロギングと分析
ページやAPIで処理する前に、リクエストデータを取得して分析し、洞察を得る。
機能フラッグ
シームレスな機能ロールアウトやテストのために、動的に機能を有効または無効にする。
ミドルウェアが最適なアプローチではないかもしれない状況を認識することも同様に重要。
以下に、注意すべきシナリオをいくつか挙げる。
複雑なデータのフェッチと操作
ミドルウェアは、直接データを取得したり操作したりするようには設計されていない。
このような操作は、代わりにRoute Handlerまたはサーバー側のユーティリティで行う。
重い計算タスク
ミドルウェアは軽量で応答が速いものでなければならない。
そうでないとページロードの遅延を引き起こす可能性がある。
重い計算タスクや長時間実行される処理は、専用のRoute Handlerの中で行うべき。
広範なセッション管理
ミドルウェアは基本的なセッションタスクを管理できるが、
広範なセッション管理は専用の認証サービスまたは専用のRoute Handler内で管理する必要がある。
データベースの直接操作
ミドルウェア内でデータベースを直接操作することは推奨されない。
データベースとのやり取りは、Route Handleやサーバサイドのユーティリティの中で行う。
Convention
ミドルウェアを定義するには、プロジェクトのルートにあるmiddleware.ts(または.js)ファイルを使用する。
例えば、pagesやappと同じレベルか、該当する場合はsrcの中。
Example
// middleware.ts
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
// This function can be marked `async` if using `await` inside
export function middleware(request: NextRequest) {
return NextResponse.redirect(new URL('/home', request.url))
}
// See "Matching Paths" below to learn more
export const config = {
matcher: '/about/:path*',
}
Matching Paths
ミドルウェアは、プロジェクト内のすべてのルートに対して呼び出される。
マッチャーを使って、特定のルートを正確にターゲットにしたり、除外したりすることが重要。
実行順序は以下
- 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 を使用すると、特定のパスで実行されるミドルウェアをフィルタリングできる。
// middleware.js
export const config = {
matcher: '/about/:path*',
}
配列構文で、単一のパスまたは複数のパスにマッチさせることができる。
// middleware.js
export const config = {
matcher: ['/about/:path*', '/dashboard/:path*'],
}
matcher configでは完全な正規表現が使用できるので、
negative lookaheads(否定先読み)や文字マッチングなどのマッチングがサポートされている。
特定のパス以外をマッチさせるnegative lookaheadsの例。
// middleware.js
export const config = {
matcher: [
/*
* Match all request paths except for the ones starting with:
* - api (API routes)
* - _next/static (static files)
* - _next/image (image optimization files)
* - favicon.ico (favicon file)
*/
'/((?!api|_next/static|_next/image|favicon.ico).*)',
],
}
missing配列、has配列、またはその両方を組み合わせて使用することで、 特定のリクエストに対してミドルウェアをバイパス(スキップ)することもできる。
// middleware.js
export const config = {
matcher: [
/*
* Match all request paths except for the ones starting with:
* - api (API routes)
* - _next/static (static files)
* - _next/image (image optimization files)
* - favicon.ico (favicon file)
*/
{
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' }],
},
],
}
設定されたマッチャー:
その1
/で始まらなければならない
その2
名前付きパラメータを含むことができる
/about/:pathは/about/aと/about/bにマッチするが、/about/a/cにはマッチしない
その3
名前付きパラメータ(:で始まる)に修飾子をつけることができる
/about/:path*は/about/a/b/cにマッチする。
- はゼロ以上。?はゼロか1。+は1つ以上。
その4
括弧で囲まれた正規表現が使える。
/about/(.) は /about/:pathと同じ。
詳細はpath-to-regexpのドキュメントを参照。
後方互換性のため、Next.jsは常に/publicを/public/indexとみなす。
したがって、/public/:pathというマッチャーがマッチする。
Conditional Statements
// middleware.ts
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を使用すると、次のことが可能になる。
- redirect: 別のURLへのリクエスト
- rewrite: 指定されたURLを表示してレスポンスを返す
- API Routes、getServerSideProps、rewriteの宛先のリクエストヘッダを設定する
- レスポンス・クッキーを設定する
- レスポンス・ヘッダを設定する
ミドルウェアからのレスポンスを生成するには、次のようにする。
- レスポンスを生成するルート (Page または Edge API Route) に書き換える。
- NextResponseを直接returnする
Using Cookies
クッキーは通常のヘッダー。
リクエストでは、Cookieヘッダーに格納されます。
レスポンスでは、Set-Cookieヘッダーに格納されます。
Next.jsは、NextRequestとNextResponseのCookiesエクステンションを通して、
これらのCookiesにアクセスし、操作する便利な方法を提供します。
その1
受信リクエストに対して、クッキーは以下のメソッドを提供する。
- get
- getAll
- set
- delete
- has
- clear
その2
発信レスポンスに対して、クッキーは以下のメソッドを提供する
- get
- getAll
- set
- delete
// middleware.ts
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
export function middleware(request: NextRequest) {
// Assume a "Cookie:nextjs=fast" header to be present on the incoming request
// Getting cookies from the request using the `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
// Setting cookies on the response using the `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: '/' }
// The outgoing response will have a `Set-Cookie:vercel=fast;path=/` header.
return response
}
Setting Headers
リクエストヘッダとレスポンスヘッダは、NextResponse APIで設定できる。
// middleware.ts
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
export function middleware(request: NextRequest) {
// Clone the request headers and set a new header `x-hello-from-middleware1`
const requestHeaders = new Headers(request.headers)
requestHeaders.set('x-hello-from-middleware1', 'hello')
// You can also set request headers in NextResponse.rewrite
const response = NextResponse.next({
request: {
// New request headers
headers: requestHeaders,
},
})
// Set a new response header `x-hello-from-middleware2`
response.headers.set('x-hello-from-middleware2', 'hello')
return response
}
バックエンドのウェブサーバーの設定によっては、
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) {
// Check the origin from the request
const origin = request.headers.get('origin') ?? ''
const isAllowedOrigin = allowedOrigins.includes(origin)
// Handle preflighted requests
const isPreflight = request.method === 'OPTIONS'
if (isPreflight) {
const preflightHeaders = {
...(isAllowedOrigin && { 'Access-Control-Allow-Origin': origin }),
...corsOptions,
}
return NextResponse.json({}, { headers: preflightHeaders })
}
// Handle simple requests
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*',
}
Producing a Response
ResponseやNextResponseのインスタンスを返すことで、
ミドルウェアから直接応答することができる。
// middleware.ts
import { NextRequest } from 'next/server'
import { isAuthenticated } from '@lib/auth'
// Limit the middleware to paths starting with `/api/`
export const config = {
matcher: '/api/:function*',
}
export function middleware(request: NextRequest) {
// Call our authentication function to check the request
if (!isAuthenticated(request)) {
// Respond with JSON indicating an error message
return Response.json(
{ success: false, message: 'authentication failed' },
{ status: 401 }
)
}
}
waitUntil and NextFetchEvent
NextFetchEventオブジェクトは、
ネイティブのFetchEventオブジェクトを拡張し、
waitUntil()メソッドを含んでいる。
waitUntil()メソッドは、
引数にPromiseを取り、Promiseが落ち着くまでミドルウェアのライフタイムを延長する。
これは、バックグラウンドで作業を行う場合に便利です。
// middleware.ts
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()
}
Advanced Middleware Flags
Next.jsのv13.1では、高度なユースケースに対応するために、ミドルウェア用に2つの追加フラグ、skipMiddlewareUrlNormalizeとskipTrailingSlashRedirectが導入された。
skipTrailingSlashRedirectは、Next.jsのリダイレクトによる末尾スラッシュの追加や削除を無効にします。
これにより、ミドルウェア内部のカスタム処理で、
あるパスでは末尾のスラッシュを維持し、
他のパスでは維持しないようにすることができます。
// next.config.js
module.exports = {
skipTrailingSlashRedirect: true,
}
// middleware.js
const legacyPrefixes = ['/docs', '/blog']
export default async function middleware(req) {
const { pathname } = req.nextUrl
if (legacyPrefixes.some((prefix) => pathname.startsWith(prefix))) {
return NextResponse.next()
}
// apply trailing slash handling
if (
!pathname.endsWith('/') &&
!pathname.match(/((?!\.well-known(?:\/.*)?)(?:[^/]+\/)*[^/]+\.\w+)/)
) {
req.nextUrl.pathname += '/'
return NextResponse.redirect(req.nextUrl)
}
}
skipMiddlewareUrlNormalizeを使用すると、
Next.jsのURL正規化を無効にして、直接訪問とクライアント遷移の処理を同じにする。
いくつかの高度なケースでは、このオプションは、元のURLを使用することにより、完全な制御を提供する。
// next.config.js
module.exports = {
skipMiddlewareUrlNormalize: true,
}
// middleware.js
export default async function middleware(req) {
const { pathname } = req.nextUrl
// GET /_next/data/build-id/hello.json
console.log(pathname)
// with the flag this now /_next/data/build-id/hello.json
// without the flag this would be normalized to /hello
}
Runtime
ミドルウェアは現在、Edgeランタイムのみをサポートしている。
Node.jsランタイムは使用できない。
Version History
Version | Changes |
---|---|
v13.1.0 | 高度なミドルウェアのフラグが追加 |
v13.0.0 | ミドルウェアはリクエスト・ヘッダ、レスポンス・ヘッダを修正し、レスポンスを送信することができる。 |
v12.2.0 | ミドルウェアは安定しています。 |
v12.0.9 | Edgeランタイムで絶対URLを強制する |
v12.0.0 | ミドルウェア(ベータ版)追加 |