Le piège du middleware Next.js : pourquoi tu l'utilises probablement mal
#Le middleware Next.js : un outil puissant... et dangereux
Si tu utilises Next.js, tu as probablement un fichier middleware.ts à la racine de ton projet. Un fichier qui intercepte chaque requête avant qu'elle n'atteigne tes pages. Sur le papier, c'est génial : authentification, redirections, A/B testing, headers CORS... le middleware peut tout faire.
Le problème, c'est que beaucoup de développeurs en font trop avec. Et les conséquences vont de la latence inexpliquée à des failles de sécurité critiques.
#Comment fonctionne le middleware
Avant de parler des pièges, un rappel rapide. Le middleware est une fonction qui s'exécute côté serveur, avant le rendu de chaque route. Il se place dans un fichier middleware.ts (ou .js) à la racine du projet.
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
export function middleware(request: NextRequest) {
// Logique exécutée avant chaque requête
return NextResponse.next()
}
export const config = {
matcher: ['/dashboard/:path*', '/api/:path*'],
}
Le matcher définit les routes sur lesquelles le middleware s'exécute. Sans matcher, il tourne sur toutes les routes — y compris les fichiers statiques.
Le middleware s'exécute dans l'ordre suivant : headers next.config.js, redirects next.config.js, middleware, puis les routes du filesystem.
#Piège 1 : tout mettre dans le middleware
C'est le piège le plus courant. Tu commences par une vérification d'authentification. Puis tu ajoutes du logging. Puis du rate limiting. Puis de la géolocalisation. Et soudain, ton middleware fait 200 lignes et chaque requête est ralentie.
Le middleware est conçu pour être léger et rapide. Il doit faire le minimum :
- Vérifier un cookie ou un token JWT
- Rediriger vers la bonne page
- Ajouter un header
Ce qu'il ne devrait jamais faire :
- Appeler une base de données
- Effectuer des calculs lourds
- Faire des appels API externes lents
- Gérer de la logique métier complexe
Un middleware lent ralentit TOUTES les requêtes de ton application, pas juste celles qui en ont besoin. C'est le chemin critique de chaque page.
#Piège 2 : les limites de l'Edge Runtime
C'est ici que ça se complique. Jusqu'à Next.js 15.5, le middleware tournait exclusivement sur l'Edge Runtime — un environnement restreint, plus proche de Cloudflare Workers que de Node.js.
Concrètement, ça veut dire :
- Pas d'API Node.js :
fs,crypto,net,child_process... rien de tout ça - Pas de TCP sockets : impossible de se connecter directement à PostgreSQL ou MySQL
- Beaucoup de packages npm incompatibles : tout ce qui dépend de Node.js natif échoue silencieusement ou crashe
Un exemple classique : tu veux vérifier un JWT avec jsonwebtoken. Ça ne marche pas sur l'Edge Runtime parce que la librairie utilise crypto de Node.js. Il faut utiliser jose à la place, qui est compatible Edge.
// Ne fonctionne PAS sur l'Edge Runtime
import jwt from 'jsonwebtoken'
// Fonctionne sur l'Edge Runtime
import { jwtVerify } from 'jose'
export async function middleware(request: NextRequest) {
const token = request.cookies.get('session')?.value
if (!token) return NextResponse.redirect(new URL('/login', request.url))
try {
const secret = new TextEncoder().encode(process.env.JWT_SECRET)
await jwtVerify(token, secret)
return NextResponse.next()
} catch {
return NextResponse.redirect(new URL('/login', request.url))
}
}
#Piège 3 : la boucle de redirections infinie
Un grand classique. Tu protèges /dashboard en redirigeant vers /login si l'utilisateur n'est pas connecté. Mais ton middleware s'exécute aussi sur /login. Et comme l'utilisateur n'est toujours pas connecté quand il arrive sur /login... il est redirigé vers /login. En boucle.
La solution : un matcher bien configuré qui exclut les pages publiques.
export const config = {
matcher: [
// Exclure les fichiers statiques, les API, et les pages publiques
'/((?!api|_next/static|_next/image|login|register|favicon.ico).*)',
],
}
Toujours définir un matcher explicite. Ne laisse jamais le middleware s'exécuter sur toutes les routes par défaut.
#Piège 4 : faire confiance au middleware pour la sécurité
C'est peut-être le piège le plus dangereux. En mars 2025, une faille critique (CVE-2025-29927) a été découverte dans Next.js. Un attaquant pouvait contourner complètement le middleware en ajoutant un simple header HTTP :
x-middleware-subrequest: middleware
Ce header, normalement utilisé en interne par Next.js pour éviter les boucles de middleware, était accepté depuis l'extérieur sans vérification. Résultat : toute la logique d'authentification du middleware était ignorée.
Les versions concernées : Next.js 11.1.4 à 13.5.6, 14.x avant 14.2.25, et 15.x avant 15.2.3.
Le middleware ne doit JAMAIS être ta seule couche de sécurité. Vercel recommande officiellement de ne pas s'appuyer uniquement sur le middleware pour l'authentification.
La bonne approche : une défense en profondeur. Le middleware peut faire une première vérification rapide (un cookie existe-t-il ?), mais la vérification réelle doit se faire dans un Data Access Layer (DAL) — une couche dédiée qui vérifie l'authentification à chaque accès aux données.
// lib/dal.ts — Data Access Layer
import { cookies } from 'next/headers'
import { verifySession } from './auth'
export async function getCurrentUser() {
const session = (await cookies()).get('session')?.value
if (!session) return null
const user = await verifySession(session)
return user
}
// Utilisé dans chaque Server Component ou Route Handler
// qui accède à des données protégées
#Piège 5 : ignorer la migration vers proxy.ts
Depuis Next.js 16, le middleware est officiellement déprécié. Le nouveau fichier s'appelle proxy.ts et apporte des changements majeurs :
| middleware.ts (ancien) | proxy.ts (nouveau) | |
|---|---|---|
| Runtime | Edge Runtime | Node.js |
| API Node.js | Non disponibles | Disponibles |
| Base de données | Impossible directement | Possible |
| Nom de la fonction | middleware() | proxy() |
| Statut | Déprécié | Actif |
Le passage au runtime Node.js résout la plupart des problèmes de compatibilité. Tu peux enfin utiliser n'importe quel package npm, te connecter à une base de données, et utiliser les API Node.js standard.
La migration est simple :
// Avant : middleware.ts
export function middleware(request: NextRequest) {
// ...
}
// Après : proxy.ts
export function proxy(request: NextRequest) {
// ...
}
Next.js fournit un codemod pour automatiser le renommage :
npx @next/codemod@canary middleware-to-proxy .
Le fichier middleware.ts fonctionne encore si tu as besoin de l'Edge Runtime, mais il sera supprimé dans une future version. Planifie ta migration.
#Les bonnes pratiques
Voici un résumé de ce que tu devrais faire (et ne pas faire) avec le middleware / proxy Next.js :
À faire :
- Utiliser un
matcherprécis pour limiter les routes concernées - Garder la logique légère : cookies, headers, redirections
- Vérifier l'authentification aussi dans ton DAL (défense en profondeur)
- Migrer vers
proxy.tssi tu es sur Next.js 16 ou plus - Utiliser
joseau lieu dejsonwebtokensi tu restes sur l'Edge Runtime
À éviter :
- Appeler une base de données depuis le middleware
- Ajouter de la logique métier complexe
- Faire confiance au middleware comme seule couche de sécurité
- Laisser le middleware s'exécuter sur toutes les routes
- Ignorer les mises à jour de sécurité de Next.js
#En résumé
Le middleware Next.js est un outil puissant, mais son nom et sa simplicité apparente poussent les développeurs à en abuser. L'équipe Next.js l'a bien compris — c'est d'ailleurs pour ça qu'ils l'ont renommé en "proxy" dans la version 16, pour clarifier son rôle : c'est une couche d'interception réseau, pas un endroit pour mettre ta logique applicative.
La règle d'or : si ta logique est plus complexe qu'une vérification de cookie et une redirection, elle n'a probablement rien à faire dans le middleware. Déplace-la dans un Server Component, un Route Handler ou un Data Access Layer.