Next.js 16 Middleware 完全ガイド 2026年版: Edge Runtime、認証パターン、面接対策

Next.js 16 Middlewareの動作原理、認証パターン、Edge Runtimeの制限と回避策、そして面接でよく聞かれる質問を実践的なコード例とともに解説します。

Next.js 16 Middleware 完全ガイド 2026年版: Edge Runtime、認証パターン、面接対策

Next.js 16のMiddlewareは、すべてのリクエストがサーバーに到達する前に実行される強力な機能です。認証、ローカライゼーション、リクエスト操作において最初の防衛ラインとして機能します。本番アプリケーションの構築に不可欠であり、フロントエンド面接でも頻出のトピックとなっています。

MiddlewareはEdgeで実行される

Next.js 16のMiddlewareはデフォルトでEdge Runtimeで実行されます。これはユーザーに近いデータセンターで実行されることを意味し、コールドスタートはミリ秒以下です。ページがレンダリングされる前に行う必要がある認証チェックやリダイレクトに最適です。

Next.js 16 Middlewareの動作原理

Next.js 16のMiddlewareは、リクエストがルートハンドラーやページコンポーネントに到達する前にインターセプトします。Middlewareファイルはプロジェクトのルート(app/またはpages/の隣)に配置し、NextRequestオブジェクトを受け取るデフォルト関数をエクスポートする必要があります。

Edge Runtimeの制約により、MiddlewareではfsBufferなどのNode.js APIを使用できません。Cloudflare Workersと同様のV8 isolatesで実行されるため、グローバル配信が可能ですが、利用可能なAPIはWeb Standardsに限定されます。

middleware.tstypescript
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'

export function middleware(request: NextRequest) {
  // Access request headers, cookies, and URL
  const token = request.cookies.get('session')?.value
  const pathname = request.nextUrl.pathname

  // Log for debugging (visible in Vercel logs)
  console.log(`Middleware: ${request.method} ${pathname}`)

  // Continue to the route handler
  return NextResponse.next()
}

// Configure which paths trigger middleware
export const config = {
  matcher: ['/((?!api|_next/static|_next/image|favicon.ico).*)'],
}

matcher設定は、どのパスがMiddlewareをトリガーするかを定義する正規表現のような構文を使用します。これがないと、静的アセットを含むすべてのリクエストでMiddlewareが実行されます。

Next.js Middlewareによる認証パターン

Middlewareが認証に優れている理由は、ページコードが実行される前に動作するからです。有効なセッションを持たないユーザーは、保護されたコンテンツを一瞬も見ることがありません。これは、リダイレクト前に保護されたコンテンツが一瞬表示される可能性があるクライアントサイドの認証チェックとは異なります。

以下のパターンは、セッショントークンを検証し、未認証ユーザーをリダイレクトします。Cookieまたはヘッダーにセッションデータを保存する任意の認証プロバイダーと統合できます。

middleware.tstypescript
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'

const protectedPaths = ['/dashboard', '/settings', '/profile']
const authPaths = ['/login', '/signup']

export async function middleware(request: NextRequest) {
  const { pathname } = request.nextUrl
  const sessionToken = request.cookies.get('session')?.value

  // Check if path requires authentication
  const isProtectedPath = protectedPaths.some(path => 
    pathname.startsWith(path)
  )
  const isAuthPath = authPaths.some(path => 
    pathname.startsWith(path)
  )

  // Validate session token (call auth service)
  const isValidSession = sessionToken 
    ? await validateSession(sessionToken)
    : false

  // Redirect unauthenticated users to login
  if (isProtectedPath && !isValidSession) {
    const loginUrl = new URL('/login', request.url)
    loginUrl.searchParams.set('callbackUrl', pathname)
    return NextResponse.redirect(loginUrl)
  }

  // Redirect authenticated users away from auth pages
  if (isAuthPath && isValidSession) {
    return NextResponse.redirect(new URL('/dashboard', request.url))
  }

  return NextResponse.next()
}

