Middleware Next.js : guide pratique — matcher, limites Edge Runtime et pièges courants

Les logs d’erreur Vercel. Le back-office venait d’être mis en ligne : en local, toutes les routes /dashboard étaient bien protégées — les visiteurs non connectés étaient redirigés vers la page de connexion. En production, un accès direct à /dashboard/settings/profile contournait la vérification et exposait des données sensibles.
J’ouvre le code : le fichier Middleware est bien là, la logique semble correcte. Alors, quoi ?
Après une demi-heure dans la doc Next.js, la réponse est dans la section matcher : j’avais écrit /dashboard/:path, qui ne couvre qu’un seul niveau comme /dashboard/settings. Les chemins multi-niveaux passaient à travers. La bonne forme : /dashboard/:path* — cet astérisque m’a évité de prendre la faute pour rien.
Ce n’est pas ma première mésaventure avec le Middleware. Bibliothèque incompatible Edge Runtime, matcher qui ne s’applique pas, boucle de redirection infinie… j’ai presque tout vu.
Si vous utilisez ou envisagez Next.js Middleware pour l’authentification, l’i18n ou les tests A/B, cet article devrait vous aider. Je détaille les pièges, les règles de configuration et trois cas complets.
Pas de formules creuses du type « à l’ère du développement web moderne ». Des problèmes concrets, comment écrire, comment éviter les pièges, et faire en sorte que le Middleware serve vraiment.
Qu’est-ce que le Middleware, et pourquoi l’utiliser ?
En bref, le Middleware est un poste de contrôle.
Avant qu’une requête n’atteigne votre page ou API, elle passe par ce filtre. Vous pouvez vérifier l’identité, modifier la requête, ou renvoyer une réponse directement — rediriger un visiteur non connecté, ou envoyer vers une version linguistique selon la région.
Il s’exécute sur Edge Runtime, point crucial. Edge Runtime ne tourne pas sur votre serveur, mais sur des nœuds edge proches de l’utilisateur (CDN). Faible latence, cold start quasi nul. Le Middleware est la couche de code la plus proche du visiteur.
Edge Runtime vs Node.js Runtime
| Caractéristique | Edge Runtime | Node.js Runtime |
|---|---|---|
| Démarrage | Cold start quasi nul | Quelques centaines de ms |
| Emplacement | Nœuds edge mondiaux | Serveur dédié |
| API | API Web standard | API Node.js complètes |
| Usage | Logique légère, réponse rapide | Calcul complexe, accès BDD |
En résumé : rapide, mais limité. Pas de modules Node.js (fs, path, etc.), ni la plupart des connexions base de données. C’est là que les pièges apparaissent le plus souvent.
Quand utiliser le Middleware ?
Toute logique ne convient pas au Middleware. Scénarios les plus courants :
1. Authentification (Auth Gate)
Le cas classique : vérifier si l’utilisateur est connecté, sinon rediriger vers /login. Plus rapide qu’une vérification dans un Server Component, car la requête n’atteint pas encore le serveur d’origine.
2. Internationalisation (i18n)
Selon la langue (URL, cookie ou navigateur), rediriger vers la bonne version : / → /zh ou /en.
3. Tests A/B
Répartir aléatoirement les utilisateurs entre deux versions de page. Un cookie maintient le groupe pour éviter un changement à chaque refresh.
4. Détection de bots et rate limiting
Bloquer les crawlers ou limiter la fréquence d’accès par IP.
5. Logs et analytics
Enregistrer chemin, referrer, user-agent et envoyer vers un service d’analyse.
6. Rewrite de contenu
Mapper en interne une URL vers un autre chemin sans changer la barre d’adresse. Utile pour routes dynamiques ou tests A/B.
Pourquoi ne pas faire tout ça dans les composants de page ?
C’est possible, mais plus lent. La logique des Server ou Client Components s’exécute après l’arrivée de la requête, voire après le rendu. Le Middleware intercepte à la edge : réponse plus rapide, meilleure UX.
Autre avantage : centralisation. Pas besoin de répéter l’authentification sur chaque page protégée — un seul endroit suffit.
Ne mettez pas pour autant toute la logique métier dans le Middleware. Requêtes BDD, calculs lourds : route API ou Server Component. Le Middleware doit rester léger et rapide.
Configuration de base et structure de fichiers
Où placer le fichier ?
Next.js impose une règle stricte : à la racine du projet ou dans src, nommé middleware.ts (ou .js).
racine-du-projet/
├── app/
├── middleware.ts ← ici
├── package.json
Avec un dossier src :
racine-du-projet/
├── src/
│ ├── app/
│ ├── middleware.ts ← ici
├── package.json
Attention : un seul fichier middleware.ts par projet. Pas de multiples middlewares dans app/ ou ailleurs — le Middleware est un gardien global.
Le Middleware minimal
import { NextRequest, NextResponse } from 'next/server';
export function middleware(request: NextRequest) {
console.log('Requête entrante :', request.url);
return NextResponse.next(); // laisser passer
}
NextResponse.next() signifie « OK, continuer » — la requête atteint la page ou l’API cible.
API principales : NextRequest et NextResponse
NextRequest étend la Request Web standard :
request.nextUrl: URL parsée (pathname, search, etc.)request.cookies: lecture/écriture des cookiesrequest.geo: géolocalisation (selon la plateforme, ex. Vercel)
NextResponse offre plusieurs modes de retour :
1. Laisser passer
return NextResponse.next();
2. Redirection (URL visible change)
return NextResponse.redirect(new URL('/login', request.url));
3. Rewrite (redirection interne, URL inchangée)
return NextResponse.rewrite(new URL('/dashboard/v2', request.url));
L’utilisateur voit /dashboard mais reçoit le contenu de /dashboard/v2. Pratique pour tests A/B ou bascule de version.
4. Réponse directe
return new NextResponse('Accès refusé', { status: 403 });
Exemple : ajouter un header personnalisé
import { NextRequest, NextResponse } from 'next/server';
export function middleware(request: NextRequest) {
const response = NextResponse.next();
response.headers.set('x-custom-header', 'my-value');
return response;
}
Utile pour un timestamp ou marquer la source de la requête sur toutes les réponses.
Note de version (important)
Avec Next.js 15, middleware.ts a été renommé en proxy.ts. middleware.ts reste compatible ; les nouveaux projets peuvent adopter le nouveau nom. Ce guide couvre Next.js 14/15 — concepts et API identiques.
Correspondance de chemins (Matcher) : la zone la plus piégeuse
Le matcher, c’est là que j’ai le plus trébuché.
Pourquoi un matcher ?
Sans matcher, le Middleware s’exécute sur chaque requête — CSS, JS, images, polices. Une page peut déclencher 20 exécutions pour 20 assets statiques : gaspillage et ralentissement.
Le matcher indique à Next.js : « n’exécuter le Middleware que sur ces chemins ».
Syntaxe de base
Exporter un objet config dans middleware.ts :
export const config = {
matcher: ['/dashboard/:path*', '/api/:path*']
}
Seuls les chemins commençant par /dashboard et /api déclenchent le Middleware.
Modificateurs : *, +, ?
* (zéro ou plus)
/dashboard/:path* correspond à :
/dashboard✓/dashboard/settings✓/dashboard/settings/profile✓
+ (un ou plus)
/dashboard/:path+ :
/dashboard✗/dashboard/settings✓/dashboard/settings/profile✓
? (zéro ou un)
/dashboard/:path? :
/dashboard✓/dashboard/settings✓/dashboard/settings/profile✗
Dans la plupart des cas, * suffit.
Pièges courants et solutions (essentiel)
| Problème | Mauvaise écriture | Bonne écriture | Raison |
|---|---|---|---|
| Multi-niveaux non interceptés | /dashboard/:path | /dashboard/:path* | Sans *, un seul niveau |
| Racine oubliée | matcher: ['/dashboard/:path*'] | matcher: ['/', '/dashboard/:path*'] | / n’est pas inclus automatiquement |
| Assets statiques interceptés | matcher: ['/:path*'] | matcher: ['/((?!_next|favicon.ico).*)'] | Exclure _next etc. par regex |
| Routes API non protégées | matcher: ['/api/users'] | matcher: ['/api/:path*'] | Un chemin précis = une seule route |
Piège classique : les assets statiques
export const config = {
matcher: ['/:path*'] // vouloir tout matcher
}
Résultat : _next/static est aussi intercepté, le Middleware s’exécute en boucle, la page devient très lente.
Solution : lookahead négatif
export const config = {
matcher: [
'/((?!api|_next/static|_next/image|favicon.ico).*)',
],
}
« Matcher tout sauf api, _next/static, _next/image et favicon.ico. »
La regex est tordue — reprenez l’exemple officiel tel quel.
Pas de valeurs dynamiques dans matcher
const lang = 'zh';
export const config = {
matcher: [`/${lang}/:path*`] // ❌ interdit
}
Le matcher doit être statique, connu à la compilation. Pas de template avec variables, pas de génération à l’exécution.
Pour une logique dynamique, placez-la dans la fonction middleware :
export function middleware(request: NextRequest) {
const { pathname } = request.nextUrl;
if (pathname.startsWith('/zh') || pathname.startsWith('/en')) {
// traitement
}
return NextResponse.next();
}
export const config = {
matcher: ['/:locale/:path*']
}
Modèles matcher recommandés
Protéger des routes spécifiques (back-office) :
export const config = {
matcher: ['/dashboard/:path*', '/admin/:path*']
}
Tout matcher sauf les assets statiques :
export const config = {
matcher: [
'/((?!_next/static|_next/image|favicon.ico|.*\\.png$).*)',
],
}
Protéger toutes les routes API :
export const config = {
matcher: ['/api/:path*']
}
La doc Next.js sur le matcher reste succincte — ce tableau résume des heures de galère. Espérons qu’il vous fera gagner du temps.
Limites Edge Runtime et contournements
La première fois, j’étais perdu. Je voulais valider un utilisateur en interrogeant la base depuis le Middleware. En local : Native Node.js APIs are not supported in the Edge Runtime.
Connecter une BDD, c’est interdit ?
Edge Runtime n’est pas un environnement Node.js complet — beaucoup d’API et de bibliothèques courantes sont indisponibles.
Ce qu’Edge Runtime ne supporte pas
| Catégorie | API/modules non supportés | Impact |
|---|---|---|
| Système de fichiers | fs, path | Pas de lecture/écriture locale |
| Sous-processus | child_process | Pas de commandes externes |
| Chiffrement | partie de crypto | Utiliser Web Crypto API |
| Base de données | drivers MongoDB, MySQL natifs | Drivers traditionnels indisponibles |
| Autres | process.emit, setImmediate | API Node.js bas niveau |
Impact concret
- Pas de connexion BDD directe pour l’authentification
- Pas de lecture de fichier config (ex.
config.json) - Pas de bibliothèques tierces dépendant de Node.js
Des contournements existent pourtant.
Stratégie : Edge Runtime comme avant-poste
Le Middleware ne fait qu’un jugement léger ; la logique lourde passe à l’étape suivante.
| Besoin | ❌ Limite Edge | ✅ Solution |
|---|---|---|
| Authentification | Pas de requête BDD | JWT local ou appel route API |
| Chiffrement | crypto partiel | Web Crypto API |
| Configuration | Pas de FS | process.env ou API |
| Logs | Pas d’écriture locale | Service tiers (Logtail, etc.) |
| Base de données | drivers classiques | BDD Edge (Vercel Postgres, Supabase) |
Exemple : validation JWT (recommandé)
Le JWT est sans état — le token contient les infos, pas besoin de BDD.
import { NextRequest, NextResponse } from 'next/server';
import { jwtVerify } from 'jose'; // compatible Edge Runtime
export async function middleware(request: NextRequest) {
const token = request.cookies.get('auth-token')?.value;
if (!token) {
return NextResponse.redirect(new URL('/login', request.url));
}
try {
const secret = new TextEncoder().encode(process.env.JWT_SECRET);
const { payload } = await jwtVerify(token, secret);
const response = NextResponse.next();
response.headers.set('x-user-id', payload.userId as string);
return response;
} catch (error) {
const response = NextResponse.redirect(new URL('/login', request.url));
response.cookies.delete('auth-token');
return response;
}
}
export const config = {
matcher: ['/dashboard/:path*']
}
Points clés :
joseplutôt quejsonwebtoken(dépend decryptoNode.js)- Secret JWT via
process.env(disponible sur Edge) - En cas d’échec, supprimer le cookie
Si la BDD est obligatoire
Ex. vérifier si un compte est suspendu — appeler une route API depuis le Middleware :
export async function middleware(request: NextRequest) {
const userId = request.cookies.get('user-id')?.value;
if (!userId) {
return NextResponse.redirect(new URL('/login', request.url));
}
const apiUrl = new URL('/api/check-user-status', request.url);
const response = await fetch(apiUrl, {
headers: { 'x-user-id': userId }
});
const { isActive } = await response.json();
if (!isActive) {
return NextResponse.redirect(new URL('/account-suspended', request.url));
}
return NextResponse.next();
}
La route API tourne en Node.js et peut accéder à la BDD — au prix d’une latence supplémentaire.
Web Crypto API
Pour chiffrement/hachage, API native du navigateur :
const data = new TextEncoder().encode('hello world');
const hashBuffer = await crypto.subtle.digest('SHA-256', data);
const hashArray = Array.from(new Uint8Array(hashBuffer));
const hashHex = hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
Plus verbeux que crypto Node.js, mais seule option sur Edge.
Comment savoir si une bibliothèque supporte Edge ?
Lire la doc ou tester. Message Native Node.js APIs are not supported = incompatible.
Alternatives courantes :
- JWT :
joseau lieu dejsonwebtoken - BDD : Vercel Postgres, Supabase, Prisma (partiel)
- Logs : Logtail, Axiom
Conseil : ne pas surcharger le Middleware. Rapide entrée/sortie ; le lourd reste dans les routes API.
Cas pratiques : trois scénarios complets
Assez de théorie — voici trois cas réels, prêts à copier.
Cas 1 : Authentification et protection de routes
Scénario : back-office où tout /dashboard/* exige une connexion.
// middleware.ts
import { NextRequest, NextResponse } from 'next/server';
import { jwtVerify } from 'jose';
export async function middleware(request: NextRequest) {
const { pathname } = request.nextUrl;
const token = request.cookies.get('auth-token')?.value;
if (!token) {
const loginUrl = new URL('/login', request.url);
loginUrl.searchParams.set('from', pathname);
return NextResponse.redirect(loginUrl);
}
try {
const secret = new TextEncoder().encode(process.env.JWT_SECRET);
const { payload } = await jwtVerify(token, secret);
const expiresAt = payload.exp as number;
const now = Math.floor(Date.now() / 1000);
const shouldRefresh = expiresAt - now < 3600;
const response = NextResponse.next();
if (shouldRefresh) {
response.headers.set('x-token-refresh-needed', 'true');
}
response.headers.set('x-user-id', payload.userId as string);
response.headers.set('x-user-role', payload.role as string);
return response;
} catch (error) {
const loginUrl = new URL('/login', request.url);
loginUrl.searchParams.set('from', pathname);
loginUrl.searchParams.set('reason', 'expired');
const response = NextResponse.redirect(loginUrl);
response.cookies.delete('auth-token');
return response;
}
}
export const config = {
matcher: ['/dashboard/:path*']
}
Points clés :
- Paramètre
frompour revenir après connexion - Renouvellement anticipé si expiration proche
- Infos utilisateur dans les headers (optionnel)
Tests :
- Sans cookie,
/dashboard→/login?from=/dashboard - Avec cookie valide → affichage normal
Problème fréquent : OK en local, KO en prod — JWT_SECRET manquant sur Vercel/Netlify.
Cas 2 : Redirection i18n
Scénario : site bilingue ; / redirige vers /zh ou /en selon la préférence.
// middleware.ts
import { NextRequest, NextResponse } from 'next/server';
const supportedLocales = ['en', 'zh', 'ja'];
const defaultLocale = 'en';
function getPreferredLocale(request: NextRequest): string {
const urlLocale = request.nextUrl.searchParams.get('lang');
if (urlLocale && supportedLocales.includes(urlLocale)) {
return urlLocale;
}
const cookieLocale = request.cookies.get('NEXT_LOCALE')?.value;
if (cookieLocale && supportedLocales.includes(cookieLocale)) {
return cookieLocale;
}
const acceptLanguage = request.headers.get('accept-language');
if (acceptLanguage) {
const browserLang = acceptLanguage.split(',')[0].split('-')[0];
if (supportedLocales.includes(browserLang)) {
return browserLang;
}
}
return defaultLocale;
}
export function middleware(request: NextRequest) {
const { pathname } = request.nextUrl;
const pathnameHasLocale = supportedLocales.some(
locale => pathname.startsWith(`/${locale}/`) || pathname === `/${locale}`
);
if (!pathnameHasLocale) {
const locale = getPreferredLocale(request);
const newUrl = new URL(`/${locale}${pathname}`, request.url);
newUrl.search = request.nextUrl.search;
const response = NextResponse.redirect(newUrl);
response.cookies.set('NEXT_LOCALE', locale, {
maxAge: 60 * 60 * 24 * 30,
path: '/'
});
return response;
}
return NextResponse.next();
}
export const config = {
matcher: [
'/((?!api|_next/static|_next/image|favicon.ico|.*\\.).*)'
]
}
Points clés :
- Priorité : param URL > cookie > navigateur
- Cookie pour mémoriser le choix
- Matcher exclut les assets statiques
Avec next-intl :
import { createI18nMiddleware } from 'next-intl/middleware';
export default createI18nMiddleware({
locales: ['en', 'zh', 'ja'],
defaultLocale: 'en'
});
export const config = {
matcher: ['/((?!api|_next|.*\\.).)']
};
next-intl gère détection, cookies, etc.
Cas 3 : Tests A/B et feature flags
Scénario : nouvelle page d’accueil pour 50 % des visiteurs.
// middleware.ts
import { NextRequest, NextResponse } from 'next/server';
export function middleware(request: NextRequest) {
const { pathname } = request.nextUrl;
if (pathname !== '/') {
return NextResponse.next();
}
let variant = request.cookies.get('ab-test-homepage')?.value;
if (!variant) {
variant = Math.random() < 0.5 ? 'A' : 'B';
}
let response: NextResponse;
if (variant === 'B') {
response = NextResponse.rewrite(new URL('/homepage-v2', request.url));
} else {
response = NextResponse.next();
}
response.cookies.set('ab-test-homepage', variant, {
maxAge: 60 * 60 * 24 * 7,
path: '/'
});
response.headers.set('x-ab-variant', variant);
return response;
}
export const config = {
matcher: ['/']
}
Points clés :
rewriteplutôt queredirect— URL reste/- Cookie pour un groupe stable
- Header
x-ab-variantpour l’analytics
Tracking dans la page :
// app/page.tsx
import { headers } from 'next/headers';
export default function HomePage() {
const headersList = headers();
const abVariant = headersList.get('x-ab-variant');
useEffect(() => {
analytics.track('page_view', {
page: 'homepage',
variant: abVariant
});
}, [abVariant]);
return <div>...</div>;
}
Groupement par user ID (stable multi-appareils) :
const userId = request.cookies.get('user-id')?.value;
if (userId) {
const hash = simpleHash(userId);
variant = hash % 2 === 0 ? 'A' : 'B';
} else {
variant = request.cookies.get('ab-test-homepage')?.value ||
(Math.random() < 0.5 ? 'A' : 'B');
}
function simpleHash(str: string): number {
let hash = 0;
for (let i = 0; i < str.length; i++) {
hash = ((hash << 5) - hash) + str.charCodeAt(i);
hash |= 0;
}
return Math.abs(hash);
}
Ces trois cas couvrent l’essentiel. Combinez-les : auth + i18n sur le même Middleware.
Optimisation des performances et bonnes pratiques
Écrire un Middleware est facile ; le bien écrire demande de l’expérience.
Organisation modulaire
Un seul middleware.ts autorisé, mais la logique peut être découpée :
racine-du-projet/
├── middleware/
│ ├── auth.ts
│ ├── i18n.ts
│ ├── ab-test.ts
│ └── rate-limit.ts
├── middleware.ts
Point d’entrée :
// middleware.ts
import { NextRequest, NextResponse } from 'next/server';
import { checkAuth } from './middleware/auth';
import { handleI18n } from './middleware/i18n';
import { handleABTest } from './middleware/ab-test';
export async function middleware(request: NextRequest) {
const { pathname } = request.nextUrl;
const i18nResponse = handleI18n(request);
if (i18nResponse) return i18nResponse;
if (pathname.startsWith('/dashboard')) {
const authResponse = await checkAuth(request);
if (authResponse) return authResponse;
}
if (pathname === '/') {
return handleABTest(request);
}
return NextResponse.next();
}
export const config = {
matcher: ['/((?!api|_next/static|_next/image|favicon.ico).*)']
}
auth.ts :
// middleware/auth.ts
import { NextRequest, NextResponse } from 'next/server';
import { jwtVerify } from 'jose';
export async function checkAuth(request: NextRequest): Promise<NextResponse | null> {
const token = request.cookies.get('auth-token')?.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 null;
} catch {
return NextResponse.redirect(new URL('/login', request.url));
}
}
Cache : réduire les calculs répétés
Vercel Edge Config pour des feature flags :
import { get } from '@vercel/edge-config';
export async function middleware(request: NextRequest) {
const featureFlags = await get('feature-flags');
if (featureFlags?.newDashboard) {
return NextResponse.rewrite(new URL('/dashboard-v2', request.url));
}
return NextResponse.next();
}
Edge Config : stockage clé-valeur distribué, lecture très rapide.
Éviter des headers trop volumineux
Headers trop grands → erreur 431 Request Header Fields Too Large.
Recommandations :
- Total headers < 8 Ko
- Infos essentielles uniquement
- Données volumineuses : token chiffré
// ❌ éviter
response.headers.set('x-user-data', JSON.stringify(userData));
// ✅ préférer
response.headers.set('x-user-id', user.id);
response.headers.set('x-user-role', user.role);
Matcher : précision > wildcard
// moins bon
export const config = {
matcher: ['/:path*']
}
// mieux
export const config = {
matcher: ['/dashboard/:path*', '/api/:path*']
}
Monitoring et débogage
En développement :
export function middleware(request: NextRequest) {
if (process.env.NODE_ENV === 'development') {
console.log('Middleware :', {
path: request.nextUrl.pathname,
method: request.method,
cookies: request.cookies.getAll()
});
}
// ...
}
En production : logs vers un service tiers
import { Logger } from '@logtail/edge';
const logger = new Logger(process.env.LOGTAIL_TOKEN);
export async function middleware(request: NextRequest) {
try {
// ...
} catch (error) {
await logger.error('Middleware error', {
path: request.nextUrl.pathname,
error: error.message
});
throw error;
}
}
Checklist
✅ À faire :
- Middleware léger ; logique lourde en route API
- Matcher précis
- JWT pour l’auth
- Code modulaire par fonction
- Logs de debug en dev
- Cookies avec expiration raisonnable
❌ À éviter :
- Calculs lourds ou requêtes BDD dans le Middleware
- Oublier le matcher
- Bibliothèques dépendant de Node.js
- Headers > 8 Ko
- Logs massifs en production
- Boucles de redirection infinies
Erreurs courantes et débogage
Erreur 1 : le Middleware ne s’exécute pas
| Cause | Vérification | Solution |
|---|---|---|
| Mauvais emplacement | Fichier à la racine ou dans src | Déplacer |
| Matcher ne couvre pas le chemin | Logger pathname | Ajuster matcher |
| Erreur de syntaxe | Console de compilation | Corriger |
| Export incorrect | export function middleware | Vérifier export |
| Cache | Supprimer .next | rm -rf .next && npm run dev |
export function middleware(request: NextRequest) {
console.log('🔥 Middleware exécuté ! Chemin :', request.nextUrl.pathname);
// ...
}
Si ce log n’apparaît pas, le Middleware ne tourne pas.
Erreur 2 : Native Node.js APIs are not supported in the Edge Runtime
Coupables fréquents : fs, path, jsonwebtoken, drivers MongoDB/MySQL.
- Trouver une alternative Edge
- Déplacer vers une route API
- Web Crypto API à la place de
cryptoNode.js
// ❌
import jwt from 'jsonwebtoken';
// ✅
import { jwtVerify } from 'jose';
Erreur 3 : Invalid middleware found
Matcher vide :
export const config = { matcher: [] } // ❌
Export manquant :
export function middleware(request: NextRequest) { ... } // ✅
Pas de return :
export function middleware(request: NextRequest) {
return NextResponse.next(); // obligatoire
}
Erreur 4 : boucle de redirection infinie
ERR_TOO_MANY_REDIRECTS — la cible de redirection est aussi interceptée.
export function middleware(request: NextRequest) {
const { pathname } = request.nextUrl;
if (pathname === '/login' || pathname === '/') {
return NextResponse.next();
}
const token = request.cookies.get('auth-token');
if (!token) {
return NextResponse.redirect(new URL('/login', request.url));
}
return NextResponse.next();
}
Ou matcher ciblé : matcher: ['/dashboard/:path*']
Erreur 5 : variables d’environnement undefined
.env.localabsent ou nom incorrect- Variables non configurées sur Vercel/Netlify
- Nom non conforme aux règles Next.js
// .env.local
JWT_SECRET=your-secret-here
Redémarrer le serveur dev après modification ; côté client, préfixe NEXT_PUBLIC_.
Astuces de débogage
Headers de traçage :
export function middleware(request: NextRequest) {
const response = NextResponse.next();
response.headers.set('x-middleware-executed', 'true');
response.headers.set('x-middleware-path', request.nextUrl.pathname);
return response;
}
Commenter par étapes pour localiser l’erreur.
Logs Vercel Edge Functions dans l’onglet Functions du projet.
Turbo en local (Next.js 15+) :
npm run dev -- --turbo
Conclusion
Trois idées à retenir :
1. Connaître les limites du Middleware
Auth légère, redirections, tests A/B : oui. Logique métier lourde, BDD, calculs : route API ou Server Component. Le Middleware est un gardien rapide, pas un majordome.
2. Le matcher est primordial
Routes dynamiques : *. Assets statiques : exclus. Pages publiques (/login) : non interceptées. En doute, reprenez les modèles ci-dessus ou loggez les chemins en dev.
3. Accepter les limites Edge Runtime
Ce n’est pas Node.js complet — vitesse et distribution mondiale en échange. JWT, Web Crypto API, appels API : les bons outils pour contourner.
Next.js Middleware n’est pas complexe, mais riche en détails. Matcher, Edge Runtime, redirections infinies — je les ai vus. Cet article vise à vous faire gagner du temps.
Pour une interception globale, testez le Middleware, puis optimisez. En cas de blocage, revenez à la section débogage.
Si cet article vous a aidé, partagez-le avec vos collègues Next.js. Prochain sujet de ma part : Server Actions — là aussi, les pièges ne manquent pas.
Bon courage — que votre Middleware fonctionne du premier coup.
Configuration complète du Middleware Next.js
De la création du fichier Middleware aux trois scénarios : protection de routes, i18n et tests A/B
⏱️ Estimated time: 3 hr
- 1
Step 1: Créer le fichier Middleware
Créer le fichier :
• Emplacement : middleware.ts (racine du projet)
• Exporter l'objet config pour le matcher
• Exporter la fonction middleware pour traiter les requêtes
Structure de base :
export const config = {
matcher: '/dashboard/:path*'
}
export function middleware(request: NextRequest) {
// logique de traitement
} - 2
Step 2: Configurer les règles matcher
Règles de correspondance :
• Chemin unique : '/dashboard'
• Route dynamique : '/dashboard/:path*' (l'astérisque couvre plusieurs niveaux)
• Plusieurs chemins : ['/dashboard/:path*', '/admin/:path*']
• Exclure des chemins : lookahead négatif, ex. '/((?!api|_next/static|_next/image|favicon.ico).*)'
Points d'attention :
• Les routes dynamiques nécessitent * pour plusieurs niveaux
• Exclure les assets statiques (_next/static, _next/image, etc.)
• Ne pas intercepter les pages publiques (page de connexion) - 3
Step 3: Implémenter la protection de routes (authentification)
Étapes :
1. Lire le token depuis le cookie
2. Valider le token (JWT ou appel API)
3. Rediriger les utilisateurs non connectés vers la page de connexion
4. Laisser passer les utilisateurs authentifiés
Points clés :
• NextRequest.cookies pour lire les cookies
• NextResponse.redirect pour rediriger
• NextResponse.next pour continuer la requête
• Éviter les boucles de redirection infinies - 4
Step 4: Implémenter l'internationalisation (changement de langue)
Étapes :
1. Détecter la langue préférée (cookie, header, valeur par défaut)
2. Déterminer si une redirection est nécessaire selon le chemin
3. Ajouter le préfixe de langue à l'URL
4. Définir le cookie de langue
Points clés :
• request.headers.get('accept-language')
• request.nextUrl.pathname pour le chemin
• NextResponse.rewrite pour réécrire l'URL
• Permettre le changement de langue sans modifier la structure d'URL - 5
Step 5: Gérer les limites Edge Runtime
Limites et solutions :
• Pas d'API Node.js → utiliser les API Web standard
• Pas de système de fichiers → variables d'environnement ou appels API
• Certains packages npm incompatibles → vérifier la compatibilité Edge
• Validation JWT → Web Crypto API ou appel à une route API
Astuces de débogage :
• console.log pour le débogage
• Consulter les logs Vercel Edge Functions
• try-catch pour capturer les erreurs - 6
Step 6: Tester et déboguer
Points de test :
• Tester tous les chemins correspondants
• Tester les chemins non correspondants (ne pas intercepter)
• Tester la logique de redirection
• Tester la compatibilité Edge Runtime
Méthodes de débogage :
• Ajouter console.log dans le middleware
• Vérifier l'onglet Network du navigateur
• Consulter les logs Vercel Edge Functions
• Mode dev Next.js pour les avertissements
FAQ
Que faire si la configuration matcher du Middleware ne fonctionne pas ?
1) Le chemin matcher est-il correct (routes dynamiques : ajouter *)
2) Les assets statiques sont-ils exclus
3) Le format du chemin est-il correct (pas de regex, utiliser le format Next.js)
Ajouter console.log dans le middleware pour voir quels chemins sont interceptés.
Pourquoi les chemins multi-niveaux ne correspondent-ils pas ?
Que faire si Edge Runtime ne supporte pas une bibliothèque ?
Solutions :
1) Vérifier si le package supporte Edge Runtime
2) Utiliser les API Web standard
3) Déplacer la logique complexe vers une route API ou un Server Component
4) Utiliser une bibliothèque alternative compatible Edge
Comment éviter une boucle de redirection infinie ?
Peut-on accéder à une base de données dans le Middleware ?
Pour une requête BDD :
1) Appeler une route API depuis le Middleware
2) Utiliser des variables d'environnement pour la config
3) Déplacer la logique complexe vers une route API ou un Server Component
Comment déboguer le Middleware ?
1) Ajouter console.log dans la fonction middleware
2) Vérifier l'onglet Network du navigateur (requêtes et réponses)
3) Consulter les logs Vercel Edge Functions
4) Mode dev Next.js pour avertissements et erreurs
Quelle différence entre Middleware et route API ?
• S'exécute sur Edge Runtime
• Avant que la requête n'atteigne la page ou la route API
• Idéal pour interception et transfert légers
Route API :
• S'exécute sur le runtime Node.js
• Accès complet aux API Node.js et à la base de données
• Adaptée à la logique métier complexe
16 min de lecture · Publié le: 25 déc. 2025 · Mis à jour le: 27 juil. 2026
Guide complet Next.js
Si vous arrivez depuis la recherche, le plus rapide est de passer à l’article précédent ou suivant de cette série.
Précédent
Tutoriel Next.js Server Actions : bonnes pratiques pour formulaires et validation
Cas pratiques pour maîtriser Next.js Server Actions : traitement de formulaires, validation Zod, sécurité et optimisation UX pour simplifier votre flux de développement
Partie 10 sur 51
Suivant
Protection des routes Next.js et contrôle d'accès : guide Middleware et défense en profondeur
Analyse approfondie de la protection des routes Next.js : du Middleware à l'architecture multi-couches, avec NextAuth et getServerSession pour un RBAC sécurisé et des exemples de code complets.
Partie 12 sur 51



Commentaires
Connectez-vous avec GitHub pour laisser un commentaire