Proxy
注意:
middlewareファイル規約は廃止され、proxyに名前変更されました。詳細はProxyへの移行を参照してください。
proxy.js|tsファイルは、Proxyを記述し、リクエストが完了する前にサーバー上でコードを実行するために使用されます。受け取るリクエストに基づいて、書き換え、リダイレクト、リクエストまたはレスポンスヘッダーの変更、または直接レスポンスを返すことで、レスポンスを変更できます。
Proxyはルートがレンダリングされる前に実行されます。認証、ログ、またはリダイレクト処理などのカスタムサーバーサイドロジックの実装に特に役立ちます。
補足:
Proxyはレンダリングコードとは別に呼び出されることを意図しており、最適化されたケースではCDNにデプロイされて高速なリダイレクト・書き換え処理を行います。共有モジュールやグローバル変数に依存しないようにしてください。
Proxyからアプリケーションに情報を渡すには、ヘッダー、クッキー、書き換え、リダイレクト、またはURLを使用してください。
プロジェクトルート、または該当する場合はsrc内にproxy.ts(または.js)ファイルを作成して、pagesまたはappと同じレベルに配置してください。
pageExtensionsをカスタマイズしている場合(例:.page.tsまたは.page.jsに変更した場合)は、それに応じてファイルをproxy.page.tsまたはproxy.page.jsと名付けてください。
import { NextResponse, NextRequest } from 'next/server'
 
// この関数は、内部で`await`を使用する場合、`async`としてマークできます
export function proxy(request: NextRequest) {
  return NextResponse.redirect(new URL('/home', request.url))
}
 
export const config = {
  matcher: '/about/:path*',
}エクスポート
Proxy関数
ファイルは単一の関数をエクスポートする必要があり、デフォルトエクスポートまたはproxyという名前付きエクスポートのいずれかです。同じファイル内の複数のProxyはサポートされていません。
// デフォルトエクスポートの例
export default function proxy(request) {
  // Proxyロジック
}Configオブジェクト(オプション)
オプションとして、Proxy関数と一緒にconfigオブジェクトをエクスポートできます。このオブジェクトには、Proxyを適用するパスを指定するmatcherが含まれます。
Matcher
matcherオプションを使用すると、Proxyを実行する特定のパスをターゲットにできます。これらのパスは複数の方法で指定できます。
- 単一のパスの場合:文字列を直接使用してパスを定義します(例:'/about')。
- 複数のパスの場合:配列を使用して複数のパスをリストします(例:matcher: ['/about', '/contact'])。これにより、Proxyは/aboutと/contactの両方に適用されます。
export const config = {
  matcher: ['/about/:path*', '/dashboard/:path*'],
}さらに、matcherオプションは正規表現を使用した複雑なパス指定(例:matcher: ['/((?!api|_next/static|_next/image|.*\\.png$).*)'])をサポートしており、どのパスを含めるか除外するかを正確に制御できます。
matcherオプションは次のキーを持つオブジェクトの配列を受け入れます。
- source:リクエストパスをマッチングするために使用するパスまたはパターン。直接的なパスマッチングの場合は文字列、より複雑なマッチングの場合はパターンになります。
- regexp(オプション):ソースに基づいてマッチングを微調整する正規表現文字列。どのパスを含めるか除外するかについて追加の制御を提供します。
- locale(オプション):- falseに設定した場合、パスマッチングでロケールベースのルーティングを無視するブール値。
- has(オプション):ヘッダー、クエリパラメータ、またはクッキーなどの特定のリクエスト要素の存在に基づいて条件を指定します。
- missing(オプション):ヘッダーやクッキーなどの特定のリクエスト要素が不在である条件に焦点を当てます。
export const config = {
  matcher: [
    {
      source: '/api/*',
      regexp: '^/api/(.*)',
      locale: false,
      has: [
        { type: 'header', key: 'Authorization', value: 'Bearer Token' },
        { type: 'query', key: 'userId', value: '123' },
      ],
      missing: [{ type: 'cookie', key: 'session', value: 'active' }],
    },
  ],
}設定された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ドキュメントで詳細を確認してください。
補足:
matcherの値は定数である必要があり、ビルド時に静的に分析できます。変数などの動的な値は無視されます。- 後方互換性のため、Next.jsは常に
/publicを/public/indexと見なします。したがって、/public/:pathのmatcherはマッチします。
パラメータ
request
Proxyを定義する場合、デフォルトエクスポート関数は単一のパラメータrequestを受け入れます。このパラメータはNextRequestのインスタンスであり、受信するHTTPリクエストを表します。
import type { NextRequest } from 'next/server'
 
