「ログインしていないユーザーを /dashboard から弾きたい」「国ごとに通貨表示を変えたい」「新デザインを5%のユーザーにだけ出したい」——これらをページの手前で、しかも静的キャッシュを活かしたまま実現するのが Routing Middleware です。
この記事は Vercel Routing Middleware の公式仕様に忠実に、認証ゲート・パーソナライズ・A/B・リダイレクトの実装をまとめます。全体像は Vercel 本番運用ガイド を参照してください。
Routing Middleware とは
公式の定義はシンプルです。
Routing Middleware executes code before a request is processed on a site, and are built on top of fluid compute.(— Routing Middleware)
重要な性質:
- リクエスト処理の前・CDN キャッシュの前にグローバル実行される。だから静的生成コンテンツにパーソナライズを足せる(キャッシュは効かせたまま、出し分けだけ手前で)。
- Fluid Compute 上で動く(Functions)。
- フレームワーク非依存——
middleware.tsをプロジェクトルートに置けば、どのフレームワークでも動く。Next.js の middleware と同じファイル名だが、Vercel プロダクトとしてはフレームワークを問わない。
// middleware.ts(プロジェクトルート)
export default function middleware(request: Request) {
const url = new URL(request.url);
// 旧パスをリダイレクト
if (url.pathname === "/old-page") {
return new Response(null, {
status: 302,
headers: { Location: "/new-page" },
});
}
// 次のハンドラへ続行
return new Response("Hello from your Middleware!");
}
ランタイムを選ぶ(Edge / Node.js / Bun)
既定は Edge ランタイム。フル Node.js API が要るなら config で nodejs に切り替えます。
// middleware.ts — Node.js ランタイムに切り替え
export const config = {
runtime: "nodejs", // 既定は 'edge'
};
export default function middleware(request: Request) {
return new Response("Hello from Node.js Middleware!");
}
| ランタイム | 使いどころ | 注意 |
|---|---|---|
| Edge(既定) | 軽量・極小レイテンシのリダイレクト/ヘッダ操作 | env は 1変数 5KB まで |
| Node.js | フル Node.js API・既存ライブラリが必要 | Fluid Compute 上で動き、Functions 課金が発生 |
| Bun | Bun ネイティブ | vercel.json の bunVersion + runtime nodejs |
Next.js で Node.js ミドルウェアを使う場合は、
next.config.tsのexperimental.nodeMiddleware: trueを有効化し、middleware.tsのconfig.runtimeをnodejsにします(matcher も併用可)。
ユースケース①:認証ゲート
未認証ユーザーを保護ルートからログインへ。ページの手前で弾くので、保護ページのレンダリングコストもかかりません。
// middleware.ts
const PROTECTED = ["/dashboard", "/settings", "/billing"];
export default function middleware(request: Request) {
const url = new URL(request.url);
const needsAuth = PROTECTED.some((p) => url.pathname.startsWith(p));
if (!needsAuth) return; // 続行
const session = request.headers.get("cookie")?.includes("session=");
if (!session) {
const login = new URL("/login", request.url);
login.searchParams.set("next", url.pathname); // 戻り先を保持
return Response.redirect(login, 307);
}
}
重要(信頼境界):ミドルウェアでの認証チェックは「UX の最適化」であって、最終的な認可ではありません。Cookie の存在チェックだけで認可を確定しない——ページ/APIのサーバー側でトークンを必ず検証します。中間者やリプレイを考えれば、認可はサーバー/DBで強制するのが鉄則です。Cookie の改ざん対策には署名付き Cookie を。
ユースケース②:パーソナライズ(地域・デバイス・言語)
静的ページをキャッシュしたまま、国・言語・デバイスで出し分けます。Vercel は地理情報ヘッダ(x-vercel-ip-country 等)を提供します。
// middleware.ts — 国に応じてロケールへ rewrite(キャッシュは活きる)
export default function middleware(request: Request) {
const url = new URL(request.url);
if (url.pathname !== "/") return;
const country = request.headers.get("x-vercel-ip-country") ?? "US";
const locale = country === "JP" ? "ja" : "en";
// rewrite(URLは変えずに内部的に別ページを返す)
const rewritten = new URL(`/${locale}`, request.url);
return new Response(null, {
status: 200,
headers: { "x-middleware-rewrite": rewritten.toString() },
});
}
キャッシュキーに属性を含めたい場合は、レスポンス側で Vary を使います(キャッシュ戦略)。
ユースケース③:A/B テストのバケット割り当て
新デザインを一部ユーザーに。Cookie でバケットを固定し、同一ユーザーには一貫した体験を返します。
// middleware.ts
export default function middleware(request: Request) {
const url = new URL(request.url);
if (url.pathname !== "/") return;
const cookie = request.headers.get("cookie") ?? "";
let bucket = cookie.match(/ab=(a|b)/)?.[1];
const res = new Response(null, { status: 200 });
if (!bucket) {
bucket = Math.random() < 0.5 ? "a" : "b";
res.headers.append("set-cookie", `ab=${bucket}; Path=/; Max-Age=2592000`);
}
res.headers.set(
"x-middleware-rewrite",
new URL(bucket === "b" ? "/home-variant" : "/", request.url).toString(),
);
return res;
}
段階的な本番ロールアウトそのものは、ミドルウェアの自作 A/B より Rolling Releases の方が安全(Skew Protection・メトリクス比較・即ロールバック付き)。ミドルウェア A/B は「機能フラグ的な恒常的な出し分け」に向きます。
ユースケース④:緊急リダイレクト・IP ブロック(Edge Config 連携)
ミドルウェアで DB を叩くとレイテンシが乗ります。フラグ・リダイレクト表・IP ブロックリストは Edge Config(P99 15ms 未満)に置くのが定石です。
// middleware.ts — Edge Config で「再デプロイなしの」制御
import { get } from "@vercel/edge-config";
export default async function middleware(request: Request) {
// メンテモード(コードを触らず即ON/OFF)
if (await get<boolean>("maintenance_mode")) {
return Response.redirect(new URL("/maintenance", request.url), 307);
}
// 悪性IPブロック(アップストリームを呼ばずに弾く)
const ip = request.headers.get("x-forwarded-for")?.split(",")[0];
const blocked = (await get<string[]>("blocked_ips")) ?? [];
if (ip && blocked.includes(ip)) {
return new Response("Forbidden", { status: 403 });
}
}
大規模な IP ブロック・レート制限・bot 対策は、ミドルウェアよりも Vercel WAF / BotID(入口で関数を呼ぶ前に効く)が適切。ミドルウェアは「アプリ寄りのロジック」、WAF は「プラットフォーム入口」と役割分担します。
制限とコスト
| 項目 | 制限 |
|---|---|
| URL 最大長 | 14 KB |
| リクエスト本文 | 4 MB |
| リクエストヘッダ数 | 64 |
| リクエストヘッダ長 | 16 KB |
| Edge env 変数 | 1変数 5KB |
- コストは Fluid Compute モデル(Active CPU 課金)。Node.js ランタイムのミドルウェアは Functions 課金が発生。
- ミドルウェアは全リクエストの手前で動くので、重い処理を置かない。判定は軽く、データは Edge Config から。
可観測性
Observability でミドルウェアのパス別呼び出し・アクション内訳(リダイレクト/リライト)・リライト先の頻度を確認できます(Observability Plus でより詳細)。console.* API がフル対応なので、必要なログは Runtime Logs に出ます(可観測性)。
本番チェックリスト(Middleware)
- ミドルウェアは軽い判定のみ(重処理・秘匿ロジックを置かない)
- 認証ゲートは UX 最適化であり、最終認可はサーバー/DBで検証
- フラグ/リダイレクト/IPリストは Edge Config(再デプロイ不要・低遅延)
- ランタイムを用途で選択(Edge 既定 / Node.js は Functions 課金)
- 大規模 bot/レート制限は WAF/BotID に委ねる
- URL 14KB・body 4MB・ヘッダ制限を超えない
- Observability でパス別の挙動を監視
まとめ
Routing Middleware は「ページの手前・キャッシュの前」という特等席で、認証・パーソナライズ・A/B・リダイレクトを実装できます。
- キャッシュを活かしたままパーソナライズできるのが最大の価値
- ランタイムは Edge 既定 / Node.js は Functions 課金
- データは Edge Config(低遅延・再デプロイ不要)
- 認可の最終確定はサーバー側、bot/レート制限は WAF
- 軽く保ち、Observability で監視
認証ゲート・多地域パーソナライズ・段階リリースの設計実装を、案件として承ります。
本記事は Routing Middleware / Edge Config 公式ドキュメント(2026年6月時点)に基づきます。仕様・上限は更新されるため、本番採用時は公式で最新値を確認してください。