async function validateSession(token: string): Promise<boolean> {
  // Call auth API endpoint or decode JWT
  // Edge-compatible: use fetch, not Node.js crypto
  try {
    const response = await fetch('https://api.example.com/validate', {
      headers: { Authorization: `Bearer ${token}` },
    })
    return response.ok
  } catch {
    return false
  }
}

callbackUrlパラメータは元の宛先を保持するため、ログイン後にユーザーは最初にリクエストしたページに移動できます。

パスマッチングとMatcher設定

matcher設定は、どのリクエストがMiddlewareをトリガーするかを決定します。これを正しく設定しないと、パフォーマンスの問題(すべての静的アセットでMiddlewareが実行される)やセキュリティホール(保護されたパスで認証チェックがトリガーされない)が発生します。

Next.js 16は3つのmatcher構文をサポートしています:文字列パス、パラメータ付きパスパターン、負の先読みを持つ正規表現のようなパターンです。

middleware.tstypescript
// Option 1: Simple string paths
export const config = {
  matcher: ['/dashboard/:path*', '/api/:path*'],
}

// Option 2: Exclude static assets with negative lookahead
export const config = {
  matcher: ['/((?!_next/static|_next/image|favicon.ico|public/).*)'],
}

// Option 3: Conditional matching with has/missing
export const config = {
  matcher: [
    {
      source: '/api/:path*',
      has: [{ type: 'header', key: 'Authorization' }],
    },
    {
      source: '/dashboard/:path*',
      missing: [{ type: 'cookie', key: 'session' }],
    },
  ],
}

オプション3のhasmissing条件は、Middlewareが実行される前にロジックを追加します。これはMiddleware関数内でチェックするよりも効率的です。

React / Next.jsの面接対策はできていますか?

インタラクティブなシミュレーター、flashcards、技術テストで練習しましょう。

Middlewareでのリクエストとレスポンスの操作

認証以外にも、Middlewareはルートハンドラーに到達する前のリクエストと、クライアントに到達する前のレスポンスを変更できます。一般的なユースケースには、セキュリティヘッダーの追加、A/Bテスト用のURL書き換え、位置情報データの注入があります。

NextResponseクラスは、各変更タイプのメソッドを提供します。RewriteはブラウザのURLを変更せずに宛先を変更し、Redirectは両方を更新します。

middleware.tstypescript
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'

export function middleware(request: NextRequest) {
  const response = NextResponse.next()

  // Add security headers to all responses
  response.headers.set('X-Frame-Options', 'DENY')
  response.headers.set('X-Content-Type-Options', 'nosniff')
  response.headers.set(
    'Referrer-Policy', 
    'strict-origin-when-cross-origin'
  )

  // Inject geolocation into request headers for route handlers
  const geo = request.geo
  if (geo?.country) {
    response.headers.set('x-user-country', geo.country)
    response.headers.set('x-user-city', geo.city ?? 'unknown')
  }

  return response
}

Geolocationデータ(request.geo)はVercelのEdge Networkによって設定されます。セルフホストのデプロイでは、ホスティングプロバイダーを通じて設定するか、サードパーティのIP位置情報サービスを使用する必要があります。

A/BテストとFeature FlagのためのURL Rewriting

MiddlewareのRewriteを使用すると、クライアントに知られることなく、Cookie、ヘッダー、またはランダム割り当てに基づいて異なるページを提供できます。このパターンはA/Bテストフレームワークや段階的な機能ロールアウトを支えています。

RewriteはEdgeで行われるため、両方のバリアントは同じURLを持ち、適切な場合はEdgeキャッシングの恩恵を受けます。

middleware.tstypescript
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'

export function middleware(request: NextRequest) {
  const { pathname } = request.nextUrl

  // Check for existing variant assignment
  let variant = request.cookies.get('ab-variant')?.value

  // Assign variant if not set (50/50 split)
  if (!variant && pathname === '/pricing') {
    variant = Math.random() < 0.5 ? 'control' : 'treatment'
    const response = NextResponse.rewrite(
      new URL(`/pricing/${variant}`, request.url)
    )
    response.cookies.set('ab-variant', variant, {
      maxAge: 60 * 60 * 24 * 30, // 30 days
      httpOnly: true,
    })
    return response
  }

  // Rewrite to assigned variant
  if (variant && pathname === '/pricing') {
    return NextResponse.rewrite(
      new URL(`/pricing/${variant}`, request.url)
    )
  }

  return NextResponse.next()
}

