コンテンツへスキップ
ドキュメントエラーミドルウェアアップグレードガイド

ミドルウェアアップグレードガイド

GA (General Availability) 向けにミドルウェアの改善を進めるにあたり、皆様からのフィードバックに基づいて、ミドルウェアのAPI (およびアプリケーションでのミドルウェアの定義方法) にいくつかの変更を加えました。

このアップグレードガイドは、変更点、その理由、および既存のミドルウェアを新しいAPIに移行する方法を理解するのに役立ちます。このガイドは、以下のNext.js開発者向けです。

  • 現在、ベータ版のNext.jsミドルウェア機能を使用している方
  • Next.jsの次の安定版 (v12.2) へのアップグレードを選択する方

最新リリース (npm i next@latest) を使用して、本日よりミドルウェアの利用をアップグレードできます。

注意: このガイドで説明されている変更は、Next.js 12.2 に含まれています。12.2 (またはNext.jsのcanaryビルド) に移行するまで、ネストされたミドルウェアを含む現在のサイト構造を維持できます。

ESLintを設定している場合は、npm i eslint-config-next@latest --save-dev を実行してESLintの設定をアップグレードし、Next.jsのバージョンと同じバージョンが使用されていることを確認する必要があります。変更を反映させるには、VSCodeの再起動も必要になる場合があります。

VercelでNext.jsミドルウェアを使用する

VercelでNext.jsを使用している場合、ミドルウェアを使用している既存のデプロイは引き続き機能し、ミドルウェアを使用してサイトをデプロイし続けることができます。サイトをNext.jsの次の安定版 (v12.2) にアップグレードする際には、このアップグレードガイドに従ってミドルウェアを更新する必要があります。

破壊的変更

  1. ネストされたミドルウェアの廃止
  2. レスポンスボディなし
  3. Cookies APIの刷新
  4. 新しいUser-Agentヘルパー
  5. ページマッチデータなし
  6. Next.js内部リクエストでのミドルウェアの実行

ネストされたミドルウェアの廃止

変更点の概要

  • pagesフォルダの横に単一のミドルウェアファイルを定義する
  • ファイルにアンダースコアを付ける必要なし
  • エクスポートされた設定オブジェクトを使用して、マッチングルートを定義するためにカスタムマッチャーを使用できる

説明

以前は、pagesディレクトリ内の任意のレベルに_middleware.tsファイルを作成できました。ミドルウェアの実行は、そのファイルが作成されたファイルパスに基づいていました。

お客様からのフィードバックに基づいて、このAPIを単一のルートミドルウェアに置き換えました。これにより、以下の改善がもたらされます。

  • 低遅延での高速実行: ネストされたミドルウェアでは、単一のリクエストが複数のミドルウェア関数を呼び出す可能性がありました。単一のミドルウェアは単一の関数実行を意味し、より効率的です。
  • 低コスト: ミドルウェアの使用は呼び出しごとに課金されます。ネストされたミドルウェアを使用すると、単一のリクエストが複数のミドルウェア関数を呼び出す可能性があり、リクエストごとに複数のミドルウェア料金が発生しました。単一のミドルウェアはリクエストごとに単一の呼び出しを意味し、より費用対効果が高いです。
  • ルート以外のものでもミドルウェアを簡単にフィルタリングできる: ネストされたミドルウェアでは、ミドルウェアファイルはpagesディレクトリに配置され、ミドルウェアはリクエストパスに基づいて実行されました。単一のルートミドルウェアに移行することで、リクエストパスに基づいてコードを実行できるだけでなく、cookiesやリクエストヘッダーの有無など、他の条件に基づいてミドルウェアをより便利に実行できるようになりました。
  • 確定的な実行順序: ネストされたミドルウェアでは、単一のリクエストが複数のミドルウェア関数にマッチする可能性がありました。例えば、/dashboard/users/* へのリクエストは、/dashboard/users/_middleware.ts/dashboard/_middleware.js の両方で定義されたミドルウェアを呼び出します。しかし、その実行順序は推測が困難です。単一のルートミドルウェアに移行することで、実行順序がより明確に定義されます。
  • Next.js Layouts (RFC) をサポート: 単一のルートミドルウェアへの移行は、Next.jsの新しいLayouts (RFC) のサポートに役立ちます。

アップグレード方法

アプリケーション内で単一のミドルウェアファイルを宣言する必要があります。このファイルはpagesディレクトリの隣に配置し、_プレフィックスなしで命名する必要があります。ミドルウェアファイルは引き続き.tsまたは.jsのいずれかの拡張子を持つことができます。

ミドルウェアはアプリ内のすべてのルートに対して呼び出され、カスタムマッチャーを使用してマッチングフィルタを定義できます。以下は、/about/* および /dashboard/:path* に対してトリガーされるミドルウェアの例で、カスタムマッチャーはエクスポートされた設定オブジェクトで定義されています

middleware.ts
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
 
export function middleware(request: NextRequest) {
  return NextResponse.rewrite(new URL('/about-2', request.url))
}
 
// Supports both a single string value or an array of matchers
export const config = {
  matcher: ['/about/:path*', '/dashboard/:path*'],
}

マッチャーの設定では完全な正規表現も許可されているため、否定先読みや文字一致などのマッチングもサポートされています。特定のパス以外すべてにマッチする否定先読みの例をこちらで確認できます

middleware.ts
export const config = {
  matcher: [
    /*
     * Match all request paths except for the ones starting with:
     * - api (API routes)
     * - _next/static (static files)
     * - favicon.ico (favicon file)
     */
    '/((?!api|_next/static|favicon.ico).*)',
  ],
}

