ウェブエンジニア問題集
第9章

ProxyとMiddleware — リクエストの入口で認証・リダイレクトを制御する

15
この章の目次開く

Route Handlersは /api/users のようなAPIエンドポイントを作る仕組みでした。では、ページやAPIに到達するに、ログイン状態を見てリダイレクトしたり、特定のURLだけアクセス制御したりしたい場合はどうすればよいでしょうか。

Next.jsでは、そうした「リクエストの入口で行う処理」を proxy.ts で書けます。Next.js 15以前の記事では middleware.ts と呼ばれていることが多いですが、Next.js 16では middleware.ts は非推奨になり、proxy.ts にリネームされています。

学習者学習者

ログインしていない人を /login に飛ばしたいだけなら、各ページでチェックすればいいのかな?

先生先生

ページごとに書くと漏れやすいよ。入口でまとめて判定したいときに proxy.ts が使える。ただし強力なので、何でもここに詰め込まないのが大事。

リクエストの入口でチェックしているイメージ


Proxyとは

Proxyは、リクエストがページやRoute Handlerに届く前に実行される処理です。

ブラウザ

proxy.ts        ← ここで認証、リダイレクト、rewrite、ヘッダー操作

page.tsx / route.ts

たとえば、次のような処理に向いています。

やりたいことProxyが向いている理由
未ログインユーザーを /login に飛ばすページに入る前にまとめて判定できる
古いURLを新しいURLへリダイレクトするレンダリング前にURLを変えられる
A/Bテストや地域別のrewriteをするリクエスト情報を見て行き先を変えられる
一部APIにCORSヘッダーを付けるRoute Handlerに届く前に共通処理できる

ただし、Proxyはすべてのリクエストの入口に近い場所で動きます。便利だからといって、DBアクセスや重いビジネスロジックを入れる場所ではありません。


proxy.ts の基本形

proxy.ts は、app/ の中ではなく、プロジェクトのルート、または src/ を使っている場合は src/proxy.ts に置きます。

my-app/
├── src/
│   ├── app/
│   │   └── page.tsx
│   └── proxy.ts
└── next.config.ts

基本形は次のとおりです。

// src/proxy.ts
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
 
export function proxy(request: NextRequest) {
  const { pathname } = request.nextUrl;
 
  if (pathname.startsWith('/admin')) {
    // ここで認証チェックなどを行う
  }
 
  return NextResponse.next();
}
ts

NextResponse.next() は「特に止めずに、次の処理へ進める」という意味です。ページなら page.tsx、APIなら route.ts へ進みます。


matcher で対象URLを絞る

Proxyは入口の処理なので、対象URLを絞らないと余計なリクエストでも実行されます。実務では config.matcher で対象を明示するのが基本です。

// src/proxy.ts
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
 
export function proxy(request: NextRequest) {
  return NextResponse.next();
}
 
export const config = {
  matcher: ['/dashboard/:path*', '/admin/:path*'],
};
ts

この例では、/dashboard/admin 配下だけでProxyが動きます。


認証リダイレクトの例

ログイン済みかどうかをCookieで判定し、未ログインなら /login にリダイレクトする例です。

// src/proxy.ts
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
 
export function proxy(request: NextRequest) {
  const session = request.cookies.get('session')?.value;
 
  if (!session) {
    const loginUrl = new URL('/login', request.url);
    loginUrl.searchParams.set('next', request.nextUrl.pathname);
    return NextResponse.redirect(loginUrl);
  }
 
  return NextResponse.next();
}
 
export const config = {
  matcher: ['/dashboard/:path*', '/admin/:path*'],
};
ts

new URL('/login', request.url) と書くと、現在のオリジンを保ったまま /login のURLを作れます。next クエリに元のパスを入れておけば、ログイン後に戻す処理も作りやすくなります。


NextRequestとNextResponseの使い方

Proxyでは、Route Handlers と同じく NextRequest / NextResponse をよく使います。

