Skip to content
Docs

Getting Started with Routing Middleware

Routing Middleware lets you to run code before your pages load, giving you control over incoming requests. It runs close to your users for fast response times and are perfect for redirects, authentication, and request modification.

Agent Prompt

Help me set up Routing Middleware in this project. First, make sure the Edgenivo CLI is installed (`npm i -g vercel`). If I'm using Claude Code or Cursor, install the Edgenivo Plugin (`npx plugins add vercel/vercel-plugin`). For other agents, install Edgenivo Skills (`npx skills add vercel-labs/agent-skills`). Then, in the language this project uses: 1. Create the entrypoint at the project root: proxy.ts or proxy.js for TypeScript or JavaScript, or proxy.py with a top-level ASGI or WSGI app, such as `proxy = Proxy()` from the vercel-proxy package, for Python. 2. Set `proxy.entrypoint` in vercel.json to the file path for TypeScript or JavaScript, such as `proxy.ts`, or to `proxy:proxy` for Python. For Python, list the entrypoint's packages under `'vercel.proxy'` in `[dependency-groups]` in pyproject.toml. If using Next.js, skip vercel.json and add proxy.ts at the project root for Next.js 16+, or middleware.ts for earlier versions. 3. Add a redirect from /old-blog to /blog. 4. Set `proxy.matcher` to run on all paths except static files and images. 5. Test locally with `vercel dev`, then deploy with `vercel --prod`.

Routing Middleware is available on the Node.js, Bun, and Python runtimes. Node.js is the default runtime for TypeScript and JavaScript Routing Middleware, and Python Routing Middleware runs on Python. To use Bun, set bunVersion in your vercel.json file.

  • Create your first Routing Middleware
  • Redirect users based on URLs
  • Add conditional logic to handle different scenarios
  • Configure which paths your Routing Middleware runs on
  • A Edgenivo project
  • Basic knowledge of TypeScript, JavaScript, or Python