設定オプションはすべてのリクエストで呼び出されないため推奨されますが、条件文を使用して特定のパスに一致する場合にのみミドルウェアを実行することもできます。条件文を使用する利点の1つは、ミドルウェアが実行される明示的な順序を定義できることです。以下の例は、以前ネストされていた2つのミドルウェアをマージする方法を示しています

middleware.ts
import type { NextRequest } from 'next/server'
 
export function middleware(request: NextRequest) {
  if (request.nextUrl.pathname.startsWith('/about')) {
    // This logic is only applied to /about
  }
 
  if (request.nextUrl.pathname.startsWith('/dashboard')) {
    // This logic is only applied to /dashboard
  }
}

レスポンスボディなし

変更点の概要

  • ミドルウェアはレスポンスボディを生成できなくなりました
  • ミドルウェアが実際にボディで応答すると、ランタイムエラーがスローされます
  • rewrite/redirect を使用して応答を処理するページ/APIに移行する

説明

クライアントサイドとサーバーサイドのナビゲーションの違いを尊重し、開発者が安全でないミドルウェアを構築しないようにするために、ミドルウェアでレスポンスボディを送信する機能を削除します。これにより、ミドルウェアはrewriteredirect、または受信リクエストの変更 (例: クッキーの設定) のみに使用されるようになります。

以下のパターンは機能しなくなります

new Response('a text value')
new Response(streamOrBuffer)
new Response(JSON.stringify(obj), { headers: 'application/json' })
NextResponse.json()

アップグレード方法

ミドルウェアが応答(認証など)に使用されるケースでは、認証エラー、ログインフォーム、またはAPIルートを表示するページへのrewrite/redirectの使用に移行する必要があります。

変更前

pages/_middleware.ts
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
import { isAuthValid } from './lib/auth'
 
export function middleware(request: NextRequest) {
  // Example function to validate auth
  if (isAuthValid(request)) {
    return NextResponse.next()
  }
 
  return NextResponse.json({ message: 'Auth required' }, { status: 401 })
}

変更後

middleware.ts
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
import { isAuthValid } from './lib/auth'
 
export function middleware(request: NextRequest) {
  // Example function to validate auth
  if (isAuthValid(request)) {
    return NextResponse.next()
  }
 
  const loginUrl = new URL('/login', request.url)
  loginUrl.searchParams.set('from', request.nextUrl.pathname)
 
  return NextResponse.redirect(loginUrl)
}

Edge API Routes

以前にミドルウェアを使用して外部APIにヘッダーを転送していた場合、現在はEdge API Routesを使用できます

pages/api/proxy.ts
import { type NextRequest } from 'next/server'
 
export const config = {
  runtime: 'edge',
}
 
export default async function handler(req: NextRequest) {
  const authorization = req.cookies.get('authorization')
  return fetch('https://backend-api.com/api/protected', {
    method: req.method,
    headers: {
      authorization,
    },
    redirect: 'manual',
  })
}

Cookies APIの刷新

変更点の概要

追加削除
cookies.setcookie
cookies.deleteclearCookie
cookies.getWithOptionscookies

説明

ベータ版のフィードバックに基づき、NextRequestNextResponseのCookies APIをget/setモデルにより合わせるように変更しています。Cookies APIはMapを拡張しており、entriesvaluesのようなメソッドを含みます。

アップグレード方法

NextResponseは現在、以下のcookiesインスタンスを持っています

  • cookies.delete
  • cookies.set
  • cookies.getWithOptions

Mapからの他の拡張メソッドも同様です。

変更前

pages/_middleware.ts
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
 
export function middleware(request: NextRequest) {
  // create an instance of the class to access the public methods. This uses `next()`,
  // you could use `redirect()` or `rewrite()` as well
  let response = NextResponse.next()
  // get the cookies from the request
  let cookieFromRequest = request.cookies['my-cookie']
  // set the `cookie`
  response.cookie('hello', 'world')
  // set the `cookie` with options
  const cookieWithOptions = response.cookie('hello', 'world', {
    path: '/',
    maxAge: 1000 * 60 * 60 * 24 * 7,
    httpOnly: true,
    sameSite: 'strict',
    domain: 'example.com',
  })
  // clear the `cookie`
  response.clearCookie('hello')
 
  return response
}

