Next.js 16: Migrating from Middleware to Proxy
@himanshuchandola||2 min read
Next.js 16 introduces a significant change: the middleware.ts file convention is deprecated in favor of proxy.ts. Here's what you need to know about the migration.
What Changed?
In Next.js 16, the middleware system has been renamed to "proxy" to better reflect its purpose — intercepting and modifying requests before they reach your routes.
The breaking change:
# Old (deprecated)
src/middleware.ts → exports function middleware()
# New (Next.js 16+)
src/proxy.ts → exports function proxy()
The Error You'll See
If you just rename the file without updating the export, you'll get this error:
⨯ The file "./src/proxy.ts" must export a function,
either as a default export or as a named "proxy" export.
How to Migrate
Step 1: Rename the file
mv src/middleware.ts src/proxy.ts
Step 2: Rename the exported function
// Before
export async function middleware(request: NextRequest) {
// ...
}
// After
export async function proxy(request: NextRequest) {
// ...
}
That's it! The config export with the matcher stays the same.
Full Example
Here's what a complete proxy.ts looks like:
import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";
export async function proxy(request: NextRequest) {
const { pathname } = request.nextUrl;
// Add security headers
const response = NextResponse.next();
response.headers.set("X-Frame-Options", "DENY");
response.headers.set("X-Content-Type-Options", "nosniff");
// Rate limiting for API routes
if (pathname.startsWith("/api/")) {
// Your rate limiting logic
}
return response;
}
export const config = {
matcher: [
"/api/:path*",
"/((?!_next/static|_next/image|favicon.ico).*)",
],
};
Why the Change?
The rename makes sense conceptually:
- Middleware implies something in the middle of request processing
- Proxy better describes intercepting and forwarding requests
It also aligns with how other frameworks describe this pattern.
The old middleware.ts convention still works but will show a deprecation warning. Update now to avoid issues in future versions.
Common Patterns That Still Work
All your existing patterns continue to work — just rename the function:
Rate Limiting
export async function proxy(request: NextRequest) {
const ip = request.headers.get("x-forwarded-for");
const { success } = await rateLimiter.limit(ip);
if (!success) {
return new NextResponse("Too many requests", { status: 429 });
}
return NextResponse.next();
}
Authentication Checks
export async function proxy(request: NextRequest) {
const token = request.cookies.get("auth-token");
if (!token && request.nextUrl.pathname.startsWith("/dashboard")) {
return NextResponse.redirect(new URL("/login", request.url));
}
return NextResponse.next();
}
Geolocation Headers
export async function proxy(request: NextRequest) {
const response = NextResponse.next();
const country = request.geo?.country ?? "US";
response.headers.set("x-user-country", country);
return response;
}
Vercel Edge Runtime
The proxy runs on Vercel's Edge Runtime by default, giving you:
- Low latency — runs at the edge closest to users
- Fast cold starts — typically under 50ms
- Global distribution — same code runs worldwide
Migration Checklist
- [ ] Rename
middleware.ts→proxy.ts - [ ] Change
export async function middleware→export async function proxy - [ ] Test locally with
npm run dev - [ ] Deploy and verify no warnings in logs
The change is minimal but will keep your Next.js app aligned with the latest conventions.