Middleware
Le Middleware vous permet d'exécuter du code avant qu'une requête ne soit complétée. Ensuite, en fonction de la requête entrante, vous pouvez modifier la réponse en la réécrivant, en redirigeant, en modifiant les en-têtes de la requête ou de la réponse, ou en répondant directement.
Le Middleware s'exécute avant que le contenu mis en cache et les routes ne soient appariés. Consultez Appariement des chemins pour plus de détails.
Cas d'utilisation
Quelques scénarios courants où le Middleware est efficace incluent :
- Redirections rapides après lecture de parties de la requête entrante
- Réécriture vers différentes pages basée sur des tests A/B ou des expériences
- Modification des en-têtes pour toutes les pages ou un sous-ensemble de pages
Le Middleware n'est pas adapté pour :
- La récupération lente de données
- La gestion de session
Convention
Utilisez le fichier middleware.ts
(ou .js
) à la racine de votre projet pour définir le Middleware. Par exemple, au même niveau que pages
ou app
, ou à l'intérieur de src
si applicable.
Remarque : Bien qu'un seul fichier
middleware.ts
soit pris en charge par projet, vous pouvez toujours organiser votre logique de middleware de manière modulaire. Séparez les fonctionnalités du middleware dans des fichiers.ts
ou.js
distincts et importez-les dans votre fichier principalmiddleware.ts
. Cela permet une gestion plus propre du middleware spécifique aux routes, agrégé dansmiddleware.ts
pour un contrôle centralisé. En imposant un seul fichier de middleware, cela simplifie la configuration, évite les conflits potentiels et optimise les performances en évitant plusieurs couches de middleware.
Exemple
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
// Cette fonction peut être marquée `async` si vous utilisez `await` à l'intérieur
export function middleware(request: NextRequest) {
return NextResponse.redirect(new URL('/home', request.url))
}
// Voir "Appariement des chemins" ci-dessous pour en savoir plus
export const config = {
matcher: '/about/:path*',
}
import { NextResponse } from 'next/server'
// Cette fonction peut être marquée `async` si vous utilisez `await` à l'intérieur
export function middleware(request) {
return NextResponse.redirect(new URL('/home', request.url))
}
// Voir "Appariement des chemins" ci-dessous pour en savoir plus
export const config = {
matcher: '/about/:path*',
}
Appariement des chemins
Le Middleware sera invoqué pour chaque route de votre projet. Compte tenu de cela, il est crucial d'utiliser des matchers pour cibler ou exclure précisément des routes spécifiques. Voici l'ordre d'exécution :
headers
depuisnext.config.js
redirects
depuisnext.config.js
- Middleware (
rewrites
,redirects
, etc.) beforeFiles
(rewrites
) depuisnext.config.js
- Routes du système de fichiers (
public/
,_next/static/
,pages/
,app/
, etc.) afterFiles
(rewrites
) depuisnext.config.js
- Routes dynamiques (
/blog/[slug]
) fallback
(rewrites
) depuisnext.config.js
Il existe deux façons de définir sur quels chemins le Middleware s'exécutera :
Matcher
matcher
vous permet de filtrer le Middleware pour qu'il s'exécute sur des chemins spécifiques.
export const config = {
matcher: '/about/:path*',
}
Vous pouvez apparier un seul chemin ou plusieurs chemins avec une syntaxe de tableau :
export const config = {
matcher: ['/about/:path*', '/dashboard/:path*'],
}
La configuration matcher
permet des regex complets, donc des correspondances comme les lookaheads négatifs ou les correspondances de caractères sont prises en charge. Un exemple de lookahead négatif pour apparier tous les chemins sauf certains spécifiques peut être vu ici :
export const config = {
matcher: [
/*
* Apparier tous les chemins de requête sauf ceux commençant par :
* - api (routes API)
* - _next/static (fichiers statiques)
* - _next/image (fichiers d'optimisation d'image)
* - favicon.ico, sitemap.xml, robots.txt (fichiers de métadonnées)
*/
'/((?!api|_next/static|_next/image|favicon.ico|sitemap.xml|robots.txt).*)',
],
}
Vous pouvez également contourner le Middleware pour certaines requêtes en utilisant les tableaux missing
ou has
, ou une combinaison des deux :
export const config = {
matcher: [
/*
* Apparier tous les chemins de requête sauf ceux commençant par :
* - api (routes API)
* - _next/static (fichiers statiques)
* - _next/image (fichiers d'optimisation d'image)
* - favicon.ico, sitemap.xml, robots.txt (fichiers de métadonnées)
*/
{
source:
'/((?!api|_next/static|_next/image|favicon.ico|sitemap.xml|robots.txt).*)',
missing: [
{ type: 'header', key: 'next-router-prefetch' },
{ type: 'header', key: 'purpose', value: 'prefetch' },
],
},
{
source:
'/((?!api|_next/static|_next/image|favicon.ico|sitemap.xml|robots.txt).*)',
has: [
{ type: 'header', key: 'next-router-prefetch' },
{ type: 'header', key: 'purpose', value: 'prefetch' },
],
},
{
source:
'/((?!api|_next/static|_next/image|favicon.ico|sitemap.xml|robots.txt).*)',
has: [{ type: 'header', key: 'x-present' }],
missing: [{ type: 'header', key: 'x-missing', value: 'prefetch' }],
},
],
}
Bon à savoir : Les valeurs du
matcher
doivent être des constantes pour pouvoir être analysées statiquement au moment de la construction. Les valeurs dynamiques telles que les variables seront ignorées.
Les matchers configurés :
- DOIVENT commencer par
/
- Peuvent inclure des paramètres nommés :
/about/:path
correspond à/about/a
et/about/b
mais pas à/about/a/c
- Peuvent avoir des modificateurs sur les paramètres nommés (commençant par
:
) :/about/:path*
correspond à/about/a/b/c
car*
signifie zéro ou plus.?
signifie zéro ou un et+
un ou plus - Peuvent utiliser des expressions régulières entre parenthèses :
/about/(.*)
est identique à/about/:path*
Lisez plus de détails dans la documentation path-to-regexp.
Bon à savoir : Pour des raisons de compatibilité ascendante, Next.js considère toujours
/public
comme/public/index
. Par conséquent, un matcher de/public/:path
correspondra.
Instructions conditionnelles
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
export function middleware(request: NextRequest) {
if (request.nextUrl.pathname.startsWith('/about')) {
return NextResponse.rewrite(new URL('/about-2', request.url))
}
if (request.nextUrl.pathname.startsWith('/dashboard')) {
return NextResponse.rewrite(new URL('/dashboard/user', request.url))
}
}
import { NextResponse } from 'next/server'
export function middleware(request) {
if (request.nextUrl.pathname.startsWith('/about')) {
return NextResponse.rewrite(new URL('/about-2', request.url))
}
if (request.nextUrl.pathname.startsWith('/dashboard')) {
return NextResponse.rewrite(new URL('/dashboard/user', request.url))
}
}
NextResponse
L'API NextResponse
vous permet de :
redirect
la requête entrante vers une URL différenterewrite
la réponse en affichant une URL donnée- Définir des en-têtes de requête pour les routes API,
getServerSideProps
et les destinations derewrite
- Définir des cookies de réponse
- Définir des en-têtes de réponse
Pour produire une réponse depuis le Middleware, vous pouvez :
rewrite
vers une route (Page ou Gestionnaire de route) qui produit une réponse- retourner une
NextResponse
directement. Voir Production d'une réponse
Utilisation des cookies
Les cookies sont des en-têtes réguliers. Sur une Request
, ils sont stockés dans l'en-tête Cookie
. Sur une Response
, ils sont dans l'en-tête Set-Cookie
. Next.js fournit un moyen pratique d'accéder et de manipuler ces cookies via l'extension cookies
sur NextRequest
et NextResponse
.
- Pour les requêtes entrantes,
cookies
vient avec les méthodes suivantes :get
,getAll
,set
etdelete
cookies. Vous pouvez vérifier l'existence d'un cookie avechas
ou supprimer tous les cookies avecclear
. - Pour les réponses sortantes,
cookies
a les méthodes suivantesget
,getAll
,set
etdelete
.
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
export function middleware(request: NextRequest) {
// Supposons qu'un en-tête "Cookie:nextjs=fast" soit présent sur la requête entrante
// Récupération des cookies de la requête en utilisant l'API `RequestCookies`
let cookie = request.cookies.get('nextjs')
console.log(cookie) // => { name: 'nextjs', value: 'fast', Path: '/' }
const allCookies = request.cookies.getAll()
console.log(allCookies) // => [{ name: 'nextjs', value: 'fast' }]
request.cookies.has('nextjs') // => true
request.cookies.delete('nextjs')
request.cookies.has('nextjs') // => false
// Définition des cookies sur la réponse en utilisant l'API `ResponseCookies`
const response = NextResponse.next()
response.cookies.set('vercel', 'fast')
response.cookies.set({
name: 'vercel',
value: 'fast',
path: '/',
})
cookie = response.cookies.get('vercel')
console.log(cookie) // => { name: 'vercel', value: 'fast', Path: '/' }
// La réponse sortante aura un en-tête `Set-Cookie:vercel=fast;path=/`.
return response
}
import { NextResponse } from 'next/server'
export function middleware(request) {
// Supposons qu'un en-tête "Cookie:nextjs=fast" soit présent sur la requête entrante
// Récupération des cookies de la requête en utilisant l'API `RequestCookies`
let cookie = request.cookies.get('nextjs')
console.log(cookie) // => { name: 'nextjs', value: 'fast', Path: '/' }
const allCookies = request.cookies.getAll()
console.log(allCookies) // => [{ name: 'nextjs', value: 'fast' }]
request.cookies.has('nextjs') // => true
request.cookies.delete('nextjs')
request.cookies.has('nextjs') // => false
// Définition des cookies sur la réponse en utilisant l'API `ResponseCookies`
const response = NextResponse.next()
response.cookies.set('vercel', 'fast')
response.cookies.set({
name: 'vercel',
value: 'fast',
path: '/',
})
cookie = response.cookies.get('vercel')
console.log(cookie) // => { name: 'vercel', value: 'fast', Path: '/' }
// La réponse sortante aura un en-tête `Set-Cookie:vercel=fast;path=/test`.
return response
}
Définition des en-têtes
Vous pouvez définir des en-têtes de requête et de réponse en utilisant l'API NextResponse
(la définition des en-têtes de requête est disponible depuis Next.js v13.0.0).
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
export function middleware(request: NextRequest) {
// Clonez les en-têtes de la requête et définissez un nouvel en-tête `x-hello-from-middleware1`
const requestHeaders = new Headers(request.headers)
requestHeaders.set('x-hello-from-middleware1', 'hello')
// Vous pouvez également définir des en-têtes de requête dans NextResponse.next
const response = NextResponse.next({
request: {
// Nouveaux en-têtes de requête
headers: requestHeaders,
},
})
// Définissez un nouvel en-tête de réponse `x-hello-from-middleware2`
response.headers.set('x-hello-from-middleware2', 'hello')
return response
}
import { NextResponse } from 'next/server'
export function middleware(request) {
// Clonez les en-têtes de la requête et définissez un nouvel en-tête `x-hello-from-middleware1`
const requestHeaders = new Headers(request.headers)
requestHeaders.set('x-hello-from-middleware1', 'hello')
// Vous pouvez également définir des en-têtes de requête dans NextResponse.next
const response = NextResponse.next({
request: {
// Nouveaux en-têtes de requête
headers: requestHeaders,
},
})
// Définissez un nouvel en-tête de réponse `x-hello-from-middleware2`
response.headers.set('x-hello-from-middleware2', 'hello')
return response
}
Bon à savoir : Évitez de définir de gros en-têtes car cela peut causer une erreur 431 Request Header Fields Too Large en fonction de la configuration de votre serveur web backend.
CORS
Vous pouvez définir des en-têtes CORS dans le Middleware pour autoriser les requêtes cross-origin, y compris les requêtes simples et pré-vérifiées.
import { NextRequest, NextResponse } from 'next/server'
const allowedOrigins = ['https://acme.com', 'https://my-app.org']
const corsOptions = {
'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
'Access-Control-Allow-Headers': 'Content-Type, Authorization',
}
export function middleware(request: NextRequest) {
// Vérifiez l'origine de la requête
const origin = request.headers.get('origin') ?? ''
const isAllowedOrigin = allowedOrigins.includes(origin)
// Gérez les requêtes pré-vérifiées
const isPreflight = request.method === 'OPTIONS'
if (isPreflight) {
const preflightHeaders = {
...(isAllowedOrigin && { 'Access-Control-Allow-Origin': origin }),
...corsOptions,
}
return NextResponse.json({}, { headers: preflightHeaders })
}
// Gérez les requêtes simples
const response = NextResponse.next()
if (isAllowedOrigin) {
response.headers.set('Access-Control-Allow-Origin', origin)
}
Object.entries(corsOptions).forEach(([key, value]) => {
response.headers.set(key, value)
})
return response
}
export const config = {
matcher: '/api/:path*',
}
import { NextResponse } from 'next/server'
const allowedOrigins = ['https://acme.com', 'https://my-app.org']
const corsOptions = {
'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
'Access-Control-Allow-Headers': 'Content-Type, Authorization',
}
export function middleware(request) {
// Vérifiez l'origine de la requête
const origin = request.headers.get('origin') ?? ''
const isAllowedOrigin = allowedOrigins.includes(origin)
// Gérez les requêtes pré-vérifiées
const isPreflight = request.method === 'OPTIONS'
if (isPreflight) {
const preflightHeaders = {
...(isAllowedOrigin && { 'Access-Control-Allow-Origin': origin }),
...corsOptions,
}
return NextResponse.json({}, { headers: preflightHeaders })
}
// Gérez les requêtes simples
const response = NextResponse.next()
if (isAllowedOrigin) {
response.headers.set('Access-Control-Allow-Origin', origin)
}
Object.entries(corsOptions).forEach(([key, value]) => {
response.headers.set(key, value)
})
return response
}
export const config = {
matcher: '/api/:path*',
}
Bon à savoir : Vous pouvez configurer les en-têtes CORS pour des routes individuelles dans les Gestionnaires de route.
Production d'une réponse
Vous pouvez répondre directement depuis un Middleware en retournant une instance de Response
ou NextResponse
. (Disponible depuis Next.js v13.1.0)
import type { NextRequest } from 'next/server'
import { isAuthenticated } from '@lib/auth'
// Limiter le middleware aux chemins commençant par `/api/`
export const config = {
matcher: '/api/:function*',
}
export function middleware(request: NextRequest) {
// Appeler notre fonction d'authentification pour vérifier la requête
if (!isAuthenticated(request)) {
// Répondre avec un JSON indiquant un message d'erreur
return Response.json(
{ success: false, message: 'authentication failed' },
{ status: 401 }
)
}
}
import { isAuthenticated } from '@lib/auth'
// Limiter le middleware aux chemins commençant par `/api/`
export const config = {
matcher: '/api/:function*',
}
export function middleware(request) {
// Appeler notre fonction d'authentification pour vérifier la requête
if (!isAuthenticated(request)) {
// Répondre avec un JSON indiquant un message d'erreur
return Response.json(
{ success: false, message: 'authentication failed' },
{ status: 401 }
)
}
}
waitUntil
et NextFetchEvent
L'objet NextFetchEvent
étend l'objet natif FetchEvent
et inclut la méthode waitUntil()
.
La méthode waitUntil()
prend une promesse comme argument et prolonge la durée de vie du Middleware jusqu'à ce que la promesse soit résolue. Ceci est utile pour effectuer des tâches en arrière-plan.
import { NextResponse } from 'next/server'
import type { NextFetchEvent, NextRequest } from 'next/server'
export function middleware(req: NextRequest, event: NextFetchEvent) {
event.waitUntil(
fetch('https://my-analytics-platform.com', {
method: 'POST',
body: JSON.stringify({ pathname: req.nextUrl.pathname }),
})
)
return NextResponse.next()
}
Flags avancés pour Middleware
Dans la version v13.1
de Next.js, deux flags supplémentaires ont été introduits pour les middlewares : skipMiddlewareUrlNormalize
et skipTrailingSlashRedirect
pour gérer des cas d'utilisation avancés.
skipTrailingSlashRedirect
désactive les redirections de Next.js pour l'ajout ou la suppression de barres obliques finales. Cela permet une gestion personnalisée dans le middleware pour conserver la barre oblique finale pour certains chemins mais pas d'autres, ce qui peut faciliter les migrations progressives.
module.exports = {
skipTrailingSlashRedirect: true,
}
const legacyPrefixes = ['/docs', '/blog']
export default async function middleware(req) {
const { pathname } = req.nextUrl
if (legacyPrefixes.some((prefix) => pathname.startsWith(prefix))) {
return NextResponse.next()
}
// appliquer la gestion des barres obliques finales
if (
!pathname.endsWith('/') &&
!pathname.match(/((?!\.well-known(?:\/.*)?)(?:[^/]+\/)*[^/]+\.\w+)/)
) {
return NextResponse.redirect(
new URL(`${req.nextUrl.pathname}/`, req.nextUrl)
)
}
}
skipMiddlewareUrlNormalize
permet de désactiver la normalisation des URL dans Next.js pour traiter les visites directes et les transitions client de la même manière. Dans certains cas avancés, cette option offre un contrôle total en utilisant l'URL d'origine.
module.exports = {
skipMiddlewareUrlNormalize: true,
}
export default async function middleware(req) {
const { pathname } = req.nextUrl
// GET /_next/data/build-id/hello.json
console.log(pathname)
// avec le flag cela donne /_next/data/build-id/hello.json
// sans le flag cela serait normalisé en /hello
}
Tests unitaires (expérimental)
À partir de Next.js 15.1, le package next/experimental/testing/server
contient des utilitaires pour aider à tester unitairement les fichiers middleware. Les tests unitaires des middlewares peuvent aider à s'assurer qu'ils ne s'exécutent que sur les chemins souhaités et que la logique de routage personnalisée fonctionne comme prévu avant que le code n'atteigne la production.
La fonction unstable_doesMiddlewareMatch
peut être utilisée pour vérifier si un middleware s'exécutera pour l'URL, les en-têtes et les cookies fournis.
import { unstable_doesMiddlewareMatch } from 'next/experimental/testing/server'
expect(
unstable_doesMiddlewareMatch({
config,
nextConfig,
url: '/test',
})
).toEqual(false)
La fonction middleware entière peut également être testée.
import { isRewrite, getRewrittenUrl } from 'next/experimental/testing/server'
const request = new NextRequest('https://nextjs.org/docs')
const response = await middleware(request)
expect(isRewrite(response)).toEqual(true)
expect(getRewrittenUrl(response)).toEqual('https://other-domain.com/docs')
// getRedirectUrl pourrait aussi être utilisé si la réponse était une redirection
Runtime
Par défaut, le middleware utilise le runtime Edge. Depuis la version v15.2 (canary), nous avons un support expérimental pour utiliser le runtime Node.js. Pour l'activer, ajoutez le flag dans votre fichier next.config
:
import type { NextConfig } from 'next'
const nextConfig: NextConfig = {
experimental: {
nodeMiddleware: true,
},
}
export default nextConfig
const nextConfig = {
experimental: {
nodeMiddleware: true,
},
}
export default nextConfig
Puis dans votre fichier middleware, définissez le runtime sur nodejs
dans l'objet config
:
export const config = {
runtime: 'nodejs',
}
export const config = {
runtime: 'nodejs',
}
Note : Cette fonctionnalité n'est pas encore recommandée pour une utilisation en production. Par conséquent, Next.js générera une erreur sauf si vous utilisez la version next@canary au lieu de la version stable.
Support des plateformes
Option de déploiement | Supporté |
---|---|
Serveur Node.js | Oui |
Conteneur Docker | Oui |
Export statique | Non |
Adaptateurs | Dépend de la plateforme |
Apprenez comment configurer le Middleware lors de l'hébergement autonome de Next.js.
Historique des versions
Version | Changements |
---|---|
v15.2.0 | Le middleware peut maintenant utiliser le runtime Node.js (expérimental) |
v13.1.0 | Ajout des flags avancés pour Middleware |
v13.0.0 | Le middleware peut modifier les en-têtes de requête, les en-têtes de réponse et envoyer des réponses |
v12.2.0 | Le middleware est stable, veuillez consulter le guide de mise à niveau |
v12.0.9 | Application des URL absolues dans le Edge Runtime (PR) |
v12.0.0 | Middleware (Beta) ajouté |