変更後

middleware.ts
export function middleware() {
  const response = new NextResponse()
 
  // set a cookie
  response.cookies.set('vercel', 'fast')
 
  // set another cookie with options
  response.cookies.set('nextjs', 'awesome', { path: '/test' })
 
  // get all the details of a cookie
  const { value, ...options } = response.cookies.getWithOptions('vercel')
  console.log(value) // => 'fast'
  console.log(options) // => { name: 'vercel', Path: '/test' }
 
  // deleting a cookie will mark it as expired
  response.cookies.delete('vercel')
 
  return response
}

新しいUser-Agentヘルパー

変更点の概要

  • リクエストオブジェクトからユーザーエージェントにアクセスできなくなりました
  • ミドルウェアのサイズを17kb削減するために、新しいuserAgentヘルパーを追加しました

説明

ミドルウェアのサイズを削減するため、リクエストオブジェクトからユーザーエージェントを抽出し、新しいヘルパーuserAgentを作成しました。

このヘルパーはnext/serverからインポートされ、ユーザーエージェントの使用を選択できるようにします。このヘルパーは、リクエストオブジェクトから利用可能だったのと同じプロパティへのアクセスを提供します。

アップグレード方法

  • next/serverからuserAgentヘルパーをインポートする
  • 作業に必要なプロパティを分割代入する

変更前

pages/_middleware.ts
import { NextRequest, NextResponse } from 'next/server'
 
export function middleware(request: NextRequest) {
  const url = request.nextUrl
  const viewport = request.ua.device.type === 'mobile' ? 'mobile' : 'desktop'
  url.searchParams.set('viewport', viewport)
  return NextResponse.rewrite(url)
}

変更後

middleware.ts
import { NextRequest, NextResponse, userAgent } from 'next/server'
 
export function middleware(request: NextRequest) {
  const url = request.nextUrl
  const { device } = userAgent(request)
  const viewport = device.type === 'mobile' ? 'mobile' : 'desktop'
  url.searchParams.set('viewport', viewport)
  return NextResponse.rewrite(url)
}

ページマッチデータなし

変更点の概要

  • 特定のページマッチのためにミドルウェアが呼び出されているかどうかを確認するには、URLPatternを使用する

説明

現在、ミドルウェアはNext.jsのルートマニフェスト(内部設定)に基づいて、ページの資産を配信しているかどうかを推定します。この値はrequest.pageを通じて公開されます。

ページおよびアセットのマッチングをより正確にするため、現在、Web標準のURLPattern APIを使用しています。

アップグレード方法

特定のページマッチのためにミドルウェアが呼び出されているかどうかを確認するには、URLPatternを使用してください。

変更前

pages/_middleware.ts
import { NextResponse } from 'next/server'
import type { NextRequest, NextFetchEvent } from 'next/server'
 
export function middleware(request: NextRequest, event: NextFetchEvent) {
  const { params } = event.request.page
  const { locale, slug } = params
 
  if (locale && slug) {
    const { search, protocol, host } = request.nextUrl
    const url = new URL(`${protocol}//${locale}.${host}/${slug}${search}`)
    return NextResponse.redirect(url)
  }
}

変更後

middleware.ts
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
 
const PATTERNS = [
  [
    new URLPattern({ pathname: '/:locale/:slug' }),
    ({ pathname }) => pathname.groups,
  ],
]
 
const params = (url) => {
  const input = url.split('?')[0]
  let result = {}
 
  for (const [pattern, handler] of PATTERNS) {
    const patternResult = pattern.exec(input)
    if (patternResult !== null && 'pathname' in patternResult) {
      result = handler(patternResult)
      break
    }
  }
  return result
}
 
export function middleware(request: NextRequest) {
  const { locale, slug } = params(request.url)
 
  if (locale && slug) {
    const { search, protocol, host } = request.nextUrl
    const url = new URL(`${protocol}//${locale}.${host}/${slug}${search}`)
    return NextResponse.redirect(url)
  }
}

Next.js内部リクエストでのミドルウェアの実行

変更点の概要

  • ミドルウェアは_nextを含むすべてのリクエストに対して実行されます

説明

Next.js v12.2より前は、_nextリクエストに対してミドルウェアは実行されませんでした。

ミドルウェアが認証に使用されるケースでは、認証エラー、ログインフォーム、またはAPIルートを表示するページへのrewrite/redirectの使用に移行する必要があります。

rewrite/redirectの使用に移行する方法の例については、レスポンスボディなしを参照してください。