Menu

ミドルウェア

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

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

ユースケース

ミドルウェアをアプリケーションに統合することで、パフォーマンス、セキュリティ、ユーザーエクスペリエンスに大きな改善をもたらすことができます。ミドルウェアが特に効果的な一般的なシナリオは次のとおりです:

  • 認証と認可:特定のページやAPIルートにアクセスする前に、ユーザーの身元を確認し、セッションクッキーを確認します。
  • サーバーサイドリダイレクト:ロケールやユーザーロールなどの特定の条件に基づいて、サーバーレベルでユーザーをリダイレクトします。
  • パスの書き換え:リクエストのプロパティに基づいて、APIルートやページへのパスを動的に書き換えることで、A/Bテスト、機能のロールアウト、または従来のパスをサポートします。
  • ボット検出:ボットトラフィックを検出およびブロックすることで、リソースを保護します。
  • ログとアナリティクス:ページまたはAPIで処理される前に、リクエストデータをキャプチャして分析します。
  • 機能フラグ:機能のシームレスなロールアウトやテストのために、動的に機能を有効または無効にします。

ミドルウェアが最適なアプローチではない状況を認識することも同様に重要です。注意すべきシナリオは次のとおりです:

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

規約

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

注意: プロジェクトごとに1つの middleware.ts ファイルのみがサポートされますが、ミドルウェアロジックをモジュール化して整理することはできます。ミドルウェア機能を別の .ts または .js ファイルに分割し、メインの middleware.ts ファイルにインポートできます。これにより、ルート固有のミドルウェアを整理し、集中管理のために 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*',
}
middleware.js
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*',
}

パスのマッチング

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

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

ミドルウェアが実行されるパスを定義する方法は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: [
    /*
     * 次で始まるリクエストパスを除くすべてのリクエストパスをマッチング:
     * - 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.js
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 の値は、ビルド時に静的に分析できるように、定数である必要があります。変数などの動的な値は無視されます。

設定されたマッチャーは:

  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 のマッチャーはマッチします。

条件文

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))
  }
}
middleware.js
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 Routes、getServerSideProps、およびrewriteの宛先のリクエストヘッダーを設定する
  • 応答クッキーを設定する
  • 応答ヘッダーを設定する

ミドルウェアから応答を生成するには、以下の方法があります:

  1. 応答を生成するルート(PageまたはEdge API Route)にrewriteする
  2. 直接NextResponseを返す。応答の生成を参照

クッキーの使用

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

  1. 受信リクエストの場合、cookiesにはgetgetAllsetdeleteのメソッドがあります。hasでクッキーの存在を確認したり、clearですべてのクッキーを削除したりできます。
  2. 送信応答の場合、cookiesにはgetgetAllsetdeleteのメソッドがあります。
middleware.ts
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
}
middleware.js
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から利用可能)。

middleware.ts
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
}
middleware.js
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
}

補足: バックエンドのウェブサーバー設定によっては、大きなヘッダーを設定すると431 Request Header Fields Too Largeエラーが発生する可能性があるため、避けてください。

CORS

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

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

レスポンスの生成

Middlewareから直接、ResponseまたはNextResponseインスタンスを返すことでレスポンスできます。(これはNext.js v13.1.0から利用可能です)

middleware.ts
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 }
    )
  }
}
middleware.js
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: 'authentication failed' },
      { status: 401 }
    )
  }
}

waitUntilNextFetchEvent

NextFetchEventオブジェクトはネイティブのFetchEventオブジェクトを拡張し、waitUntil()メソッドを含みます。

waitUntil()メソッドはプロミスを引数として受け取り、プロミスが解決するまでMiddlewareの有効期間を延長します。これはバックグラウンドで作業を実行する際に有用です。

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()
}

高度なMiddlewareフラグ

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()
  }
 
  // 末尾のスラッシュ処理を適用
  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)
  // フラグを使用すると、これは /_next/data/build-id/hello.json になります
  // フラグなしの場合、これは /hello に正規化されます
}

ランタイム

現在、MiddlewareはEdge runtimeと互換性のあるAPIのみをサポートしています。Node.js専用のAPIはサポートされていません

バージョン履歴

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