Cookieはバリアント割り当てを永続化し、ユーザーが再訪問時に同じバージョンを見ることを保証します。30日間の有効期限は、実験の一貫性と新しい実験へのユーザー再割り当て能力のバランスを取ります。

Edge Runtimeの制限と回避策

Edge Runtimeは、Node.jsとの互換性を犠牲にして、グローバル配信と高速なコールドスタートを実現します。Middlewareは、bcryptjsonwebtoken(RS256使用時)、TCPソケットに依存するデータベースクライアントなど、Node.js固有のモジュールをインポートできません。

Next.js Edge RuntimeドキュメントにサポートされているAPIのリストがあります。Web Crypto、fetch、およびほとんどのWeb Platform APIは動作します。重い計算はNode.jsで実行されるAPIルートに移動する必要があります。

middleware.ts - JWT validation without jsonwebtokentypescript
import { jwtVerify } from 'jose' // Edge-compatible JWT library
import type { NextRequest } from 'next/server'

const JWT_SECRET = new TextEncoder().encode(process.env.JWT_SECRET!)

export async function middleware(request: NextRequest) {
  const token = request.cookies.get('token')?.value

  if (!token) {
    return redirectToLogin(request)
  }

  try {
    // jose library works in Edge Runtime
    const { payload } = await jwtVerify(token, JWT_SECRET)
    
    // Token is valid, check expiration
    if (payload.exp && payload.exp < Date.now() / 1000) {
      return redirectToLogin(request)
    }

    return NextResponse.next()
  } catch {
    // Invalid token signature or format
    return redirectToLogin(request)
  }
}

function redirectToLogin(request: NextRequest) {
  const url = new URL('/login', request.url)
  url.searchParams.set('callbackUrl', request.nextUrl.pathname)
  return NextResponse.redirect(url)
}

joseライブラリはEdge互換のJWT操作を提供します。Node.jsのcryptoモジュールを避け、内部でWeb Cryptoを使用しています。

Next.js Middleware面接でよく聞かれる質問

面接官は、リクエストライフサイクル、セキュリティパターン、Edge computing制約の理解を評価するためにMiddlewareについて質問します。これらの質問は、Next.jsを使用するシニアフロントエンドやフルスタックの役職で出題されます。

Middlewareはリクエストライフサイクルのどこで実行されますか?

Middlewareは、リクエストがNext.jsサーバーに到達した後、ルートマッチングやページレンダリングの前に実行されます。Vercelでは、ユーザーに最も近いデータセンターであるEdgeで実行されます。この配置により、無効なリクエストがオリジンサーバーに到達しないため、認証とリダイレクトに最適です。

MiddlewareがAPIルートと比較してできないことは何ですか?

MiddlewareはNode.jsではなくEdge Runtimeで実行されます。Node.js組み込みモジュール(fspath、特定のアルゴリズムを使用するcrypto)、TCPベースのデータベースクライアント、Node.js APIに依存するnpmパッケージは使用できません。重い計算はAPIルートまたはServer Componentsで行う必要があります。

ブロッキングなしでMiddlewareで認証を処理するにはどうすればよいですか?

パターンは、セッションCookieの確認、認証エンドポイントへの検証またはJWT署名の検証、無効な場合のリダイレクトで構成されます。すべてのリクエストをブロックしないようにするには、matcher設定を使用して保護されたパスでのみMiddlewareを実行します。ステートレスJWT検証は、すべてのリクエストで外部認証サービスを呼び出すよりも高速です。

Middlewareでエラーが発生した場合はどうなりますか?

Middlewareでの未処理エラーは、クライアントに500レスポンスを返します。ページはレンダリングされません。Middlewareロジックをtry-catchでラップし、フォールバックレスポンス(多くの場合NextResponse.next())を返すことで、完全なリクエスト失敗を防ぎます。続行する前にエラーをログに記録すると、デバッグに役立ちます。

