# Vercel Routing Middleware implementation guide: auth gates, personalization, A/B, and redirects before the cache

> A Routing Middleware implementation guide faithful to Vercel's official docs. With middleware that runs globally before request processing and before the cache, implement auth gates, region/device personalization, A/B testing, emergency redirects, and IP blocking. It explains, with real code: choosing the Edge/Node.js/Bun runtime, combining with Edge Config, limits like 14KB URL and 4MB body, and observability.

- Published: 2026-06-28
- Author: 友田 陽大
- Tags: Vercel, Next.js, セキュリティ, パフォーマンス, TypeScript, フロントエンド, アーキテクチャ設計
- URL: https://tomodahinata.com/en/blog/vercel-middleware-routing-edge-auth-personalization-guide
- Category: Vercel in production
- Pillar guide: https://tomodahinata.com/en/blog/vercel-production-platform-guide

## Key points

- Routing Middleware is code that runs globally before request processing and before the CDN cache. It runs on Fluid Compute and is framework-agnostic (place middleware.ts at the root). It's the leading way to add personalization to statically generated content.
- The default runtime is Edge. You can change to Node.js with config.runtime='nodejs' to use the full Node.js API (Bun is bunVersion in vercel.json + runtime nodejs). Node.js middleware runs on Fluid Compute and incurs Functions billing.
- Uses are auth gates (redirecting unauthenticated users from protected routes), region/device/language personalization, A/B test bucket assignment, emergency redirects, and IP blocking. Don't put heavy processing or secret logic in middleware.
- If you depend on a DB, a dedicated DB far from the edge is slow. Use a global store like Edge Config (P99 under 15ms) or Blob. Edge Config is ideal for flags and redirect tables.
- Request limits are URL 14KB, body 4MB, 64 headers / 16KB. Observability can visualize per-path calls and the breakdown of rewrites/redirects. Don't make a value anyone can see a trust boundary (verify authorization on the server side too).

---

"I want to keep unauthenticated users out of `/dashboard`," "I want to change currency display per country," "I want to show the new design to only 5% of users" — what realizes these **before the page** and **while keeping the static cache** is **Routing Middleware.**

This article, faithful to the official spec of [Vercel Routing Middleware](https://vercel.com/docs/routing-middleware), collects the implementation of auth gates, personalization, A/B, and redirects. For the big picture, see the [Vercel production-operations guide](/blog/vercel-production-platform-guide).

---

## What Routing Middleware is

The official definition is simple.

> 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))

Important properties:

- It runs globally **before request processing and before the CDN cache.** So you can add **personalization to statically generated content** (keep the cache effective, and only do the differentiation upfront).
- It **runs on Fluid Compute** ([Functions](/blog/vercel-functions-fluid-compute-streaming-cron-guide)).
- It's **framework-agnostic** — place `middleware.ts` at the project root and it runs in any framework. Same filename as Next.js's middleware, but as a Vercel product it's **framework-independent.**

```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!");
}
```

---

## Choose the runtime (Edge / Node.js / Bun)

**The default is the Edge runtime.** If you need the full Node.js API, switch to `nodejs` with `config`.

```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!");
}
```

| Runtime | When to use | Note |
|---|---|---|
| **Edge** (default) | Lightweight, ultra-low-latency redirects/header manipulation | env is up to 5KB per variable |
| **Node.js** | Full Node.js API / existing libraries needed | Runs on Fluid Compute and incurs **Functions billing** |
| **Bun** | Bun-native | `bunVersion` in `vercel.json` + runtime `nodejs` |

> To use Node.js middleware in Next.js, enable `experimental.nodeMiddleware: true` in `next.config.ts` and set `config.runtime` in `middleware.ts` to `nodejs` (a matcher can also be used together).

---

## Use case ①: auth gate

Send unauthenticated users from protected routes to login. Since you reject them **before the page**, there's no rendering cost for the protected page either.

```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);
  }
}
```

