# Vercel Routing Middleware 実装ガイド：認証ゲート・パーソナライズ・A/B・リダイレクトをキャッシュ前で

> Vercel公式に忠実なRouting Middleware実装ガイド。リクエスト処理前・キャッシュ前にグローバル実行されるミドルウェアで、認証ゲート・地域/デバイス別パーソナライズ・A/Bテスト・緊急リダイレクト・IPブロックを実装。Edge/Node.js/Bunランタイムの選択、Edge Configとの組み合わせ、14KB URL・4MB body等の制限、observabilityまで実コードで解説します。

- 公開日: 2026-06-28
- 著者: 友田 陽大
- タグ: Vercel, Next.js, セキュリティ, パフォーマンス, TypeScript, フロントエンド, アーキテクチャ設計
- URL: https://tomodahinata.com/blog/vercel-middleware-routing-edge-auth-personalization-guide
- カテゴリ: Vercel 本番運用
- 総合ガイド: https://tomodahinata.com/blog/vercel-production-platform-guide

## 要点

- Routing Middlewareはリクエスト処理の前・CDNキャッシュの前にグローバル実行されるコード。Fluid Compute上で動き、フレームワーク非依存（middleware.tsをルートに置く）。静的生成コンテンツにパーソナライズを足す最有力手段
- 既定ランタイムはEdge。config.runtime='nodejs'でNode.jsに変更でき、フルNode.js APIが使える（Bunはvercel.jsonのbunVersion＋runtime nodejs）。Node.jsミドルウェアはFluid Compute上で動き、Functions課金が発生する
- 用途は認証ゲート（保護ルートの未認証リダイレクト）、地域/デバイス/言語別パーソナライズ、A/Bテストのバケット割り当て、緊急リダイレクト、IPブロック。重い処理や秘匿ロジックはミドルウェアに置かない
- DBに依存するなら、エッジから遠い専用DBは遅い。Edge Config（P99 15ms未満）やBlobなどグローバルストアを使う。フラグやリダイレクト表はEdge Configが最適
- リクエスト制限はURL 14KB・body 4MB・ヘッダ64個/16KB。Observabilityでパス別呼び出し・リライト/リダイレクトの内訳を可視化できる。誰でも見られる値を信頼境界にしない（認可はサーバー側でも検証）

---

「ログインしていないユーザーを `/dashboard` から弾きたい」「国ごとに通貨表示を変えたい」「新デザインを5%のユーザーにだけ出したい」——これらを**ページの手前**で、しかも**静的キャッシュを活かしたまま**実現するのが **Routing Middleware** です。

