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

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

ミドルウェアの一般提供 (GA) に向けた改善作業の一環として、皆様からのフィードバックに基づき、ミドルウェア 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. 新しいユーザーエージェントヘルパー
  5. ページ一致データはもうありません
  6. 内部 Next.js リクエストでミドルウェアを実行する

ネストされたミドルウェアなし

変更の概要 説明

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

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

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

アップグレード方法

アプリケーションには、1つのミドルウェアファイルを宣言する必要があります。これは、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
  }
}

レスポンスボディなし

変更の概要 説明

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

次のパターンは機能しなくなります。

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ルート

以前にミドルウェアを使用してヘッダーを外部APIに転送していた場合は、Edge APIルートを使用できるようになりました。

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

Cookie APIの改訂 変更の概要
追加削除
cookies.setcookie
cookies.deleteclearCookie
cookies.getWithOptionscookies

説明

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

アップグレード方法

NextResponseには、次のcookiesインスタンスがあります。

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
}

新しいユーザーエージェントヘルパー

変更の概要 説明

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

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

アップグレード方法

変更前
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.js v12.2より前では、ミドルウェアは_nextリクエストに対して実行されませんでした。

ミドルウェアを認証に使用している場合は、認証エラー、ログインフォーム、または API ルートを表示するページへのrewrite/redirectを使用するように移行する必要があります。

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