これらの概念についてより深い練習をするには、Next.js Middlewareと認証の面接問題モジュールで追加のシナリオをカバーしています。

開発と本番環境でのMiddlewareデバッグ

Middlewareのバグは、コードがブラウザDevToolsではなくEdgeで実行されるため、厄介です。開発モードではMiddlewareログがターミナルに表示されますが、本番環境では適切なログインフラストラクチャが必要です。

以下のアプローチは、機密データを公開せずに問題をデバッグするのに十分なコンテキストでMiddleware実行をログに記録します。

middleware.tstypescript
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'

export function middleware(request: NextRequest) {
  const start = Date.now()
  const requestId = crypto.randomUUID()

  // Log request start
  console.log(JSON.stringify({
    type: 'middleware_request',
    requestId,
    method: request.method,
    path: request.nextUrl.pathname,
    userAgent: request.headers.get('user-agent')?.slice(0, 100),
    country: request.geo?.country,
  }))

  // Process request
  const response = processRequest(request)

  // Log completion
  console.log(JSON.stringify({
    type: 'middleware_response',
    requestId,
    duration: Date.now() - start,
    status: response.status,
  }))

  // Pass request ID to route handlers
  response.headers.set('x-request-id', requestId)
  return response
}

function processRequest(request: NextRequest): NextResponse {
  // Middleware logic here
  return NextResponse.next()
}

Vercelでは、これらのログがFunctionsタブに表示されます。セルフホストのデプロイでは、Datadogなどのサービスにログを送信するか、ホスティングプラットフォームが取り込める構造化ログを使用する必要があります。

Middlewareパフォーマンスのベストプラクティス

Middlewareは、マッチしたすべてのリクエストで実行されます。遅いMiddleware関数は、すべてのページ読み込みにレイテンシを追加します。目標は、ほとんどのリクエストで5ms未満でMiddleware実行を完了することです。

  • 非同期操作を最小限に:各awaitはレイテンシを追加します。可能な場合は検証結果をキャッシュします。
  • matcher設定を使用:Middlewareロジックが不要な静的アセットとパブリックパスを除外します。
  • すべてのリクエストで外部呼び出しを避ける:認証サービスを呼び出す代わりにJWTをローカルで検証します。適切な場合はCookieに結果をキャッシュします。
  • バンドルサイズを小さく保つ:Middlewareには独自のバンドルサイズ制限があります(Vercelでは1MB)。必要なものだけをインポートします。

React Next.js技術ページでは、Middleware知識を補完するServer Componentsやデータフェッチングパターンなどの関連概念をカバーしています。

今すぐ練習を始めましょう!

面接シミュレーターと技術テストで知識をテストしましょう。

本番環境向けNext.js 16 Middlewareチェックリスト

  • Middlewareはapp/pages/内ではなく、プロジェクトルートにmiddleware.tsとして配置する
  • matcherを設定して、_next/static_next/image、パブリックアセットをMiddleware実行から除外する
  • JWT検証には、jsonwebtokenではなくjoseなどのEdge互換ライブラリを使用する
  • ログインにリダイレクトする際は、ユーザーが意図した宛先に戻れるよう、クエリパラメータにコールバックURLを保存する
  • すべてのルートで一貫した適用のため、Middlewareでセキュリティヘッダーを追加する
  • システム全体でリクエストを追跡するため、リクエストIDでMiddleware実行をログに記録する
  • トークンをローカルで検証し、署名されたCookieにユーザーデータをキャッシュすることで、外部API呼び出しを最小限に抑える
今日のチャレンジ

React / Next.js のバグを見つけられますか

実際のコード、隠れたバグ、1日1回。アカウントなしで試せます。

Anthony Fillion-Maillet

執筆

Anthony Fillion-Maillet

SharpSkill 創業者

10 年以上フルスタック開発に携わっています。SharpSkill を運営し、ここで公開される内容に責任を負っています。

2026年9月20日 更新

タグ

#nextjs
#middleware
#edge-runtime
#authentication
#typescript
#react

共有

関連記事