URLを読む

export function proxy(request: NextRequest) {
  const pathname = request.nextUrl.pathname;
  const searchParams = request.nextUrl.searchParams;
 
  if (pathname === '/old') {
    const url = new URL('/new', request.url);
    url.search = searchParams.toString();
    return NextResponse.redirect(url);
  }
 
  return NextResponse.next();
}
ts

Cookieを読む

export function proxy(request: NextRequest) {
  const token = request.cookies.get('token')?.value;
 
  if (!token) {
    return NextResponse.redirect(new URL('/login', request.url));
  }
 
  return NextResponse.next();
}
ts

ヘッダーを追加する

export function proxy(request: NextRequest) {
  const requestHeaders = new Headers(request.headers);
  requestHeaders.set('x-from-proxy', 'true');
 
  return NextResponse.next({
    request: {
      headers: requestHeaders,
    },
  });
}
ts

NextResponse.next({ request: { headers } }) は、後続のページやRoute Handlerに渡すリクエストヘッダーを変更します。一方で、クライアントに返すレスポンスヘッダーを設定したい場合は、const response = NextResponse.next() を作ってから response.headers.set() します。


Edge Runtimeとは

Edge Runtimeは、Node.jsサーバーよりもユーザーに近い場所で軽量に処理を動かすための実行環境です。Vercelにデプロイした場合で考えると、アプリ本体が動くリージョンとは別に、CDNに近い場所でリクエストをさばくイメージです。

ただし、Edge Runtimeは「Node.jsをそのままエッジで動かす環境」ではありません。Web標準APIを中心にした、制限のあるJavaScript実行環境です。

Node.js Runtime
  - fs, crypto, net などNode.js APIを使える
  - DB接続や重いサーバー処理に向く
  - アプリのレンダリングやRoute Handlerの標準実行環境
 
Edge Runtime
  - fetch, Request, Response, Headers などWeb標準API中心
  - 起動が軽く、リクエストの入口処理に向く
  - Node.jsのネイティブAPIは使えない
学習者学習者

Edge Runtimeって速そうだけど、Node.jsと同じコードが全部動くわけではないんだね。

その通りです。fs でファイルを読む、Node.js専用のライブラリを使う、ネイティブモジュールに依存する、といった処理はEdge Runtimeでは動かないことがあります。


Edge RuntimeとNode.js Runtimeの違い

違いを実務目線で整理すると、次のようになります。

観点Node.js RuntimeEdge Runtime
主な用途レンダリング、Route Handler、DB連携、重いサーバー処理リダイレクト、rewrite、軽い認証判定、ヘッダー操作
使えるAPINode.js APIを広く使えるWeb標準API中心
fs使える使えない
require使える場面が多い基本はES Modules
npmパッケージNode.js前提のものも使いやすいEdge対応していないと動かない場合がある
DB接続向いている直接接続は避けたいことが多い
起動・配置アプリのサーバー側ユーザーに近い場所で動かしやすい

Edge Runtimeは「軽い入口処理」に向いています。逆に、DBへ複雑なクエリを投げる、外部APIをいくつも叩いて集約する、ファイルを読み書きする、といった処理はRoute HandlerやServer Actionsに寄せる方が安全です。


Edge RuntimeはVercel独自の概念なのか

Edge Runtimeという考え方自体は、Vercelだけのものではありません。Cloudflare Workers、Deno Deploy、Netlify Edge Functionsなど、ユーザーに近い場所で軽量なJavaScriptを実行する仕組みは複数あります。

ただし、Next.jsのEdge Runtimeとして何が使えるか、どの機能がどう動くかは、Next.jsとデプロイ先の実装に依存します。VercelではNext.jsとの統合が強く、ProxyやEdge向け機能を自然に使いやすい一方、セルフホスティングや別プラットフォームでは対応状況を確認する必要があります。

