コンテンツへスキップ

ミドルウェア

ミドルウェアを使用すると、リクエストが完了する前にコードを実行できます。その後、受信リクエストに基づいて、リライト、リダイレクト、リクエストまたはレスポンスヘッダーの変更、または直接応答することにより、レスポンスを変更できます。

ミドルウェアは、キャッシュされたコンテンツとルートがマッチングされる前に実行されます。詳細については、パスのマッチングを参照してください。

ユースケース

アプリケーションにミドルウェアを統合すると、パフォーマンス、セキュリティ、およびユーザーエクスペリエンスが大幅に向上する可能性があります。ミドルウェアが特に効果的な一般的なシナリオには、次のものがあります。

  • 認証と承認: 特定のページまたは API ルートへのアクセスを許可する前に、ユーザーの ID を確認し、セッション Cookie を確認します。
  • サーバーサイドリダイレクト: 特定の条件(ロケール、ユーザーロールなど)に基づいて、サーバーレベルでユーザーをリダイレクトします。
  • パスのリライト: リクエストプロパティに基づいて、API ルートまたはページへのパスを動的にリライトすることにより、A/B テスト、機能のロールアウト、またはレガシーパスをサポートします。
  • ボット検出: ボットトラフィックを検出してブロックすることにより、リソースを保護します。
  • ログと分析: ページまたは API による処理の前に、洞察を得るためにリクエストデータをキャプチャして分析します。
  • 機能フラグ: シームレスな機能のロールアウトまたはテストのために、機能を動的に有効または無効にします。

ミドルウェアが最適なアプローチではない可能性のある状況を認識することも同様に重要です。注意すべきいくつかのシナリオを次に示します。

  • 複雑なデータフェッチと操作: ミドルウェアは、直接的なデータフェッチまたは操作用に設計されていません。これは代わりにルートハンドラーまたはサーバーサイドユーティリティ内で行う必要があります。
  • 負荷の高い計算タスク: ミドルウェアは軽量で迅速に応答する必要があります。そうでない場合、ページ読み込みに遅延が発生する可能性があります。負荷の高い計算タスクまたは長時間実行されるプロセスは、専用のルートハンドラー内で行う必要があります。
  • 広範なセッション管理: ミドルウェアは基本的なセッションタスクを管理できますが、広範なセッション管理は、専用の認証サービスまたはルートハンドラー内で管理する必要があります。
  • 直接的なデータベース操作: ミドルウェア内で直接的なデータベース操作を実行することはお勧めしません。データベースインタラクションは、ルートハンドラーまたはサーバーサイドユーティリティ内で行う必要があります。

規約

ミドルウェアを定義するには、プロジェクトのルートに middleware.ts (または .js) ファイルを使用します。たとえば、pages または app と同じレベル、または該当する場合は src 内です。

: プロジェクトごとにサポートされている middleware.ts ファイルは 1 つだけですが、ミドルウェアロジックをモジュール式に整理することはできます。ミドルウェア機能を個別の .ts または .js ファイルに分割し、メインの middleware.ts ファイルにインポートします。これにより、ルート固有のミドルウェアをよりクリーンに管理でき、一元的な制御のために middleware.ts に集約されます。単一のミドルウェアファイルを強制することにより、構成が簡素化され、潜在的な競合が防止され、複数のミドルウェアレイヤーを回避することでパフォーマンスが最適化されます。

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*',
}

パスのマッチング

ミドルウェアは、プロジェクト内のすべてのルートに対して呼び出されます。そのため、特定のルートを正確にターゲットまたは除外するためにマッチャーを使用することが重要です。以下は実行順序です。

  1. next.config.jsheaders
  2. next.config.jsredirects
  3. ミドルウェア (rewrites, redirects など)
  4. next.config.jsbeforeFiles (rewrites)
  5. ファイルシステムルート (public/, _next/static/, pages/, app/ など)
  6. next.config.jsafterFiles (rewrites)
  7. 動的ルート (/blog/[slug])
  8. next.config.jsfallback (rewrites)

ミドルウェアが実行されるパスを定義する方法は2つあります。

  1. カスタムマッチャー設定
  2. 条件文

マッチャー

matcher を使用すると、特定のパスで実行されるミドルウェアをフィルター処理できます。

middleware.js
export const config = {
  matcher: '/about/:path*',
}

単一のパスまたは複数のパスを配列構文でマッチングできます。

middleware.js
export const config = {
  matcher: ['/about/:path*', '/dashboard/:path*'],
}

