De ce middleware.ts a devenit proxy.ts în Next.js 16
Dacă ai lucrat cu Next.js în ultimii ani, aproape sigur ai un fișier middleware.ts în rădăcina proiectului. Poate verifică dacă utilizatorul e autentificat, poate redirecționează pe baza limbii, poate setează câteva headere de securitate. Ei bine, odată cu Next.js 16, acel fișier are un nou nume — și nu e doar o chestiune cosmetică, cum s-ar putea crede la prima vedere.
Echipa Vercel a decis să redenumească middleware.ts în proxy.ts. De ce? Termenul „middleware" crea confuzie constantă cu middleware-ul din Express.js, sugerând că poți face orice acolo — apeluri la baze de date, validare complexă de JWT-uri, logică de business. Sincer, am văzut proiecte în care middleware-ul devenise un monstru de sute de linii. În realitate, acest fișier a fost întotdeauna gândit ca un strat subțire la granița rețelei.
Noul nume — „proxy" — comunică mult mai clar rolul său: un proxy care interceptează request-uri înainte să ajungă la aplicație. Perfect pentru redirect-uri, rewrite-uri și verificări rapide. Nimic mai mult, nimic mai puțin.
Ce s-a schimbat tehnic: Runtime-ul
Dincolo de simpla redenumire, există o schimbare tehnică pe care nu trebuie s-o ignori.
Noul proxy.ts rulează implicit pe runtime-ul Node.js, nu pe Edge Runtime cum era middleware-ul. Asta înseamnă acces complet la API-urile Node.js, dar și un model mental mai clar — proxy-ul trăiește pe server, alături de restul aplicației tale.
Dacă totuși ai nevoie de Edge Runtime (de exemplu, pentru latență minimă la nivel global), poți continua să folosești middleware.ts — dar e depreciat și va fi eliminat într-o versiune viitoare. Deci, recomandarea oficială e să migrezi la proxy.ts cât mai curând posibil.
Diferențe cheie între middleware.ts și proxy.ts
- Nume fișier:
middleware.ts→proxy.ts - Funcție exportată:
middleware()→proxy() - Runtime implicit: Edge Runtime → Node.js
- Config flags:
skipMiddlewareUrlNormalize→skipProxyUrlNormalize - Funcționalitate: identică — redirect-uri, rewrite-uri, headere, cookies
Cum migrezi de la middleware.ts la proxy.ts
Ai două opțiuni: manual sau automat. Ambele sunt simple, dar hai să le luăm pe rând.
Opțiunea 1: Codemod automat (recomandat)
Next.js oferă un codemod care face totul automat — redenumește fișierul, schimbă numele funcției exportate și actualizează flag-urile de configurare. Pur și simplu rulezi:
npx @next/codemod@canary middleware-to-proxy .
Comanda scanează proiectul, redenumește middleware.ts în proxy.ts, transformă export function middleware în export function proxy și actualizează orice referință la skipMiddlewareUrlNormalize. Destul de convenabil, nu?
Opțiunea 2: Migrare manuală
Dacă preferi controlul total (și uneori chiar merită, mai ales dacă ai o configurare mai neobișnuită), pașii sunt simpli:
- Redenumește fișierul din
middleware.tsînproxy.ts - Schimbă numele funcției exportate din
middlewareînproxy - Înlocuiește
skipMiddlewareUrlNormalizecuskipProxyUrlNormalizeînnext.config.js
Iată un exemplu concret, înainte și după:
Înainte (middleware.ts):
// middleware.ts
import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";
export function middleware(request: NextRequest) {
const session = request.cookies.get("session");
if (!session) {
return NextResponse.redirect(new URL("/login", request.url));
}
return NextResponse.next();
}
export const config = {
matcher: ["/dashboard/:path*"],
};
După (proxy.ts):
// proxy.ts
import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";
export function proxy(request: NextRequest) {
const session = request.cookies.get("session");
if (!session) {
return NextResponse.redirect(new URL("/login", request.url));
}
return NextResponse.next();
}
export const config = {
matcher: ["/dashboard/:path*"],
};
Observi diferența? Practic, doar numele funcției s-a schimbat. Logica rămâne identică — ceea ce face migrarea mult mai puțin intimidantă decât pare.
Configurarea matcher-ului: Filtrează unde rulează proxy-ul
Una dintre cele mai importante configurări e matcher-ul. Fără el, proxy-ul rulează pe fiecare request — inclusiv pentru imagini, fonturi, fișiere statice și JavaScript-ul framework-ului. Serios, nu vrei asta.
Matcher simplu
export const config = {
matcher: ["/dashboard/:path*", "/api/:path*"],
};
Excluderea fișierelor statice (pattern recomandat)
export const config = {
matcher: [
{
source: "/((?!api|_next/static|_next/image|favicon.ico).*)",
missing: [
{ type: "header", key: "next-router-prefetch" },
{ type: "header", key: "purpose", value: "prefetch" },
],
},
],
};
Acest pattern face două lucruri esențiale: exclude rutele interne Next.js și fișierele statice, plus ignoră prefetch-urile automate de la next/link. E un pattern pe care-l recomand în aproape orice proiect.
Matcher avansat cu condiții
Matcher-ul suportă și condiții bazate pe headere, cookies și query params — ceea ce îl face surprinzător de flexibil:
export const config = {
matcher: [
{
source: "/api/:path*",
has: [
{ type: "header", key: "Authorization" },
],
},
{
source: "/admin/:path*",
missing: [
{ type: "cookie", key: "admin_session" },
],
},
],
};
Modificatori de pattern
Parametrii numiți (cei care încep cu :) suportă modificatori pentru flexibilitate extra:
/blog/:slug— se potrivește exact cu un segment dinamic/blog/:path*— zero sau mai multe segmente/blog/:path+— unul sau mai multe segmente/blog/:path?— zero sau un segment
Cazuri practice de utilizare cu exemple de cod
Bon, hai să trecem la partea cea mai utilă — exemple concrete pe care le poți adapta direct în proiectele tale.
1. Verificare de autentificare cu redirect
Cel mai comun pattern — redirecționezi utilizatorii neautentificați la pagina de login:
// proxy.ts
import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";
export function proxy(request: NextRequest) {
const token = request.cookies.get("auth_token");
// Rute publice - le lași să treacă
const publicPaths = ["/login", "/register", "/forgot-password"];
if (publicPaths.some((p) => request.nextUrl.pathname.startsWith(p))) {
return NextResponse.next();
}
// Fără token? Redirect la login
if (!token) {
const loginUrl = new URL("/login", request.url);
loginUrl.searchParams.set("callbackUrl", request.nextUrl.pathname);
return NextResponse.redirect(loginUrl);
}
return NextResponse.next();
}
export const config = {
matcher: ["/((?!_next/static|_next/image|favicon.ico).*)"],
};
Un sfat important: nu valida JWT-uri complexe în proxy. Verifică doar existența cookie-ului de sesiune. Validarea completă aparține Server Components sau Server Actions, unde ai acces complet la baza de date și poți face verificări mai amănunțite.
2. Protejarea rutelor API
Poți returna direct un răspuns JSON fără a ajunge la route handler — util când vrei să blochezi accesul devreme:
// proxy.ts
import type { NextRequest } from "next/server";
export function proxy(request: NextRequest) {
const apiKey = request.headers.get("x-api-key");
if (!apiKey || apiKey !== process.env.API_SECRET_KEY) {
return Response.json(
{ error: "Cheie API lipsă sau invalidă" },
{ status: 401 }
);
}
}
export const config = {
matcher: "/api/:path*",
};
3. Headere de securitate globale
Adaugă headere de securitate la toate răspunsurile. E unul din acele lucruri pe care ar trebui să le ai în orice aplicație de producție:
// proxy.ts
import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";
export function proxy(request: NextRequest) {
const response = NextResponse.next();
response.headers.set("X-Frame-Options", "DENY");
response.headers.set("X-Content-Type-Options", "nosniff");
response.headers.set("Referrer-Policy", "strict-origin-when-cross-origin");
response.headers.set(
"Permissions-Policy",
"camera=(), microphone=(), geolocation=()"
);
return response;
}
export const config = {
matcher: ["/((?!_next/static|_next/image).*)"],
};
4. Redirect pe baza geolocalizării
Folosește datele de geolocalizare pentru rutare bazată pe regiune. Asta e deosebit de util pentru site-urile multilingve:
// proxy.ts
import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";
export function proxy(request: NextRequest) {
const country = request.geo?.country || "US";
const pathname = request.nextUrl.pathname;
// Deja pe o rută localizată? Lasă să treacă
if (pathname.startsWith("/ro") || pathname.startsWith("/en")) {
return NextResponse.next();
}
// Redirect pe baza țării
const localeMap: Record = {
RO: "/ro",
MD: "/ro",
DE: "/de",
FR: "/fr",
};
const locale = localeMap[country] || "/en";
return NextResponse.redirect(
new URL(`${locale}${pathname}`, request.url)
);
}
5. A/B Testing cu rewrite
Ăsta e un pattern pe care nu-l vezi suficient de des în practică, dar e extrem de puternic. Distribuie utilizatorii în grupuri de test fără a schimba URL-ul din browser:
// proxy.ts
import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";
export function proxy(request: NextRequest) {
const pathname = request.nextUrl.pathname;
if (pathname === "/pricing") {
// Verifică dacă utilizatorul are deja un grup asignat
let bucket = request.cookies.get("ab_pricing")?.value;
if (!bucket) {
// Asignare aleatorie: 50/50
bucket = Math.random() < 0.5 ? "control" : "variant";
}
const response = bucket === "variant"
? NextResponse.rewrite(new URL("/pricing-variant", request.url))
: NextResponse.next();
// Persistă grupul în cookie pentru consistență
response.cookies.set("ab_pricing", bucket, {
maxAge: 60 * 60 * 24 * 30, // 30 zile
httpOnly: true,
});
return response;
}
return NextResponse.next();
}
Prin rewrite, utilizatorul vede /pricing în browser, dar conținutul vine de la /pricing-variant. Cookie-ul asigură consistența — aceeași persoană vede aceeași variantă la fiecare vizită. Simplu și elegant.
Organizarea modulară a logicii de proxy
Un singur fișier proxy.ts e suportat per proiect. Dar asta nu înseamnă că trebuie să pui totul într-un fișier imens (și te rog, nu o face). Poți modulariza logica prin importuri:
// lib/proxy/auth.ts
import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";
export function handleAuth(request: NextRequest) {
const token = request.cookies.get("auth_token");
if (!token) {
return NextResponse.redirect(new URL("/login", request.url));
}
return null; // continua cu urmatorul handler
}
// lib/proxy/security.ts
import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";
export function addSecurityHeaders(response: NextResponse) {
response.headers.set("X-Frame-Options", "DENY");
response.headers.set("X-Content-Type-Options", "nosniff");
return response;
}
// proxy.ts
import { handleAuth } from "@/lib/proxy/auth";
import { addSecurityHeaders } from "@/lib/proxy/security";
import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";
export function proxy(request: NextRequest) {
// 1. Verificare autentificare
const authResponse = handleAuth(request);
if (authResponse) return authResponse;
// 2. Adaugă headere de securitate
const response = NextResponse.next();
return addSecurityHeaders(response);
}
Structura recomandată de directoare arată cam așa:
├── lib/
│ └── proxy/
│ ├── auth.ts # Logica de autentificare
│ ├── security.ts # Headere de securitate
│ ├── i18n.ts # Internaționalizare
│ └── ab-testing.ts # A/B testing
├── proxy.ts # Fișierul principal
└── next.config.js
Integrarea cu Auth.js v5 și alte librării de autentificare
Dacă folosești Auth.js (fostul NextAuth), Clerk sau Supabase Auth, migrarea la proxy.ts necesită puțină atenție suplimentară. Din fericire, principalele librării s-au adaptat deja.
Auth.js v5
Auth.js e deja compatibil cu schimbarea. Dacă folosești Next.js 16, exportul se face din proxy.ts așa:
// proxy.ts
export { auth as proxy } from "@/auth";
export const config = {
matcher: ["/((?!api|_next/static|_next/image|favicon.ico).*)"],
};
Linia export { auth as proxy } redenumește automat funcția auth exportată de Auth.js în proxy, respectând noua convenție. O singură linie, nici o bătaie de cap.
Supabase Auth
Pentru Supabase, actualizarea e similară — schimbi numele fișierului și al funcției exportate, dar logica de refresh a token-ului rămâne aceeași:
// proxy.ts
import { createServerClient } from "@supabase/ssr";
import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";
export async function proxy(request: NextRequest) {
let response = NextResponse.next({
request: { headers: request.headers },
});
const supabase = createServerClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
{
cookies: {
getAll() {
return request.cookies.getAll();
},
setAll(cookiesToSet) {
cookiesToSet.forEach(({ name, value, options }) => {
response.cookies.set(name, value, options);
});
},
},
}
);
await supabase.auth.getUser();
return response;
}
Greșeli frecvente și cum să le eviți
Am văzut (și recunosc, am făcut) destule greșeli cu middleware-ul de-a lungul timpului. Iată cele mai frecvente capcane cu noul proxy.ts.
1. Bucle infinite de redirect
Probabil cea mai clasică greșeală. Redirecționezi utilizatorii neautentificați la /login, dar proxy-ul rulează și pe /login, detectează absența token-ului și... redirecționează din nou la /login. La infinit. Soluția e simplă — exclude rutele publice:
const publicPaths = ["/login", "/register", "/api/auth"];
if (publicPaths.some((p) => request.nextUrl.pathname.startsWith(p))) {
return NextResponse.next();
}
2. Operații grele în proxy
Nu apela baze de date, nu verifica JWT-uri cu operații criptografice complexe, nu faci request-uri HTTP către servicii externe lente. Proxy-ul rulează pe fiecare request — orice latență adăugată aici afectează TTFB-ul (Time To First Byte) al întregii aplicații. Ăsta e genul de problemă care te costă mult în producție.
3. Lipsa matcher-ului
Fără matcher, proxy-ul interceptează absolut totul — inclusiv fișierele statice din _next/static, imaginile, fonturile. Întotdeauna definește un matcher care exclude resursele statice. Serios, nu sări peste asta.
4. Confuzia cu revalidarea ISR
Un detaliu care scapă multora: proxy-ul nu se execută pentru request-urile de revalidare ISR on-demand. Dacă ai rewrite-uri sau logică de rutare în proxy care afectează o rută ISR, asigură-te că revalidezi calea exactă, nu cea rescrisă.
Checklist de migrare de la Next.js 15 la Next.js 16
Înainte să consideri migrarea completă, treci prin lista asta. Am compilat-o din experiența proprie și din ce am citit în discuțiile comunității:
- Rulează codemod-ul:
npx @next/codemod@canary middleware-to-proxy . - Verifică că funcția exportată se numește
proxy, numiddleware - Actualizează
skipMiddlewareUrlNormalizelaskipProxyUrlNormalize - Verifică dacă librăriile de autentificare (Auth.js, Clerk, Supabase) au versiuni compatibile cu Next.js 16
- Testează că Edge Runtime nu mai e necesar — sau păstrează temporar
middleware.tspentru cazurile Edge - Mută orice logică grea (validare JWT, apeluri DB) în Server Components sau Server Actions
- Confirmă că matcher-ul exclude fișierele statice și prefetch-urile
- Testează rutele protejate, redirect-urile și rewrite-urile manual
Întrebări frecvente
Pot folosi și middleware.ts și proxy.ts în același proiect?
Nu. Next.js 16 suportă doar un singur fișier de interceptare a request-urilor. Dacă ai ambele fișiere, proxy.ts are prioritate. Cel mai bine e să migrezi complet la proxy.ts și să ștergi middleware.ts.
Ce se întâmplă dacă nu migrez la proxy.ts?
Deocamdată, middleware.ts funcționează în continuare în Next.js 16 — dar e marcat ca depreciat. Vei primi avertismente în consolă, iar într-o versiune viitoare va fi eliminat complet. E mult mai bine să migrezi acum, pe termenii tăi, decât să fii forțat de un breaking change pe nepregătite.
proxy.ts rulează pe Edge Runtime sau Node.js?
Implicit, proxy.ts rulează pe Node.js runtime. Asta diferă de vechiul middleware.ts, care rula pe Edge Runtime. Dacă ai nevoie specifică de Edge, poți continua temporar cu middleware.ts, dar ține cont că va fi depreciat.
Cum transmit date din proxy către componente?
Proxy-ul e separat de codul de rendering. Pentru a transmite informații, folosește headere de request (request.headers.set), cookies, rewrite-uri cu query params sau URL-ul direct. Nu poți folosi variabile globale sau module partajate între proxy și componentele tale — știu că ar fi fost convenabil, dar din păcate nu funcționează așa.
Trebuie să schimb și configurația next-intl sau alte librării i18n?
Da, în cele mai multe cazuri. Librăriile care exportau funcții pentru middleware.ts (precum next-intl) trebuie actualizate. De obicei, e suficient să redenumești fișierul și să ajustezi exportul. Verifică documentația librăriei pentru instrucțiuni specifice privind Next.js 16.