実務では、次のように考えると迷いにくいです。

  • Edge Runtimeは「Vercelだけのマーケティング用語」ではない
  • ただし、Next.jsでのEdge Runtimeの挙動はNext.jsとデプロイ先の対応に左右される
  • Node.js APIに依存する処理はEdgeに置かない
  • 認証の最終チェックやDB更新は、Route Handler / Server Actions側にも必ず置く

Next.js 16の変更 — middleware.ts から proxy.ts

Next.js 16では、従来の middleware.ts というファイル規約が非推奨になり、proxy.ts にリネームされました。
- src/middleware.ts
+ src/proxy.ts
diff

関数名も変わります。

- export function middleware(request: NextRequest) {
+ export function proxy(request: NextRequest) {
    return NextResponse.next();
  }
diff

移行用のcodemodも用意されています。

npx @next/codemod@canary middleware-to-proxy .
bash

名称変更の意図は、Expressのような「アプリ内部のmiddleware」と誤解されやすかったためです。Next.jsのProxyは、アプリのレンダリングコードの中間に差し込むというより、アプリの前段にあるネットワーク境界でリクエストを処理するものと捉える方が近いです。


Proxyに書くべき処理・書くべきでない処理

Proxyは便利ですが、何でも置ける場所ではありません。

処理Proxyに置いてよいか理由
未ログインなら /login にリダイレクト向いているページ到達前に判断したい
/old/new にリダイレクト向いているレンダリング不要
/dashboard 配下だけアクセス制御向いているmatcher で絞りやすい
複雑なDB検索避ける重くなりやすく、入口処理に向かない
ユーザーの権限を最終確定するProxyだけでは不十分Server Actions / Route Handlers側でも確認が必要
フォーム送信後のDB更新向かないServer ActionsやRoute Handlerの責務
ページごとの細かい表示分岐向かないことが多いServer Component内で扱う方が自然

迷ったら、「その処理はリクエストがページ/APIに届く前に終わっている必要があるか?」と考えます。必要がなければ、ページ、Route Handler、Server Actionsに置く方が読みやすいです。


よくあるハマりどころ

ハマりどころ原因対策
画像や静的ファイルにもProxyが走るmatcher が広すぎる対象パスを狭くする
ログインページへ無限リダイレクトする/login 自体もProxy対象になっているmatcher から /login を外す
Edgeで動かないライブラリがあるNode.js APIやネイティブ機能に依存しているEdge対応ライブラリにするか、処理をRoute Handlerへ移す
Proxyで認証したのにAPIが叩けるAPIが matcher 対象外Route Handler側でも認証・認可する
古い記事の middleware.ts をそのまま使っているNext.js 16で名称が変わったproxy.tsproxy() に移行する
ProxyにDB処理を入れて遅くなる入口処理が重い軽い判定だけにし、詳細処理はサーバー側へ寄せる

まとめ

  • proxy.ts は、ページやRoute Handlerに届く前に実行される入口処理
  • Next.js 16では middleware.ts が非推奨になり、proxy.ts / proxy() にリネームされた
  • matcher で対象URLを狭く絞るのが基本
  • NextRequest でURL・Cookie・ヘッダーを読み、NextResponse で通過・リダイレクト・rewrite・ヘッダー操作を行う
  • Edge RuntimeはWeb標準API中心の軽量な実行環境で、Node.js Runtimeとは使えるAPIが異なる
  • 古いMiddlewareはEdge Runtimeのみという説明が多いが、Next.js 15.5以降とNext.js 16のProxyではランタイムの扱いが変わっている
  • Proxyは強力だが、認証・認可の最終チェックはRoute HandlerやServer Actions側にも必ず置く

次の章では、データ取得中の ローディングとエラー処理 を扱います。fetch を待つ間に何を見せるか、失敗したらどう表示するか——loading.tsxerror.tsx で宣言的に実装できます。

Next.jsクイズに挑戦するこの章で学んだNext.jsの知識を、4択クイズでアウトプットして定着させよう