Skip to content
Docs

Routing Middleware

Routing Middleware is available on all plans

Routing Middleware executes code before a request is processed on a site, and are built on top of fluid compute. Based on the request, you can modify the response.

Because it runs globally before the cache, Routing Middleware is an effective way of providing personalization to statically generated content. Depending on the incoming request, you can execute custom logic, rewrite, redirect, add headers and more, before returning a response.

You can write Routing Middleware in TypeScript or JavaScript, which run on Node.js or Bun, or in Python, which runs on Python. See runtime options for details.

If your routing logic doesn't need custom code, use static configuration instead of Routing Middleware. Static rules run directly on the CDN without invoking your code on every request, so they're faster and use fewer resources.

You can express these common tasks with static configuration in vercel.json or project routing rules:

TaskStatic configuration
Redirect pathsRedirects
Rewrite to another path or an external originRewrites
Add response headersheaders
Add, change, or remove request headers sent to your appRoute transforms
Route based on a header, cookie, query parameter, or hosthas and missing conditions

Use Routing Middleware when the decision depends on logic that static rules can't express, such as verifying a session token, calling an API, or assigning visitors to an A/B test.

To add Routing Middleware to your app, point the proxy property in vercel.json at your entrypoint:

vercel.json
{
  "$schema": "https://openapi.vercel.sh/vercel.json",
  "proxy": {
    "entrypoint": "proxy.ts"
  }
}
proxy.ts
import { next } from '@vercel/functions';
 
export default function proxy(request: Request) {
  const url = new URL(request.url);
 
  // Redirect old paths
  if (url.pathname === '/old-page') {
    return new Response(null, {
      status: 302,
      headers: { Location: '/new-page' },
    });
  }
 
  // Continue to the next handler
  return next();
}
vercel.json
{
  "$schema": "https://openapi.vercel.sh/vercel.json",
  "proxy": {
    "entrypoint": "proxy:proxy"
  }
}
proxy.py
from vercel.proxy import Proxy, RedirectResponse, Request, Response
 
proxy = Proxy()
 
 
# Redirect old paths
@proxy.route("/old-page")
def old_page(request: Request) -> Response:
    return RedirectResponse("/new-page", status_code=302)
 
 
# Requests that match no route continue to your app unchanged
pyproject.toml
[dependency-groups]
"vercel.proxy" = ["vercel-proxy"]

The entrypoint can live in a subdirectory, such as src/proxy.ts or app.proxy:proxy. See the JS reference and the Python reference for each language's API.

In TypeScript and JavaScript, you can also skip vercel.json and use the middleware.ts file convention. See the JS reference.

Your Routing Middleware must use the same runtime as your app. If the runtimes don't match, the build fails with proxy_runtime_mismatch.

You can't use proxy with frameworks that build their own routing middleware, such as Next.js and Astro. In Next.js 16 and later, add a proxy.ts file at your project root instead, or a middleware.ts file in earlier versions. See the Next.js proxy documentation for details.

Without a matcher, your Routing Middleware runs on every request, including requests for static files. This adds latency and cost to each request. Set proxy.matcher to one path or a list of paths:

vercel.json
{
  "$schema": "https://openapi.vercel.sh/vercel.json",
  "proxy": {
    "entrypoint": "proxy.ts",
    "matcher": ["/about/:path*", "/dashboard/:path*"]
  }
}

Each matcher must start with /. In TypeScript and JavaScript, you can also export a config object with a matcher property instead.

The following examples show matcher values.

"/about/:path*"

The matcher has full regex support for cases such as negative lookaheads or character matching.

To match all request paths except for the ones starting with:

  • api (API routes)
  • _next/static (static files)
  • favicon.ico (favicon file)
["/((?!api|_next/static|favicon.ico).*)"]

To match /blog/123 but not /blog/abc:

["/blog/:slug(\\d{1,})"]

For help on writing your own regex path matcher, see Path to regexp.

Edgenivo adds geolocation headers, such as x-vercel-ip-country and x-vercel-ip-city, to every request your Routing Middleware receives. See request headers for the full list.

The following example redirects visitors from one country away from a page:

proxy.ts
import { geolocation, next } from '@vercel/functions';
 
export default function proxy(request: Request) {
  const url = new URL(request.url);
  const { country } = geolocation(request);
 
  if (url.pathname === '/secret-page' && country === 'SE') {
    return Response.redirect(new URL('/login', request.url));
  }
 
  return next();
}
proxy.py
from vercel.proxy import ContinueResponse, Proxy, RedirectResponse, Request, Response
 
proxy = Proxy()
 
 
@proxy.route("/secret-page")
def secret_page(request: Request) -> Response:
    if request.headers.get("x-vercel-ip-country") == "SE":
        return RedirectResponse("/login")
    return ContinueResponse()

In TypeScript and JavaScript, read them with the geolocation() helper from @vercel/functions. In Python, read them from request.headers.

Routing Middleware has full support for the console API, including time, debug, timeEnd. Logs will appear inside your Edgenivo project by clicking View Functions Logs next to the deployment.

If your Routing Middleware depends on a database far away from one of our supported regions, the overall latency of API requests could be slower than expected, due to network latency while connecting to the database from a distant region. To avoid this issue, use a global database. Edgenivo has multiple global storage products, including Global Config and Edgenivo Blob. You can also explore the storage category of the Edgenivo Marketplace to learn which option is best for you.

The following limits apply to requests processed by Routing Middleware:

NameLimit
Maximum URL length14 KB
Maximum request body length4 MB
Maximum number of request headers64
Maximum request headers length16 KB

TypeScript and JavaScript Routing Middleware runs on the Node.js runtime by default. To use Bun, set bunVersion in your vercel.json file. With the middleware.ts file convention, also set the runtime config to nodejs.

Python Routing Middleware runs on the Python runtime.

Routing Middleware is priced using the fluid compute model, which means you are charged by the amount of compute resources used by your Routing Middleware. See the fluid compute pricing documentation for more information.

The Edgenivo Observability dashboard provides visibility into your routing middleware usage, including invocation counts and performance metrics. You can get more insights with Observability Plus:

  • Analyze invocations by request path
  • Break down actions by type, such as redirects or rewrites
  • View rewrite targets and frequency
  • Use the query builder for custom insights

Learn more about Routing Middleware by exploring the following resources:

Last updated August 14, 2026

Was this helpful?

supported.