> **Important (trust boundary)**: the auth check in middleware is **a UX optimization**, not **the final authorization.** Don't finalize authorization on a cookie-existence check alone — always verify the token on the server side of the page/API. Considering MITM and replay, the iron rule is to [enforce authorization on the server/DB](/blog/multi-tenant-saas-data-isolation-authorization-design-guide). For cookie tampering, use a signed cookie.

---

## Use case ②: personalization (region, device, language)

While **keeping the static page cached**, differentiate by country, language, and device. Vercel provides geo headers (`x-vercel-ip-country`, etc.).

```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() },
  });
}
```

If you want to include attributes in the cache key, use `Vary` on the response side ([caching strategy](/blog/vercel-caching-isr-cache-components-ppr-guide)).

---

## Use case ③: A/B test bucket assignment

Show the new design to some users. Fix the bucket with a cookie and return a consistent experience to the same user.

```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;
}
```

> For staged production rollout itself, [Rolling Releases](/blog/vercel-deployments-cicd-rollback-rolling-releases-guide) (with Skew Protection, metric comparison, and instant rollback) is safer than a self-built A/B in middleware. Middleware A/B suits "feature-flag-like permanent differentiation."

---

## Use case ④: emergency redirects, IP blocking (Edge Config integration)

Hitting a DB in middleware adds latency. The standard is to put **flags, redirect tables, and IP blocklists in Edge Config** (P99 under 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 });
  }
}
```

> For large-scale IP blocking, rate limiting, and bot countermeasures, [Vercel WAF / BotID](/blog/vercel-firewall-waf-botid-ddos-security-guide) (which works at the entrance before calling the function) is more appropriate than middleware. Divide roles: middleware is "app-side logic," and the WAF is "the platform entrance."

---

## Limits and cost

| Item | Limit |
|---|---|
| Max URL length | 14 KB |
| Request body | 4 MB |
| Number of request headers | 64 |
| Request header length | 16 KB |
| Edge env variable | 5KB per variable |

- **Cost** is the Fluid Compute model ([Active CPU billing](/blog/vercel-cost-active-cpu-pricing-optimization-guide)). Node.js-runtime middleware incurs Functions billing.
- Since middleware runs **before every request**, **don't put heavy processing in it.** Keep judgments light and pull data from Edge Config.

---

## Observability

In Observability you can check middleware's **per-path calls, action breakdown (redirect/rewrite), and the frequency of rewrite targets** (more detailed with Observability Plus). The `console.*` API is fully supported, so necessary logs appear in Runtime Logs ([observability](/blog/vercel-observability-monitoring-speed-insights-log-drains-guide)).

---

## Production checklist (Middleware)

- [ ] Middleware does **only light judgments** (don't put heavy processing/secret logic)
- [ ] The auth gate is a UX optimization, and **the final authorization is verified on the server/DB**
- [ ] Flags/redirects/IP lists in **Edge Config** (no redeploy needed, low latency)
- [ ] Choose the runtime by use (Edge default / Node.js incurs Functions billing)
- [ ] Delegate large-scale bot/rate-limiting to **WAF/BotID**
- [ ] Don't exceed the URL 14KB, body 4MB, and header limits
- [ ] Monitor per-path behavior with Observability

---

## Conclusion

Routing Middleware can implement auth, personalization, A/B, and redirects from the prime seat of "**before the page, before the cache.**"

1. The greatest value is being able to **personalize while keeping the cache effective**
2. The runtime is **Edge default / Node.js incurs Functions billing**
3. Data is in **Edge Config** (low latency, no redeploy needed)
4. **Finalize authorization on the server side**, and bot/rate-limiting with the **WAF**
5. Keep it light and monitor with Observability

I take on the design and implementation of auth gates, multi-region personalization, and staged releases as a project.

> This article is based on the official documentation of [Routing Middleware](https://vercel.com/docs/routing-middleware) / [Edge Config](https://vercel.com/docs/edge-config) (as of June 2026). The spec and limits get updated, so confirm the latest values in the official docs when adopting in production.
