ProxyとMiddleware — リクエストの入口で認証・リダイレクトを制御する
この章の目次開く
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();
}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*'],
};この例では、/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*'],
};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();
}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();
}ヘッダーを追加する
export function proxy(request: NextRequest) {
const requestHeaders = new Headers(request.headers);
requestHeaders.set('x-from-proxy', 'true');
return NextResponse.next({
request: {
headers: requestHeaders,
},
});
}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 Runtime | Edge Runtime |
|---|---|---|
| 主な用途 | レンダリング、Route Handler、DB連携、重いサーバー処理 | リダイレクト、rewrite、軽い認証判定、ヘッダー操作 |
| 使えるAPI | Node.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関数名も変わります。
- export function middleware(request: NextRequest) {
+ export function proxy(request: NextRequest) {
return NextResponse.next();
}移行用のcodemodも用意されています。
npx @next/codemod@canary middleware-to-proxy .名称変更の意図は、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.ts と proxy() に移行する |
| 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.tsx と error.tsx で宣言的に実装できます。
