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 principal middleware.ts. Cela permet une gestion plus propre du middleware spécifique aux routes, agrégé dans middleware.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*',
}

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 :

  1. headers depuis next.config.js
  2. redirects depuis next.config.js
  3. Middleware (rewrites, redirects, etc.)
  4. beforeFiles (rewrites) depuis next.config.js
  5. Routes du système de fichiers (public/, _next/static/, pages/, app/, etc.)
  6. afterFiles (rewrites) depuis next.config.js
  7. Routes dynamiques (/blog/[slug])
  8. fallback (rewrites) depuis next.config.js

Il existe deux façons de définir sur quels chemins le Middleware s'exécutera :

  1. Configuration personnalisée du matcher
  2. Instructions conditionnelles

Matcher

matcher vous permet de filtrer le Middleware pour qu'il s'exécute sur des chemins spécifiques.

middleware.js
export const config = {
  matcher: '/about/:path*',
}

Vous pouvez apparier un seul chemin ou plusieurs chemins avec une syntaxe de tableau :

middleware.js
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 :

middleware.js
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 :

middleware.js
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 :

  1. DOIVENT commencer par /
  2. Peuvent inclure des paramètres nommés : /about/:path correspond à /about/a et /about/b mais pas à /about/a/c
  3. 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
  4. 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))
  }
}

NextResponse

L'API NextResponse vous permet de :

  • redirect la requête entrante vers une URL différente
  • rewrite 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 de rewrite
  • 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 :

  1. rewrite vers une route (Page ou Gestionnaire de route) qui produit une réponse
  2. 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.

  1. Pour les requêtes entrantes, cookies vient avec les méthodes suivantes : get, getAll, set et delete cookies. Vous pouvez vérifier l'existence d'un cookie avec has ou supprimer tous les cookies avec clear.
  2. Pour les réponses sortantes, cookies a les méthodes suivantes get, getAll, set et delete.
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
}

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
}

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*',
}

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 }
    )
  }
}

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.

middleware.ts
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.

next.config.js
module.exports = {
  skipTrailingSlashRedirect: true,
}
middleware.js
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.

next.config.js
module.exports = {
  skipMiddlewareUrlNormalize: true,
}
middleware.js
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

Puis dans votre fichier middleware, définissez le runtime sur nodejs dans l'objet config :

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éploiementSupporté
Serveur Node.jsOui
Conteneur DockerOui
Export statiqueNon
AdaptateursDépend de la plateforme

Apprenez comment configurer le Middleware lors de l'hébergement autonome de Next.js.

Historique des versions

VersionChangements
v15.2.0Le middleware peut maintenant utiliser le runtime Node.js (expérimental)
v13.1.0Ajout des flags avancés pour Middleware
v13.0.0Le middleware peut modifier les en-têtes de requête, les en-têtes de réponse et envoyer des réponses
v12.2.0Le middleware est stable, veuillez consulter le guide de mise à niveau
v12.0.9Application des URL absolues dans le Edge Runtime (PR)
v12.0.0Middleware (Beta) ajouté