Vous mettez à jour vers Next.js 16, le build passe, puis un avertissement tombe : votre middleware.ts est déprécié. Le middleware Next.js 16 n'a pas disparu, il a été renommé proxy.ts, déplacé sur le runtime Node.js et recadré dans son rôle. Ce n'est pas un simple coup de peinture sémantique.
Derrière ce renommage se cachent trois changements qui touchent directement la production : un runtime différent avec ses conséquences sur la latence, un codemod à connaître pour migrer proprement, et un piège d'authentification capable d'exposer vos routes si vous comptiez uniquement sur le middleware. On passe chacun en revue, code à l'appui.
À la fin, vous saurez quelle commande lancer, quoi vérifier avant de déployer, et pourquoi l'équipe Next.js pousse désormais à utiliser cette fonctionnalité le moins possible.
Pourquoi Next.js 16 renomme le middleware en proxy
Le mot « middleware » est un faux ami. Pour la plupart des développeurs venus de Node, il évoque le middleware Express : une pile de fonctions qu'on empile sur le chemin d'une requête. Or le middleware Next.js n'a jamais fonctionné comme ça. Il s'exécute une seule fois, en amont du rendu, sur un périmètre réseau placé devant l'application.
L'équipe Next.js a tranché : ce malentendu encourageait les mauvais usages. On voyait des bases de données interrogées dans le middleware, de la logique métier entière déportée là où elle n'a rien à faire. Le nouveau nom, proxy, dit ce que la fonctionnalité est vraiment : un proxy qui intercepte la requête avant qu'elle n'atteigne vos routes, pour rediriger, réécrire ou ajouter un en-tête. Rien de plus.
Le message officiel est sans ambiguïté : proxy doit rester un dernier recours. Quand une logique peut vivre dans un Server Component, un Route Handler ou la couche de données, elle n'a pas sa place ici.
Du Edge runtime au Node.js : la latence change de camp
Jusqu'à Next.js 15, le middleware tournait par défaut sur l'Edge Runtime : un environnement léger, déployé au plus près de l'utilisateur, mais bridé — pas d'API Node complète, pas de modules natifs. Next.js 16 inverse la logique. proxy.ts s'exécute sur le runtime Node.js, à l'origine, et ce choix n'est pas configurable : l'option runtime lève une erreur si vous tentez de la définir.
| Critère | Middleware (≤ 15) | Proxy (16) |
|---|---|---|
| Runtime par défaut | Edge | Node.js |
| Localisation | Proche du client | À l'origine |
| API Node complètes | Non | Oui |
| Runtime configurable | Oui | Non |
Le gain : vous accédez enfin à tout l'écosystème Node, sans contournements. Le coût : la latence. Un utilisateur à Stockholm, une origine à us-east-1, et chaque redirection gérée par le proxy paie un aller-retour transatlantique — 80 à 100 ms ajoutés à chaque requête concernée. C'est l'argument massue pour ne mettre dans proxy.ts que l'essentiel et déplacer le reste au plus près des données.
Migrer avec le codemod middleware-to-proxy
Bonne nouvelle : la migration ne demande pas de réécriture manuelle. Next.js fournit un codemod qui renomme le fichier et la fonction d'un seul geste.
# Renomme middleware.ts en proxy.ts et la fonction associée
npx @next/codemod@canary middleware-to-proxy .
Concrètement, le codemod transforme l'export sans toucher au corps de votre logique :
- export function middleware() {
+ export function proxy() {
Le fichier proxy.ts se place exactement où vivait middleware.ts : à la racine du projet, ou dans src/, au même niveau que app/. La fonction reste un export par défaut ou un export nommé proxy, un seul par fichier. Si vous aviez personnalisé pageExtensions, le fichier suit la même règle (proxy.page.ts). Les drapeaux de configuration qui portaient le mot middleware changent aussi de nom, on y revient plus bas. Le reste — NextResponse, cookies, matcher — est strictement identique.
Le matcher du middleware Next.js 16 n'a pas bougé
Tout ce que vous saviez du matcher reste vrai. Sans lui, le proxy s'exécute sur chaque requête, y compris les fichiers statiques _next/static, l'optimisation d'images _next/image et les assets de public/. Oublier cette exclusion, c'est risquer de bloquer le chargement de votre CSS ou de vos images avec une logique d'auth mal ciblée.
export const config = {
// On exclut ce qui n'a pas besoin du proxy : la perf en dépend
matcher: ['/((?!api|_next/static|_next/image|favicon.ico).*)'],
}
Le matcher accepte toujours les chaînes, les tableaux, les expressions régulières avec lookahead négatif, ainsi que les conditions has et missing sur les en-têtes, cookies et query params. Un détail de sécurité à connaître : même si vous excluez _next/data du matcher, le proxy continue de s'exécuter sur ces routes. C'est volontaire — cela évite de protéger une page tout en laissant fuiter la route de données correspondante.
Proxy n'est pas une barrière de sécurité
Voici le piège qui coûte cher. Un proxy.ts n'est pas une barrière d'authentification fiable. Les Server Functions (Server Actions) ne sont pas des routes distinctes : elles sont traitées comme des requêtes POST vers la route où elles sont déclarées. Si votre matcher exclut ce chemin, le proxy ne s'exécute pas, et votre vérification d'auth saute avec lui, en silence.
Le proxy reste utile en première ligne : rediriger un visiteur non connecté, poser un en-tête, gérer le CORS. Mais la vraie autorisation se joue au plus près de la donnée. C'est exactement la logique qu'on applique avec une solution comme Clerk ou Better Auth, détaillée dans notre guide sur l'authentification Next.js App Router : la session se valide là où la donnée est lue, pas seulement à l'entrée.
Flags renommés : skipProxyUrlNormalize et compagnie
Dernier point de migration : les drapeaux avancés. skipMiddlewareUrlNormalize devient skipProxyUrlNormalize. Il désactive la normalisation d'URL de Next.js, utile quand vous gérez vous-même des réécritures via fetch et avez besoin de la forme d'URL brute, en-têtes RSC compris. skipTrailingSlashRedirect, lui, garde son nom : il vous laisse gérer manuellement les slashs de fin, pratique pour des migrations incrémentales.
Rappel utile : le runtime n'est plus configurable dans le proxy. Si une partie de votre ancienne logique dépendait explicitement de l'Edge Runtime, repensez-la — le code tourne désormais sur Node, à l'origine, avec les implications de latence vues plus haut.
En pratique
La migration du middleware Next.js 16 vers proxy.ts se résume à trois gestes : lancer le codemod, renommer les flags, et auditer ce que vous aviez mis dans le middleware. Profitez-en pour faire le ménage. Tout ce qui ressemble à de la logique métier ou à un accès base de données n'a rien à faire dans un proxy ; déplacez-le dans un Server Component ou un Route Handler.
Sur la latence, soyez chirurgical. Chaque chemin couvert par le proxy paie le coût d'un passage à l'origine. Réservez-le aux redirections et aux en-têtes, et appuyez-vous sur le système de cache de Next.js pour le reste, un sujet qu'on creuse dans notre article sur le caching de l'App Router. Côté validation des entrées, gardez Zod au niveau de vos Server Actions plutôt que dans le proxy, comme expliqué dans notre guide sur la validation avec Zod.
Chez Kreio, agence Next.js basée à Évreux (Normandie), on applique ces patterns sur des projets clients en production. Besoin d'un audit de migration vers Next.js 16 ou d'un renfort tech ? Parlons-en.