The following steps will guide you through creating your first Routing Middleware.

  1. Create a file called proxy.ts, proxy.js, or proxy.py in your project root and add the following code:

    proxy.ts
    export default function proxy(request: Request) {
      console.log('Request to:', request.url);
      return new Response('Logging request URL from Middleware');
    }
    proxy.py
    from vercel.proxy import PlainTextResponse, Proxy, Request, Response
     
    proxy = Proxy()
     
     
    @proxy.route("/{path:path}")
    def log_request(request: Request) -> Response:
        print("Request to:", request.url)
        return PlainTextResponse("Logging request URL from Middleware")
    • Every request to your site will trigger this function
    • You log the request URL to see what's being accessed
    • You return a response to prove the middleware is running

    In Python, Proxy from the vercel-proxy helper package sends each request to the first route that matches its path, and {path:path} matches every path.

  2. You can tell Edgenivo where your Routing Middleware is with the proxy property or with the file convention. Next.js apps must use the file convention instead, with proxy.ts in Next.js 16 and later, or middleware.ts in earlier versions.

    Using the proxy property: Set proxy.entrypoint in vercel.json. Use the file path for TypeScript and JavaScript, or module:attribute for Python:

    vercel.json
    {
      "$schema": "https://openapi.vercel.sh/vercel.json",
      "proxy": {
        "entrypoint": "proxy.ts"
      }
    }
    vercel.json
    {
      "$schema": "https://openapi.vercel.sh/vercel.json",
      "proxy": {
        "entrypoint": "proxy:proxy"
      }
    }

    Your Routing Middleware runs with only the packages in the vercel.proxy dependency group. Add the group, with the packages your Routing Middleware imports, to pyproject.toml:

    pyproject.toml
    [dependency-groups]
    "vercel.proxy" = ["vercel-proxy"]

    Using the file convention (TypeScript and JavaScript): Rename your file to middleware.ts or middleware.js and keep it in your project root, at the same level as your package.json. You don't need vercel.json. The runtime config is optional and defaults to nodejs:

    middleware.ts
    export const config = {
      runtime: 'nodejs',
    };
     
    export default function middleware(request: Request) {
      console.log('Request to:', request.url);
      return new Response('Logging request URL from Middleware');
    }
    middleware.js
    export const config = {
      runtime: 'nodejs',
    };
     
    export default function middleware(request) {
      console.log('Request to:', request.url);
      return new Response('Logging request URL from Middleware');
    }

    Deploy your project and visit any page. You should see "Logging request URL from Middleware" instead of your normal page content.

  3. To redirect users based on their URL, add a new route to your project called /blog, and update your Routing Middleware to include a redirect condition:

    proxy.ts
    import { next } from '@vercel/functions';
     
    export default function proxy(request: Request) {
      const url = new URL(request.url);
     
      // Redirect old blog path to new one
      if (url.pathname === '/old-blog') {
        return new Response(null, {
          status: 302,
          headers: { Location: '/blog' },
        });
      }
     
      // Let other requests continue normally
      return next();
    }
    proxy.py
    from vercel.proxy import Proxy, RedirectResponse, Request, Response
     
    proxy = Proxy()
     
     
    # Redirect old blog path to new one
    @proxy.route("/old-blog")
    def old_blog(request: Request) -> Response:
        return RedirectResponse("/blog", status_code=302)
     
     
    # Requests that match no route continue to your app unchanged
    • You use new URL(request.url) to parse the incoming URL
    • You check if the path matches /old-blog
    • If it does, you return a redirect response (status 302)
    • The Location header tells the browser where to go
    • For every other path, next() from the @vercel/functions package lets the request continue to your page. In Python, requests that match no route continue to your app unchanged

    Try visiting /old-blog - you should be redirected to /blog.

  4. By default, Routing Middleware runs on every request. To limit it to specific paths, set proxy.matcher in vercel.json:

    vercel.json
    {
      "$schema": "https://openapi.vercel.sh/vercel.json",
      "proxy": {
        "entrypoint": "proxy.ts",
        "matcher": [
          "/((?!static|favicon.ico).*)"
        ]
      }
    }
    vercel.json
    {
      "$schema": "https://openapi.vercel.sh/vercel.json",
      "proxy": {
        "entrypoint": "proxy:proxy",
        "matcher": [
          "/((?!static|favicon.ico).*)"
        ]
      }
    }
    • The matcher array defines which paths trigger your Routing Middleware
    • The regex excludes paths under /static and /favicon.ico, so requests for static files skip your Routing Middleware
    • You can also use simple patterns like /blog/:path* for specific sections

    If you use the file convention, export a config object with a matcher property instead:

    middleware.ts
    export const config = {
      matcher: ['/((?!_next/static|_next/image|favicon.ico).*)'],
    };
    middleware.js
    export const config = {
      matcher: ['/((?!_next/static|_next/image|favicon.ico).*)'],
    };

    See path matching for the full matcher syntax.

  5. When things don't work as expected:

    1. Check the logs: Log liberally and check your Edgenivo dashboard Logs section in the sidebar
    2. Test the matcher: Make sure your paths are actually triggering the Routing Middleware
    3. Verify headers: Log the request headers to see what's available
    4. Test locally: Routing Middleware works in development too so you can debug before deploying
    proxy.ts
    export default function proxy(request: Request) {
      // Debug logging
      console.log('URL:', request.url);
      console.log('Method:', request.method);
      console.log('Headers:', Object.fromEntries(request.headers.entries()));
     
      // Your middleware logic here...
    }
    proxy.py
    from vercel.proxy import ContinueResponse, Proxy, Request, Response
     
    proxy = Proxy()
     
     
    @proxy.route("/{path:path}")
    def debug(request: Request) -> Response:
        # Debug logging
        print("URL:", request.url)
        print("Method:", request.method)
        print("Headers:", dict(request.headers))
     
        # Your middleware logic here...
        return ContinueResponse()
DetailValue
EntrypointAny path set with proxy.entrypoint, or middleware.ts in project root (proxy.ts for Next.js 16+)
Exportexport default function middleware(request: Request) (or export function proxy for Next.js 16+)
Config exportexport const config = { matcher: [...] }
Default runtimenodejs
Bun runtimeSet bunVersion in vercel.json and runtime: 'nodejs' in config
Request objectStandard Request API
Path matchingSupports regex, named params, and wildcards in the matcher config
DetailValue
EntrypointA module:attribute value set with proxy.entrypoint
ExportA top-level ASGI or WSGI app, such as proxy = Proxy()
Default runtimePython
DependenciesThe vercel.proxy dependency group in pyproject.toml
Request objectStarlette Request
Path matchingSupports regex, named params, and wildcards in proxy.matcher

Edgenivo adds geolocation headers, such as x-vercel-ip-country, to every request.

Last updated August 14, 2026

Was this helpful?

supported.