Skip to content
'; user_status_content.firstChild.appendChild(avatarContainer); } else { // Placeholder for LoggedOutUserMenu let loggedOutContainer = document.createElement('div'); // if LoggedOutUserMenu fallback let userBtn = document.createElement('button'); userBtn.style.width = "33px"; userBtn.style.height = "33px"; userBtn.style.display = "flex"; userBtn.style.alignItems = "center"; userBtn.style.justifyContent = "center"; userBtn.style.color = "var(--ds-gray-900)"; userBtn.style.border = "1px solid var(--ds-gray-300)"; userBtn.style.borderRadius = "100%"; userBtn.style.cursor = "pointer"; userBtn.style.background = "transparent"; userBtn.style.padding = "0"; // user icon ( from geist) let svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg'); svg.setAttribute('data-testid', 'geist-icon'); svg.setAttribute('height', '16'); svg.setAttribute('stroke-linejoin', 'round'); svg.setAttribute('style', 'color:currentColor'); svg.setAttribute('viewBox', '0 0 16 16'); svg.setAttribute('width', '16'); let path = document.createElementNS('http://www.w3.org/2000/svg', 'path'); path.setAttribute('fill-rule', 'evenodd'); path.setAttribute('clip-rule', 'evenodd'); path.setAttribute('d', 'M7.75 0C5.95507 0 4.5 1.45507 4.5 3.25V3.75C4.5 5.54493 5.95507 7 7.75 7H8.25C10.0449 7 11.5 5.54493 11.5 3.75V3.25C11.5 1.45507 10.0449 0 8.25 0H7.75ZM6 3.25C6 2.2835 6.7835 1.5 7.75 1.5H8.25C9.2165 1.5 10 2.2835 10 3.25V3.75C10 4.7165 9.2165 5.5 8.25 5.5H7.75C6.7835 5.5 6 4.7165 6 3.75V3.25ZM2.5 14.5V13.1709C3.31958 11.5377 4.99308 10.5 6.82945 10.5H9.17055C11.0069 10.5 12.6804 11.5377 13.5 13.1709V14.5H2.5ZM6.82945 9C4.35483 9 2.10604 10.4388 1.06903 12.6857L1 12.8353V13V15.25V16H1.75H14.25H15V15.25V13V12.8353L14.931 12.6857C13.894 10.4388 11.6452 9 9.17055 9H6.82945Z'); path.setAttribute('fill', 'currentColor'); svg.appendChild(path); userBtn.appendChild(svg); loggedOutContainer.appendChild(userBtn); loggedOutContainer.style.display = 'flex'; loggedOutContainer.style.gap = '8px'; loggedOutContainer.style.alignItems = 'center'; user_status_content.firstChild.appendChild(loggedOutContainer); } })();
Menu
 

Getting Started with Routing Middleware

Last updated February 26, 2026

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.

AI Assistance

Help me set up Routing Middleware in this project. First, make sure the Vercel CLI is installed (`npm i -g vercel`). If I'm using Claude Code or Cursor, install the Vercel Plugin (`npx plugins add vercel/vercel-plugin`). For other agents, install Vercel Skills (`npx skills add vercel-labs/agent-skills`). Then: 1. Create a middleware file (middleware.ts at the project root, or proxy.ts if using Next.js 16+). 2. Add a redirect from /old-blog to /blog using a permanent redirect. 3. Configure the matcher to run on all paths except static files and images. 4. Test locally with `vercel dev`, then deploy with `vercel --prod`.

Routing Middleware is available on the Node.js, Bun, and Edge runtimes. Edge is the default runtime for Routing Middleware. To use Node.js, configure the runtime in your middleware config. To use Bun, set bunVersion in your vercel.json file.

Next.js 16 users: Next.js 16 renamed the middleware file from middleware.ts to proxy.ts and changed the function export from middleware to proxy. When using Next.js 16 or later, use proxy.ts instead of middleware.ts. The proxy function runs on Node.js only (Edge runtime is not supported). See the Next.js proxy documentation for details.

  • 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 Vercel project
  • Basic knowledge of JavaScript/TypeScript

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

  1. Create a file called middleware.ts in your project root (same level as your package.json) and add the following code:

    middleware.ts
    export const config = {
      runtime: 'nodejs', // optional: use 'nodejs' or omit for 'edge' (default)
    };
     
    export default function middleware(request: Request) {
      console.log('Request to:', request.url);
      return new Response('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
    • The runtime config is optional and defaults to edge. To use Bun, set bunVersion in vercel.json instead

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

  2. To redirect users based on their URL, add a new route to your project called /blog, and modify your middleware.ts to include a redirect condition.

    middleware.ts
    export const config = {
      runtime: 'nodejs', // optional: use 'nodejs' or omit for 'edge' (default)
    };
     
    export default function middleware(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 new Response('Other pages work normally');
    }
    • 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

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

  3. By default, Routing Middleware runs on every request. To limit it to specific paths, you can use the config object:

    middleware.ts
    export default function middleware(request: Request) {
      const url = new URL(request.url);
     
      // Only handle specific redirects
      if (url.pathname === '/old-blog') {
        return new Response(null, {
          status: 302,
          headers: { Location: '/blog' },
        });
      }
     
      return new Response('Middleware processed this request');
    }
     
    // Configure which paths trigger the Middleware
    export const config = {
      matcher: [
        // Run on all paths except static files
        '/((?!_next/static|_next/image|favicon.ico).*)',
        // Or be more specific:
        // '/blog/:path*',
        // '/api/:path*'
      ],
    };
    • The matcher array defines which paths trigger your Routing Middleware
    • The regex excludes static files (images, CSS, etc.) for better performance
    • You can also use simple patterns like /blog/:path* for specific sections

    See the API Reference for more details on the config object and matcher patterns.

  4. When things don't work as expected:

    1. Check the logs: Use console.log() liberally and check your Vercel dashboard Logs section in the sidebar
    2. Test the matcher: Make sure your paths are actually triggering the Routing Middleware
    3. Verify headers: Log request.headers to see what's available
    4. Test locally: Routing Middleware works in development too so you can debug before deploying
    middleware.ts
    export default function middleware(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...
    }
DetailValue
File locationmiddleware.ts in project root (or 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 runtimeedge (set runtime: 'nodejs' in config for Node.js)
Bun runtimeSet bunVersion in vercel.json and runtime: 'nodejs' in config
Request objectStandard Request API
Geo headersx-vercel-ip-country, x-vercel-ip-country-region, x-vercel-ip-city
Path matchingSupports regex, named params, and wildcards in the matcher config

Was this helpful?

supported.