export function proxy(request: NextRequest) {
  // Proxyロジックはここに記述します
}補足:
NextRequestはNext.js Proxyの受信HTTPリクエストを表すタイプですが、NextResponseはHTTPレスポンスを操作して送り返すために使用するクラスです。
NextResponse
NextResponse APIを使用すると、以下を実行できます。
- 受信するリクエストを別のURLにredirect(リダイレクト)する
- 指定されたURLを表示することでレスポンスをrewrite(書き換える)
- APIルート、getServerSideProps、およびrewriteの宛先についてリクエストヘッダーを設定する
- レスポンスクッキーを設定する
- レスポンスヘッダーを設定する
Proxyからレスポンスを生成するには、以下の方法があります。
- レスポンスを生成するルート(PageまたはEdge APIルート)にrewrite(書き換える)
- NextResponseを直接返す。レスポンスの生成を参照してください
実行順序
Proxyはプロジェクト内のすべてのルートに対して呼び出されます。このため、matcherを使用して特定のルートを正確にターゲットにするか除外することが重要です。以下が実行順序です。
- next.config.jsの- headers
- next.config.jsの- redirects
- Proxy(rewrites、redirectsなど)
- next.config.jsの- beforeFiles(- rewrites)
- ファイルシステムルート(public/、_next/static/、pages/、app/など)
- next.config.jsの- afterFiles(- rewrites)
- ダイナミックルート(/blog/[slug])
- next.config.jsの- fallback(- rewrites)
ランタイム
ProxyはデフォルトでNode.jsランタイムを使用します。runtime設定オプションはProxyファイルでは利用できません。Proxyでruntime設定オプションを設定するとエラーがスローされます。
高度なProxyフラグ
Next.js v13.1では、Proxyの高度なユースケースに対応するためのskipMiddlewareUrlNormalizeとskipTrailingSlashRedirectという2つの追加フラグが導入されました。
skipTrailingSlashRedirectは、末尾のスラッシュを追加または削除するためのNext.jsリダイレクトを無効にします。これにより、Proxy内でカスタム処理を行って、一部のパスについては末尾のスラッシュを保持し、他のパスについては保持しないことができます。これにより、段階的な移行が簡単になります。
module.exports = {
  skipTrailingSlashRedirect: true,
}const legacyPrefixes = ['/docs', '/blog']
 
export default async function proxy(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 proxy(req) {
  const { pathname } = req.nextUrl
 
  // GET /_next/data/build-id/hello.json
 
  console.log(pathname)
  // このフラグで /_next/data/build-id/hello.json になります
  // このフラグなしでは /hello に正規化されます
}例
条件分岐
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
 
export function proxy(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))
  }
}クッキーの使用
クッキーは通常のヘッダーです。Requestでは、Cookieヘッダーに格納されます。Responseでは、Set-Cookieヘッダーに格納されます。Next.jsは、NextRequestとNextResponseのcookies拡張を通じて、これらのクッキーにアクセスして操作するための便利な方法を提供しています。
- 受信するリクエストについて、cookiesにはget、getAll、set、およびdeleteクッキーメソッドが付属しています。hasを使用してクッキーの存在を確認でき、clearを使用してすべてのクッキーを削除できます。
- 送信するレスポンスについて、cookiesにはget、getAll、set、およびdeleteメソッドがあります。
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
 
export function proxy(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
}ヘッダーの設定
NextResponse APIを使用してリクエストおよびレスポンスヘッダーを設定できます(リクエストヘッダーの設定はNext.js v13.0.0以降で利用可能です)。
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
 
export function proxy(request: NextRequest) {
  // リクエストヘッダーをクローンして、新しいヘッダー`x-hello-from-proxy1`を設定します
  const requestHeaders = new Headers(request.headers)
  requestHeaders.set('x-hello-from-proxy1', 'hello')
 
  // NextResponse.nextでもリクエストヘッダーを設定できます
  const response = NextResponse.next({
    request: {
      // 新しいリクエストヘッダー
      headers: requestHeaders,
    },
  })
 
  // 新しいレスポンスヘッダー`x-hello-from-proxy2`を設定します
  response.headers.set('x-hello-from-proxy2', 'hello')
  return response
}このコードスニペットは以下を使用しています。
- NextResponse.next({ request: { headers: requestHeaders } })で- requestHeadersをアップストリームで利用可能にします
- ではなく NextResponse.next({ headers: requestHeaders })はクライアントにrequestHeadersを利用可能にします
詳細はNextResponse Proxyのヘッダーを参照してください。
補足:大きなヘッダーを設定することは避けてください。バックエンドのWebサーバー設定によっては、431 Request Header Fields Too Largeエラーが発生する可能性があります。
CORS
Proxyでクロスオリジンリクエスト(シンプルリクエストとプリフライトリクエストを含む)を許可するように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 proxy(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*',
}レスポンスの生成
ResponseまたはNextResponseインスタンスを返すことでProxyから直接レスポンスを生成できます。(これはNext.js v13.1.0以降で利用可能です)
import type { NextRequest } from 'next/server'
import { isAuthenticated } from '@lib/auth'
 
// Proxyを`/api/`で始まるパスに制限します
export const config = {
  matcher: '/api/:function*',
}
 
