Skip to main content
友田 陽大
Vercel in production
Vercel
Next.js
セキュリティ
パフォーマンス
TypeScript
フロントエンド
アーキテクチャ設計

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
Reading time
6 min read
Author
友田 陽大
Share

"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, collects the implementation of auth gates, personalization, A/B, and redirects. For the big picture, see the Vercel production-operations 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)

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).
  • 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.
// 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.

// middleware.ts — Node.js ランタイムに切り替え
export const config = {
  runtime: "nodejs", // 既定は 'edge'
};

export default function middleware(request: Request) {
  return new Response("Hello from Node.js Middleware!");
}
RuntimeWhen to useNote
Edge (default)Lightweight, ultra-low-latency redirects/header manipulationenv is up to 5KB per variable
Node.jsFull Node.js API / existing libraries neededRuns on Fluid Compute and incurs Functions billing
BunBun-nativebunVersion 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.

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

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


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.

// 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 (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).

// 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 (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

ItemLimit
Max URL length14 KB
Request body4 MB
Number of request headers64
Request header length16 KB
Edge env variable5KB per variable
  • Cost is the Fluid Compute model (Active CPU billing). 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).


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 / 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.

友田

友田 陽大

Developer of a METI Minister's Award–winning product. With TypeScript + Python + AWS, I deliver SaaS, industry DX, and production-grade generative AI (RAG) end to end — from requirements to infrastructure and operations — single-handedly.

I can take on the implementation from this article as an engagement

Vercel apps, from design to production and cost optimization

Function design assuming Fluid Compute (safe global state, waitUntil, Cron), four-layer caching (ISR/CDN/Runtime Cache/Cache Components), safe deploys (preview/Promote/Instant Rollback/Rolling Releases), entry-point defense (Firewall/WAF/BotID), storage selection (Blob/Edge Config/Marketplace), and Active-CPU-billing-aware cost optimization. With experience running Next.js products on Vercel in production, I deliver fast, cheap, and secure.

Available for both project-based (contract) and advisory engagements. Start with a free 30-minute consult.

Also worth reading