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

## Getting Started Steps

1. Make sure the Vercel CLI is installed (`npm i -g vercel`). If 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`).  
2. Create a middleware file (`middleware.ts` at the project root, or `proxy.ts` if using Next.js 16+).
3. Add a redirect from `/old-blog` to `/blog` using a permanent redirect.
4. Configure the 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](/content/docs/functions/runtimes/node-js/index.html), [Bun](/content/docs/functions/runtimes/bun/index.html), and [Edge](/content/docs/functions/runtimes/edge/index.html) 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`](/content/docs/project-configuration/vercel-json#bunversion/index.html) 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](https://nextjs.org/docs/app/api-reference/file-conventions/proxy) for details.

## What you will learn

- 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

## Prerequisites

- A Vercel project
- Basic knowledge of JavaScript/TypeScript

## Creating a Routing Middleware

### Create a new file for your Routing Middleware

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

```javascript
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`](/content/docs/project-configuration/vercel-json#bunversion/index.html) 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.

### Redirecting users

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.

```javascript
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`.

### Configure which paths trigger the middleware

By default, Routing Middleware runs on every request. To limit it to specific paths, you can use the [`config`](/content/docs/routing-middleware/api#config-object/index.html) object:

```javascript
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`](/content/docs/routing-middleware/api#match-paths-based-on-custom-matcher-config/index.html) 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.

### Debugging Routing Middleware

When things don't work as expected:

1. Check the logs: Use `console.log()` liberally and check your [Vercel dashboard](/content/dashboard/index.html) Logs section.
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.

```javascript
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...
}
```

## Middleware reference

| Detail | Value |
| --- | --- |
| File location | Any path set with [`proxy.entrypoint`](/content/docs/project-configuration/vercel-json#proxy/index.html), or `middleware.ts` in project root (`proxy.ts` for Next.js 16+) |
| Export | `export default function middleware(request: Request)` (or `export function proxy` for Next.js 16+) |
| [Config export](/content/docs/routing-middleware/api/index.html) | `export const config = { matcher: [...] }` |
| Default runtime | [`edge`](/content/docs/functions/runtimes/edge/index.html) (set `runtime: 'nodejs'` in config for [Node.js](/content/docs/functions/runtimes/node-js/index.html)) |
| Bun runtime | Set [`bunVersion`](/content/docs/project-configuration/vercel-json/index.html) in `vercel.json` and `runtime: 'nodejs'` in config |
| Request object | Standard `Request` API |
| Geo headers | `x-vercel-ip-country`, `x-vercel-ip-country-region`, `x-vercel-ip-city` |
| [Path matching](/content/docs/routing-middleware/api/index.html) | Supports regex, named params, and wildcards in the `matcher` config |

## Next steps

- [Routing Middleware overview](/content/docs/routing-middleware/index.html)
- [Routing Middleware API reference](/content/docs/routing-middleware/api/index.html)  
_Last updated August 14, 2026_