matcher 設定は完全な正規表現を許可するため、否定先読みや文字マッチングのようなマッチングもサポートされています。特定のパスを除くすべてにマッチングする否定先読みの例を以下に示します。

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, sitemap.xml, robots.txt (metadata files)
     */
    '/((?!api|_next/static|_next/image|favicon.ico|sitemap.xml|robots.txt).*)',
  ],
}

また、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, sitemap.xml, robots.txt (metadata files)
     */
    {
      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 の値は、ビルド時に静的に解析できるように定数である必要があります。変数のような動的な値は無視されます。

設定されたマッチャー

  1. / で始まる必要があります
  2. 名前付きパラメーターを含めることができます: /about/:path/about/a および /about/b にマッチングしますが、/about/a/c にはマッチングしません
  3. 名前付きパラメーターに修飾子 (: で始まる) を設定できます: /about/:path*/about/a/b/c にマッチングします。なぜなら、* は *0回以上の繰り返し* を意味するからです。 ? は *0回または1回*、+ は *1回以上の繰り返し* を意味します
  4. 括弧で囲まれた正規表現を使用できます: /about/(.*)/about/:path* と同じです

path-to-regexp のドキュメントで詳細を確認してください。

知っておくとよいこと: 後方互換性のために、Next.js は常に /public/public/index として扱います。したがって、/public/:path のマッチャーはマッチングします。

条件文

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 を使用すると、次のことが可能になります。

  • 受信リクエストを別の URL に redirect する
  • 指定された URL を表示してレスポンスを rewrite する
  • API ルート、getServerSideProps、および rewrite の宛先に対してリクエストヘッダーを設定する
  • レスポンスクッキーを設定する
  • レスポンスヘッダーを設定する

ミドルウェアからレスポンスを生成するには、次のいずれかの方法を使用できます。

  1. レスポンスを生成するルート (ページ または ルートハンドラー) に rewrite する
  2. NextResponse を直接返す。レスポンスの生成を参照してください

クッキーの使用

クッキーは通常のヘッダーです。Request では、Cookie ヘッダーに格納されます。Response では、Set-Cookie ヘッダーに格納されます。Next.js は、NextRequest および NextResponsecookies 拡張を通じて、これらのクッキーにアクセスして操作する便利な方法を提供します。

  1. 受信リクエストの場合、cookies には、getgetAllset、および delete クッキーのメソッドが付属しています。has を使用してクッキーの存在を確認したり、clear を使用してすべてのクッキーを削除したりできます。
  2. 送信レスポンスの場合、cookies には、getgetAllset、および 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
}

ヘッダーの設定

NextResponse API を使用して、リクエストヘッダーとレスポンスヘッダーを設定できます (リクエストヘッダーの設定は Next.js v13.0.0 以降で利用可能です)。

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.next
  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
}

知っておくとよいこと: 大規模なヘッダーの設定は、バックエンドの Web サーバー構成によっては 431 Request Header Fields Too Large エラーの原因になる可能性があるため、避けてください。

CORS

ミドルウェアで CORS ヘッダーを設定して、単純な および プリフライト リクエストを含む、クロスオリジンリクエストを許可できます。

middleware.ts
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*',
}

知っておくとよいこと: ルートハンドラー で個々のルートの CORS ヘッダーを設定できます。

レスポンスの生成

Response または NextResponse インスタンスを返すことで、ミドルウェアから直接応答できます (これは Next.js v13.1.0 以降で利用可能です)。

middleware.ts
import type { 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 および 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()
}

高度なミドルウェアフラグ

Next.js の v13.1 では、ミドルウェア用に skipMiddlewareUrlNormalizeskipTrailingSlashRedirect の 2 つの追加フラグが導入され、高度なユースケースに対応しました。

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+)/)
  ) {
    return NextResponse.redirect(
      new URL(`${req.nextUrl.pathname}/`, 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
}

ランタイム

現在、ミドルウェアはEdgeランタイムと互換性のあるAPIのみをサポートしています。Node.jsに固有のAPIはサポートされていません

バージョン履歴

バージョン変更点
v13.1.0高度なミドルウェアフラグが追加されました
v13.0.0ミドルウェアはリクエストヘッダー、レスポンスヘッダーを変更し、レスポンスを送信できます
v12.2.0ミドルウェアは安定版となりました。アップグレードガイドを参照してください。
v12.0.9Edgeランタイムで絶対URLを強制 (PR)
v12.0.0ミドルウェア (ベータ版) が追加されました