この記事は [Vercel Routing Middleware](https://vercel.com/docs/routing-middleware) の公式仕様に忠実に、認証ゲート・パーソナライズ・A/B・リダイレクトの実装をまとめます。全体像は [Vercel 本番運用ガイド](/blog/vercel-production-platform-guide) を参照してください。

---

## Routing Middleware とは

公式の定義はシンプルです。

> Routing Middleware executes code *before* a request is processed on a site, and are built on top of fluid compute.（— [Routing Middleware](https://vercel.com/docs/routing-middleware)）

重要な性質：

- **リクエスト処理の前・CDN キャッシュの前**にグローバル実行される。だから**静的生成コンテンツにパーソナライズ**を足せる（キャッシュは効かせたまま、出し分けだけ手前で）。
- **Fluid Compute 上で動く**（[Functions](/blog/vercel-functions-fluid-compute-streaming-cron-guide)）。
- **フレームワーク非依存**——`middleware.ts` をプロジェクトルートに置けば、どのフレームワークでも動く。Next.js の middleware と同じファイル名だが、Vercel プロダクトとしては**フレームワークを問わない**。

```ts
// 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` に切り替えます。

```ts
// 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 も併用可）。

---

## ユースケース①：認証ゲート

未認証ユーザーを保護ルートからログインへ。**ページの手前**で弾くので、保護ページのレンダリングコストもかかりません。

```ts
// 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で強制](/blog/multi-tenant-saas-data-isolation-authorization-design-guide)するのが鉄則です。Cookie の改ざん対策には署名付き Cookie を。

---

## ユースケース②：パーソナライズ（地域・デバイス・言語）

静的ページを**キャッシュしたまま**、国・言語・デバイスで出し分けます。Vercel は地理情報ヘッダ（`x-vercel-ip-country` 等）を提供します。

```ts
// 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` を使います（[キャッシュ戦略](/blog/vercel-caching-isr-cache-components-ppr-guide)）。

---

## ユースケース③：A/B テストのバケット割り当て

新デザインを一部ユーザーに。Cookie でバケットを固定し、同一ユーザーには一貫した体験を返します。

```ts
// 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](/blog/vercel-deployments-cicd-rollback-rolling-releases-guide) の方が安全（Skew Protection・メトリクス比較・即ロールバック付き）。ミドルウェア A/B は「機能フラグ的な恒常的な出し分け」に向きます。

---

## ユースケース④：緊急リダイレクト・IP ブロック（Edge Config 連携）

ミドルウェアで DB を叩くとレイテンシが乗ります。**フラグ・リダイレクト表・IP ブロックリストは Edge Config**（P99 15ms 未満）に置くのが定石です。

```ts
// 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](/blog/vercel-firewall-waf-botid-ddos-security-guide)（入口で関数を呼ぶ前に効く）が適切。ミドルウェアは「アプリ寄りのロジック」、WAF は「プラットフォーム入口」と役割分担します。

---

## 制限とコスト

| 項目 | 制限 |
|---|---|
| URL 最大長 | 14 KB |
| リクエスト本文 | 4 MB |
| リクエストヘッダ数 | 64 |
| リクエストヘッダ長 | 16 KB |
| Edge env 変数 | 1変数 5KB |

- **コスト**は Fluid Compute モデル（[Active CPU 課金](/blog/vercel-cost-active-cpu-pricing-optimization-guide)）。Node.js ランタイムのミドルウェアは Functions 課金が発生。
- ミドルウェアは**全リクエストの手前**で動くので、**重い処理を置かない**。判定は軽く、データは Edge Config から。

---

## 可観測性

Observability でミドルウェアの**パス別呼び出し・アクション内訳（リダイレクト/リライト）・リライト先の頻度**を確認できます（Observability Plus でより詳細）。`console.*` API がフル対応なので、必要なログは Runtime Logs に出ます（[可観測性](/blog/vercel-observability-monitoring-speed-insights-log-drains-guide)）。

---

## 本番チェックリスト（Middleware）

- [ ] ミドルウェアは**軽い判定のみ**（重処理・秘匿ロジックを置かない）
- [ ] 認証ゲートは UX 最適化であり、**最終認可はサーバー/DBで**検証
- [ ] フラグ/リダイレクト/IPリストは **Edge Config**（再デプロイ不要・低遅延）
- [ ] ランタイムを用途で選択（Edge 既定 / Node.js は Functions 課金）
- [ ] 大規模 bot/レート制限は **WAF/BotID** に委ねる
- [ ] URL 14KB・body 4MB・ヘッダ制限を超えない
- [ ] Observability でパス別の挙動を監視

---

## まとめ

Routing Middleware は「**ページの手前・キャッシュの前**」という特等席で、認証・パーソナライズ・A/B・リダイレクトを実装できます。

1. **キャッシュを活かしたままパーソナライズ**できるのが最大の価値
2. ランタイムは **Edge 既定 / Node.js は Functions 課金**
3. データは **Edge Config**（低遅延・再デプロイ不要）
4. 認可の**最終確定はサーバー側**、bot/レート制限は **WAF**
5. 軽く保ち、Observability で監視

認証ゲート・多地域パーソナライズ・段階リリースの設計実装を、案件として承ります。

> 本記事は [Routing Middleware](https://vercel.com/docs/routing-middleware) / [Edge Config](https://vercel.com/docs/edge-config) 公式ドキュメント（2026年6月時点）に基づきます。仕様・上限は更新されるため、本番採用時は公式で最新値を確認してください。
