Découvrez le middleware Next.js, une fonctionnalité puissante pour intercepter et modifier les requêtes entrantes. Apprenez comment implémenter l'authentification, l'autorisation, la redirection et les tests A/B avec des exemples pratiques.
Middleware Next.js : Maîtriser l'interception de requêtes pour des applications dynamiques
Le middleware Next.js offre un moyen flexible et puissant d'intercepter et de modifier les requêtes entrantes avant qu'elles n'atteignent vos routes. Cette capacité vous permet de mettre en œuvre un large éventail de fonctionnalités, de l'authentification et l'autorisation à la redirection et aux tests A/B, tout en optimisant les performances. Ce guide complet vous présentera les concepts fondamentaux du middleware Next.js et vous montrera comment l'exploiter efficacement.
Qu'est-ce que le middleware Next.js ?
Dans Next.js, un middleware est une fonction qui s'exécute avant qu'une requête ne soit complétée. Il vous permet de :
- Intercepter les requêtes : Examiner les en-têtes, les cookies et l'URL de la requête entrante.
- Modifier les requêtes : Réécrire les URL, définir des en-têtes ou rediriger les utilisateurs en fonction de critères spécifiques.
- Exécuter du code : Lancer une logique côté serveur avant le rendu d'une page.
Les fonctions de middleware sont définies dans le fichier middleware.ts
(ou middleware.js
) à la racine de votre projet. Elles sont exécutées pour chaque route de votre application, ou pour des routes spécifiques basées sur des matchers configurables.
Concepts clés et avantages
Objet Request
L'objet request
donne accès aux informations sur la requête entrante, notamment :
request.url
: L'URL complète de la requête.request.method
: La méthode HTTP (par ex., GET, POST).request.headers
: Un objet contenant les en-têtes de la requête.request.cookies
: Un objet représentant les cookies de la requête.request.geo
: Fournit les données de géolocalisation associées à la requête, si disponibles.
Objet Response
Les fonctions de middleware retournent un objet Response
pour contrôler le résultat de la requête. Vous pouvez utiliser les réponses suivantes :
NextResponse.next()
: Continue le traitement normal de la requête, lui permettant d'atteindre la route prévue.NextResponse.redirect(url)
: Redirige l'utilisateur vers une URL différente.NextResponse.rewrite(url)
: Réécrit l'URL de la requête, servant ainsi une page différente sans redirection. L'URL reste la même dans le navigateur.- Retourner un objet
Response
personnalisé : Permet de servir un contenu personnalisé, comme une page d'erreur ou une réponse JSON spécifique.
Matchers
Les matchers vous permettent de spécifier à quelles routes votre middleware doit s'appliquer. Vous pouvez définir des matchers en utilisant des expressions régulières ou des modèles de chemin. Cela garantit que votre middleware ne s'exécute que lorsque c'est nécessaire, améliorant ainsi les performances et réduisant la surcharge.
Edge Runtime
Le middleware Next.js s'exécute sur l'Edge Runtime, un environnement d'exécution JavaScript léger qui peut être déployé à proximité de vos utilisateurs. Cette proximité minimise la latence et améliore les performances globales de votre application, en particulier pour les utilisateurs répartis dans le monde entier. L'Edge Runtime est disponible sur le Edge Network de Vercel et d'autres plateformes compatibles. L'Edge Runtime a certaines limitations, notamment l'utilisation des API Node.js.
Exemples pratiques : Implémentation de fonctionnalités de middleware
1. Authentification
Un middleware d'authentification peut être utilisé pour protéger les routes qui nécessitent que les utilisateurs soient connectés. Voici un exemple de mise en œuvre de l'authentification à l'aide de cookies :
// middleware.ts
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
export function middleware(request: NextRequest) {
const token = request.cookies.get('auth_token');
if (!token) {
return NextResponse.redirect(new URL('/login', request.url))
}
return NextResponse.next()
}
export const config = {
matcher: ['/dashboard/:path*'],
}
Ce middleware vérifie la présence d'un cookie auth_token
. Si le cookie n'est pas trouvé, l'utilisateur est redirigé vers la page /login
. Le config.matcher
spécifie que ce middleware ne doit s'exécuter que pour les routes sous /dashboard
.
Perspective globale : Adaptez la logique d'authentification pour prendre en charge diverses méthodes d'authentification (par ex., OAuth, JWT) et intégrez-la avec différents fournisseurs d'identité (par ex., Google, Facebook, Azure AD) pour répondre aux besoins des utilisateurs de diverses régions.
2. Autorisation
Un middleware d'autorisation peut être utilisé pour contrôler l'accès aux ressources en fonction des rôles ou des permissions des utilisateurs. Par exemple, vous pourriez avoir un tableau de bord administrateur accessible uniquement à des utilisateurs spécifiques.
// middleware.ts
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
export async function middleware(request: NextRequest) {
const token = request.cookies.get('auth_token');
if (!token) {
return NextResponse.redirect(new URL('/login', request.url))
}
// Exemple : Récupérer les rôles de l'utilisateur depuis une API (remplacez par votre logique réelle)
const userResponse = await fetch('https://api.example.com/userinfo', {
headers: {
Authorization: `Bearer ${token}`,
},
});
const userData = await userResponse.json();
if (userData.role !== 'admin') {
return NextResponse.redirect(new URL('/unauthorized', request.url))
}
return NextResponse.next()
}
export const config = {
matcher: ['/admin/:path*'],
}
Ce middleware récupère le rôle de l'utilisateur et vérifie s'il a le rôle admin
. Sinon, il est redirigé vers une page /unauthorized
. Cet exemple utilise un point de terminaison d'API factice. Remplacez `https://api.example.com/userinfo` par le point de terminaison réel de votre serveur d'authentification.
Perspective globale : Soyez attentif aux réglementations sur la protection des données (par ex., RGPD, CCPA) lors du traitement des données des utilisateurs. Mettez en œuvre des mesures de sécurité appropriées pour proteger les informations sensibles et garantir la conformité avec les lois locales.
3. Redirection
Un middleware de redirection peut être utilisé pour rediriger les utilisateurs en fonction de leur emplacement, de leur langue ou d'autres critères. Par exemple, vous pourriez rediriger les utilisateurs vers une version localisée de votre site web en fonction de leur adresse IP.
// middleware.ts
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
export function middleware(request: NextRequest) {
const country = request.geo?.country || 'US'; // Par défaut, US si la géolocalisation échoue
if (country === 'DE') {
return NextResponse.redirect(new URL('/de', request.url))
}
if (country === 'FR') {
return NextResponse.redirect(new URL('/fr', request.url))
}
return NextResponse.next()
}
export const config = {
matcher: ['/'],
}
Ce middleware vérifie le pays de l'utilisateur en fonction de son adresse IP et le redirige vers la version localisée appropriée du site web (/de
pour l'Allemagne, /fr
pour la France). Si la géolocalisation échoue, il utilise par défaut la version américaine. Notez que cela dépend de la disponibilité de la propriété geo (par exemple, lors d'un déploiement sur Vercel).
Perspective globale : Assurez-vous que votre site web prend en charge plusieurs langues et devises. Donnez aux utilisateurs la possibilité de sélectionner manuellement leur langue ou leur région préférée. Utilisez des formats de date et d'heure appropriés pour chaque locale.
4. Test A/B
Le middleware peut être utilisé pour implémenter des tests A/B en assignant aléatoirement les utilisateurs à différentes variantes d'une page et en suivant leur comportement. Voici un exemple simplifié :
// middleware.ts
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
function getRandomVariant() {
return Math.random() < 0.5 ? 'A' : 'B';
}
export function middleware(request: NextRequest) {
let variant = request.cookies.get('variant')?.value;
if (!variant) {
variant = getRandomVariant();
const response = NextResponse.next();
response.cookies.set('variant', variant);
return response;
}
if (variant === 'B') {
return NextResponse.rewrite(new URL('/variant-b', request.url));
}
return NextResponse.next();
}
export const config = {
matcher: ['/'],
}
Ce middleware assigne les utilisateurs soit à la variante 'A', soit à la 'B'. Si un utilisateur n'a pas déjà de cookie variant
, un est assigné aléatoirement et défini. Les utilisateurs assignés à la variante 'B' sont réécrits vers la page /variant-b
. Vous suivriez ensuite les performances de chaque variante pour déterminer laquelle est la plus efficace.
Perspective globale : Tenez compte des différences culturelles lors de la conception des tests A/B. Ce qui fonctionne bien dans une région peut ne pas trouver d'écho auprès des utilisateurs d'une autre. Assurez-vous que votre plateforme de test A/B est conforme aux réglementations sur la vie privée dans différentes régions.
5. Feature Flags
Les feature flags vous permettent d'activer ou de désactiver des fonctionnalités dans votre application sans déployer de nouveau code. Le middleware peut être utilisé pour déterminer si un utilisateur doit avoir accès à une fonctionnalité spécifique en fonction de son ID utilisateur, de son emplacement ou d'autres critères.
// middleware.ts
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
export async function middleware(request: NextRequest) {
// Exemple : Récupérer les feature flags depuis une API
const featureFlagsResponse = await fetch('https://api.example.com/featureflags', {
headers: {
'X-User-Id': 'user123',
},
});
const featureFlags = await featureFlagsResponse.json();
if (featureFlags.new_feature_enabled) {
// Activer la nouvelle fonctionnalité
return NextResponse.next();
} else {
// Désactiver la nouvelle fonctionnalité (par ex., rediriger vers une page alternative)
return NextResponse.redirect(new URL('/alternative-page', request.url));
}
}
export const config = {
matcher: ['/new-feature'],
}
Ce middleware récupère les feature flags depuis une API et vérifie si le flag new_feature_enabled
est activé. Si c'est le cas, l'utilisateur peut accéder à la page /new-feature
. Sinon, il est redirigé vers une /alternative-page
.
Perspective globale : Utilisez les feature flags pour déployer progressivement de nouvelles fonctionnalités aux utilisateurs de différentes régions. Cela vous permet de surveiller les performances et de résoudre les problèmes avant de lancer la fonctionnalité à un public plus large. Assurez-vous également que votre système de feature flagging est scalable à l'échelle mondiale et fournit des résultats cohérents quel que soit l'emplacement de l'utilisateur. Tenez compte des contraintes réglementaires régionales pour les déploiements de fonctionnalités.
Techniques avancées
Chaînage de middlewares
Vous pouvez enchaîner plusieurs fonctions de middleware pour effectuer une série d'opérations sur une requête. Cela peut être utile pour décomposer une logique complexe en modules plus petits et plus faciles à gérer.
// middleware.ts
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
export function middleware(request: NextRequest) {
const response = NextResponse.next();
// Première fonction de middleware
const token = request.cookies.get('auth_token');
if (!token) {
return NextResponse.redirect(new URL('/login', request.url))
}
// Deuxième fonction de middleware
response.headers.set('x-middleware-custom', 'value');
return response;
}
export const config = {
matcher: ['/dashboard/:path*'],
}
Cet exemple montre deux middlewares en un. Le premier effectue l'authentification et le second définit un en-tête personnalisé.
Utilisation des variables d'environnement
Stockez les informations sensibles, telles que les clés d'API et les identifiants de base de données, dans des variables d'environnement plutôt que de les coder en dur dans vos fonctions de middleware. Cela améliore la sécurité et facilite la gestion de la configuration de votre application.
// middleware.ts
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
const API_KEY = process.env.API_KEY;
export async function middleware(request: NextRequest) {
const response = await fetch('https://api.example.com/data', {
headers: {
'X-API-Key': API_KEY,
},
});
// ...
}
export const config = {
matcher: ['/data'],
}
Dans cet exemple, la API_KEY
est récupérée à partir d'une variable d'environnement.
Gestion des erreurs
Implémentez une gestion robuste des erreurs dans vos fonctions de middleware pour éviter que des erreurs inattendues ne fassent planter votre application. Utilisez des blocs try...catch
pour intercepter les exceptions et consigner les erreurs de manière appropriée.
// middleware.ts
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
export async function middleware(request: NextRequest) {
try {
const response = await fetch('https://api.example.com/data');
// ...
} catch (error) {
console.error('Erreur lors de la récupération des données :', error);
return NextResponse.error(); // Ou rediriger vers une page d'erreur
}
}
export const config = {
matcher: ['/data'],
}
Meilleures pratiques
- Gardez les fonctions de middleware légères : Évitez d'effectuer des opérations coûteuses en calcul dans le middleware, car cela peut affecter les performances. Déléguez les traitements complexes à des tâches d'arrière-plan ou à des services dédiés.
- Utilisez les matchers efficacement : N'appliquez le middleware qu'aux routes qui en ont besoin.
- Testez minutieusement votre middleware : Rédigez des tests unitaires pour vous assurer que vos fonctions de middleware fonctionnent correctement.
- Surveillez les performances du middleware : Utilisez des outils de surveillance pour suivre les performances de vos fonctions de middleware et identifier les goulots d'étranglement.
- Documentez votre middleware : Documentez clairement le but et la fonctionnalité de chaque fonction de middleware.
- Tenez compte des limitations de l'Edge Runtime : Soyez conscient des limitations de l'Edge Runtime, telles que l'absence d'API Node.js. Adaptez votre code en conséquence.
Dépannage des problèmes courants
- Le middleware ne s'exécute pas : Vérifiez à nouveau la configuration de votre matcher pour vous assurer que le middleware est appliqué aux bonnes routes.
- Problèmes de performance : Identifiez et optimisez les fonctions de middleware lentes. Utilisez des outils de profilage pour localiser les goulots d'étranglement.
- Compatibilité avec l'Edge Runtime : Assurez-vous que votre code est compatible avec l'Edge Runtime. Évitez d'utiliser des API Node.js qui ne sont pas prises en charge.
- Problèmes de cookies : Vérifiez que les cookies sont correctement définis et récupérés. Faites attention aux attributs des cookies tels que
domain
,path
etsecure
. - Conflits d'en-têtes : Soyez conscient des conflits d'en-têtes potentiels lors de la définition d'en-têtes personnalisés dans le middleware. Assurez-vous que vos en-têtes ne remplacent pas involontairement des en-têtes existants.
Conclusion
Le middleware Next.js est un outil puissant pour créer des applications web dynamiques et personnalisées. En maîtrisant l'interception de requêtes, vous pouvez implémenter une large gamme de fonctionnalités, de l'authentification et l'autorisation à la redirection et aux tests A/B. En suivant les meilleures pratiques décrites dans ce guide, vous pouvez exploiter le middleware Next.js pour créer des applications performantes, sécurisées et évolutives qui répondent aux besoins de votre base d'utilisateurs mondiale. Exploitez la puissance du middleware pour débloquer de nouvelles possibilités dans vos projets Next.js et offrir des expériences utilisateur exceptionnelles.