Vercel Routing Middleware
You are an expert in Vercel Routing Middleware — the platform-level request interception layer.
What It Is
Routing Middleware runs before the cache on every request matching its config. It is a Vercel platform feature (not framework-specific) that works with Next.js, SvelteKit, Astro, Nuxt, or any deployed framework. Built on Fluid Compute.
- Preferred platform configuration: Set
proxy.entrypointinvercel.json. The entrypoint can use any supported filename or directory and runs on Node.js. Frameworks that build their own routing middleware (Next.js, Astro) do not use theproxyproperty; use the framework's file convention instead. - File convention:
middleware.tsormiddleware.jsat the project root. This convention defaults to Edge; setruntime: 'nodejs'to use Node.js. - Next.js 16: Use
proxy.tsand exportproxy. Next.js Proxy runs on Node.js only.
CRITICAL: Middleware Disambiguation
There are THREE "middleware" concepts in the Vercel ecosystem:
| Concept | File | Runtime | Scope | When to Use |
|---|---|---|---|---|
| Vercel Routing Middleware | proxy.entrypoint or middleware.ts | Node/Edge/Bun | Any framework, platform-level | Request interception before cache: rewrites, redirects, geo, A/B |
| Next.js 16 Proxy | proxy.ts (root, or src/proxy.ts if using --src-dir) | Node.js only | Next.js 16+ only | Network-boundary proxy needing full Node APIs. NOT for auth. |
| Vercel Functions | Route or function file | Node/Bun/Python/Rust | General-purpose | Request handlers and backend compute, not an interception layer |
Why the rename in Next.js 16: middleware.ts → proxy.ts clarifies it sits at the network boundary (not general-purpose middleware). Partly motivated by CVE-2025-29927 (middleware auth bypass via x-middleware-subrequest header). The exported function must also be renamed from middleware to proxy. Migration codemod: npx @next/codemod@latest middleware-to-proxy
Deprecation: Next.js 16 still accepts middleware.ts but treats it as deprecated and logs a warning. It will be removed in a future version.
Bun Runtime
To run Routing Middleware (and all Vercel Functions) on Bun, add bunVersion to vercel.json:
json{ "bunVersion": "1.x" }
Set the middleware runtime to nodejs — Bun replaces the Node.js runtime transparently:
tsexport const config = { runtime: 'nodejs', // Bun swaps in when bunVersion is set };
Bun reduces average latency by ~28% in CPU-bound workloads. Currently in Public Beta — supports Next.js, Express, Hono, and Nitro.
Basic Example
Configure an explicit entrypoint for framework-agnostic Routing Middleware:
json{ "$schema": "https://openapi.vercel.sh/vercel.json", "proxy": { "entrypoint": "proxy.ts", "matcher": ["/((?!_next/static|favicon.ico).*)"] } }
ts// proxy.ts import { geolocation, rewrite } from '@vercel/functions'; export default function proxy(request: Request) { const { country } = geolocation(request); const url = new URL(request.url); url.pathname = country === 'US' ? '/us' + url.pathname : '/intl' + url.pathname; return rewrite(url); }
Helper Methods (@vercel/functions)
For non-Next.js frameworks, import from @vercel/functions:
| Helper | Purpose |
|---|---|
next() | Continue middleware chain (optionally modify headers) |
rewrite(url) | Transparently serve content from a different URL |
geolocation(request) | Get city, country, latitude, longitude, region |
ipAddress(request) | Get client IP address |
waitUntil(promise) | Keep function running after response is sent |
For Next.js, NextResponse provides next(), rewrite(), and redirect(). Use geolocation(request) and ipAddress(request) from @vercel/functions; NextRequest.geo and NextRequest.ip were removed in Next.js 15.
Matcher Configuration
Middleware runs on every route by default. Use config.matcher to scope it:
ts// Single path export const config = { matcher: '/dashboard/:path*' }; // Multiple paths export const config = { matcher: ['/dashboard/:path*', '/api/:path*'] }; // Regex: exclude static files export const config = { matcher: ['/((?!_next/static|favicon.ico).*)'], };
Tip: Using matcher is preferred — unmatched paths skip middleware invocation entirely (saves compute).
Common Patterns
IP-Based Header Injection
tsimport { ipAddress, next } from '@vercel/functions'; export default function middleware(request: Request) { return next({ headers: { 'x-real-ip': ipAddress(request) || 'unknown' } }); }
A/B Testing via Global Config
tsimport { get } from '@vercel/global-config'; import { rewrite } from '@vercel/functions'; export default async function middleware(request: Request) { const variant = await get('experiment-homepage'); // <1ms read const url = new URL(request.url); url.pathname = variant === 'B' ? '/home-b' : '/home-a'; return rewrite(url); }
Background Processing
tsimport type { RequestContext } from '@vercel/functions'; export default function middleware(request: Request, context: RequestContext) { context.waitUntil( fetch('https://analytics.example.com/log', { method: 'POST', body: request.url }) ); return new Response('OK'); }
Request Limits
| Limit | Value |
|---|---|
| Max URL length | 14 KB |
| Max request body | 4 MB |
| Max request headers | 64 headers / 16 KB total |
Three CDN Routing Mechanisms
Vercel's CDN supports three routing mechanisms, evaluated in this order:
| Order | Mechanism | Scope | Deploy Required | How to Configure |
|---|---|---|---|---|
| 1 | Bulk Redirects | Up to 1M static path→path redirects | No (runtime via Dashboard/API/CLI) | Dashboard, CSV upload, REST API |
| 2 | Project-Level Routes | Headers, rewrites, redirects | No (instant publish) | Dashboard, REST API, vercel routes CLI |
| 3 | Deployment Config Routes | Full routing rules | Yes (deploy) | vercel.json, vercel.ts, next.config.ts |
Project-level routes (added March 2026) let you update routing rules — response headers, rewrites to external APIs — without triggering a new deployment. They run after bulk redirects and before deployment config routes. Available on all plans.
Project-Level Routes — Configuration Methods
Project-level routes take effect instantly (no deploy required). Three ways to manage them:
| Method | How |
|---|---|
| Dashboard | Project → CDN → Routing tab. Live map of global traffic, cache management, and route editor in one view. |
| REST API | GET/POST/PATCH/DELETE /v1/projects/{projectId}/routes — 8 dedicated endpoints for CRUD on project routes. |
| Vercel CLI | Use vercel routes to stage, inspect, publish, restore, and export project-level rules. |
Deployment-level routes in vercel.json, vercel.ts, or framework config are a separate mechanism (row 3 above) and require a deploy.
Use project-level routes for operational changes (CORS headers, API proxy rewrites, A/B redirects) that shouldn't require a full redeploy.
Programmatic Configuration with vercel.ts
Instead of static vercel.json, you can use vercel.ts (or .js, .mjs, .cjs, .mts) with the @vercel/config package for type-safe, dynamic routing configuration:
ts// vercel.ts import { routes, type VercelConfig } from '@vercel/config/v1'; export const config: VercelConfig = { rewrites: [ routes.rewrite('/api/(.*)', 'https://backend.example.com/$1'), ], headers: [ routes.header('/(.*)', [{ key: 'X-Frame-Options', value: 'DENY' }]), ], };
For project-level rules that take effect without a deployment, use vercel routes add, inspect staged changes with vercel routes list --diff, then run vercel routes publish.
Constraint: Only one config file per project — vercel.json or vercel.ts, not both.
When to Use
- Geo-personalization of static pages (runs before cache)
- A/B testing rewrites with Global Config
- Custom redirects based on request properties
- Header injection (CSP, CORS, custom headers)
- Lightweight auth checks (defense-in-depth only — not sole auth layer)
- Project-level routes for headers/rewrites without redeploying
When NOT to Use
- Need full Node.js APIs in Next.js → use
proxy.ts - General compute or request handling → use Vercel Functions on the default Node.js runtime
- Heavy business logic or database queries → use server-side framework features
- Auth as sole protection → use Layouts, Server Components, or Route Handlers
- Thousands of static redirects → use Bulk Redirects (up to 1M per project)
References
- 📖 docs: https://vercel.com/docs/routing-middleware
- 📖 API reference: https://vercel.com/docs/routing-middleware/api
- 📖 getting started: https://vercel.com/docs/routing-middleware/getting-started