export function proxy(request: NextRequest) {
  // 認証関数を呼び出してリクエストを確認します
  if (!isAuthenticated(request)) {
    // エラーメッセージを示すJSONでレスポンスします
    return Response.json(
      { success: false, message: 'authentication failed' },
      { status: 401 }
    )
  }
}負の一致
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配列、またはその両方の組み合わせを使用して、特定のリクエストについてProxyをバイパスできます。
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' }],
    },
  ],
}waitUntilとNextFetchEvent
NextFetchEventオブジェクトはネイティブなFetchEventオブジェクトを拡張し、waitUntil()メソッドを含みます。
waitUntil()メソッドはPromiseを引数として受け取り、Promiseが解決されるまでProxyの寿命を拡張します。これはバックグラウンドで作業を実行する場合に役立ちます。
import { NextResponse } from 'next/server'
import type { NextFetchEvent, NextRequest } from 'next/server'
 
export function proxy(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 15.1以降、next/experimental/testing/serverパッケージには、Proxyファイルをユニットテストするのに役立つユーティリティが含まれています。Proxyのユニットテストは、必要なパスにのみ実行されること、およびカスタムルーティングロジックが本番環境に到達する前に機能することを確認するのに役立ちます。
unstable_doesProxyMatch関数を使用して、提供されたURL、ヘッダー、およびクッキーについてProxyが実行されるかどうかをアサートできます。
import { unstable_doesProxyMatch } from 'next/experimental/testing/server'
 
expect(
  unstable_doesProxyMatch({
    config,
    nextConfig,
    url: '/test',
  })
).toEqual(false)Proxy関数全体もテストできます。
import { isRewrite, getRewrittenUrl } from 'next/experimental/testing/server'
 
const request = new NextRequest('https://nextjs.org/docs')
const response = await proxy(request)
expect(isRewrite(response)).toEqual(true)
expect(getRewrittenUrl(response)).toEqual('https://other-domain.com/docs')
// getRedirectUrlも使用できます。レスポンスがリダイレクトの場合プラットフォームサポート
| デプロイオプション | サポート状況 | 
|---|---|
| Node.jsサーバー | はい | 
| Dockerコンテナ | はい | 
| 静的エクスポート | いいえ | 
| アダプター | プラットフォーム依存 | 
Next.jsをセルフホスティングする場合のProxyの設定方法を確認してください。
Proxyへの移行
変更の理由
middlewareの名前変更の理由は、「middleware」という用語はExpress.jsミドルウェアと混同されやすく、その目的の誤解につながるためです。また、Middlewareは非常に有能であるため、その使用を推奨する可能性があります。しかし、この機能は最後の手段として使用することをお勧めします。
Next.jsは、開発者がMiddlewareを使用せずに目標を達成できるより優れたAPIを備えた、より優れたエルゴノミクスを提供することに進んでいます。これがmiddlewareの名前変更の理由です。
なぜ「Proxy」か
Proxyという名前は、Middlewareが何ができるかを明確にしています。「proxy」という用語は、アプリの前に設置されたネットワーク境界があることを意味します。これはMiddlewareの動作です。また、MiddlewareはデフォルトでEdge Runtimeで実行され、クライアントにより近く、アプリのリージョンとは別に実行できます。これらの動作は「proxy」という用語と適切に一致し、この機能の目的をより明確に提供します。
移行方法
Middlewareに依存する以外に選択肢がない場合は、ユーザーはMiddlewareの使用を避けることをお勧めします。私たちの目標は、Middlewareを使用せずに目標を達成できるように、より優れたエルゴノミクスを備えたAPIを提供することです。
「middleware」という用語は、ユーザーがExpress.jsミドルウェアと混同する可能性が高く、誤用につながる可能性があります。私たちの方向性を明確にするため、ファイル規約を「proxy」に名前変更しています。これは、Middlewareから遠ざかり、その過負荷な機能を分解し、Proxyの目的を明確にしていることを強調しています。
Next.jsはmiddleware.tsからproxy.tsへの移行を支援するためのコードモッドを提供しています。次のコマンドを実行して移行できます。
npx @next/codemod@canary middleware-to-proxy .このコードモッドはファイルと関数名をmiddlewareからproxyに名前変更します。
// middleware.ts -> proxy.ts
 
- export function middleware() {
+ export function proxy() {バージョン履歴
| バージョン | 変更内容 | 
|---|---|
| v16.0.0 | Middlewareは廃止され、Proxyに名前変更されました | 
| v15.5.0 | MiddlewareはNode.jsランタイムを使用できるようになりました(安定版) | 
| v15.2.0 | MiddlewareはNode.jsランタイムを使用できるようになりました(実験的) | 
| v13.1.0 | 高度なMiddlewareフラグが追加されました | 
| v13.0.0 | Middlewareはリクエストヘッダー、レスポンスヘッダーを変更し、レスポンスを送信できるようになりました | 
| v12.2.0 | Middlewareは安定版です。アップグレードガイドを参照してください | 
| v12.0.9 | Edge Runtimeで絶対URLを強制します(PR) | 
| v12.0.0 | Middleware(ベータ版)が追加されました |