SD
Next.jsMiddlewareApp RouterTypeScript

Middleware Next.js App Router : protégez vos routes et gérez les redirections

Découvrez comment utiliser le Middleware Next.js pour protéger vos routes, gérer les redirections et personnaliser les requêtes avant le rendu.

AMAlexis Mouchon8 min de lecture

Le Middleware Next.js est l'un de ces outils qu'on sous-estime souvent au départ, mais qui finit par devenir indispensable dès qu'une application prend de l'ampleur. Protéger des routes, rediriger des utilisateurs non authentifiés, adapter les réponses selon la géolocalisation ou le rôle de l'utilisateur — tout ça se gère proprement dans un seul fichier, avant même que la page soit rendue.

Dans cet article, on va explorer en profondeur comment fonctionne le Middleware dans l'App Router, avec des cas d'usage concrets et du code TypeScript prêt à l'emploi.

Qu'est-ce que le Middleware Next.js ?

Le Middleware s'exécute avant le traitement d'une requête sur le serveur — entre l'arrivée de la requête et la réponse renvoyée au client. Concrètement, il tourne sur le Edge Runtime de Vercel (ou sur votre infrastructure Node.js), ce qui lui confère une latence extrêmement faible.

Son rôle peut être varié :

Le fichier se nomme middleware.ts et doit être placé à la racine du projet (au même niveau que app/ ou src/).

Structure de base du Middleware

// middleware.ts
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'

export function middleware(request: NextRequest) {
  return NextResponse.next()
}

export const config = {
  matcher: ['/dashboard/:path*', '/admin/:path*'],
}

Le config.matcher est crucial : il définit sur quelles routes le middleware s'applique. Sans lui, il s'exécuterait sur chaque requête — y compris les assets statiques, ce qu'on veut éviter.

Syntaxe du matcher

export const config = {
  matcher: [
    // Appliquer sur toutes les routes sauf les assets et les API
    '/((?!_next/static|_next/image|favicon.ico|api/).*)',
    
    // Ou cibler précisément
    '/dashboard/:path*',
    '/admin/:path*',
    '/profile',
  ],
}

Cas d'usage 1 : Protection des routes authentifiées

Le cas le plus fréquent. On veut rediriger vers /login si l'utilisateur n'a pas de session valide.

// middleware.ts
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'

const PROTECTED_ROUTES = ['/dashboard', '/admin', '/profile']

export function middleware(request: NextRequest) {
  const { pathname } = request.nextUrl

  // Vérifier si la route est protégée
  const isProtectedRoute = PROTECTED_ROUTES.some((route) =>
    pathname.startsWith(route)
  )

  if (!isProtectedRoute) {
    return NextResponse.next()
  }

  // Récupérer le token depuis les cookies
  const token = request.cookies.get('auth-token')?.value

  if (!token) {
    const loginUrl = new URL('/login', request.url)
    loginUrl.searchParams.set('callbackUrl', pathname)
    return NextResponse.redirect(loginUrl)
  }

  return NextResponse.next()
}

export const config = {
  matcher: ['/((?!_next/static|_next/image|favicon.ico|api/auth).*)'],
}

Attention : le Middleware tourne sur l'Edge Runtime — vous ne pouvez pas y utiliser des librairies Node.js classiques ni faire des appels à votre base de données directement. Vérifiez plutôt la présence et la validité basique du token (signature JWT par exemple).

Cas d'usage 2 : Vérification JWT sans base de données

Pour valider un JWT sans appel externe, utilisez la librairie jose qui est compatible Edge :

// middleware.ts
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
import { jwtVerify } from 'jose'

const SECRET = new TextEncoder().encode(process.env.JWT_SECRET)

async function verifyToken(token: string): Promise<boolean> {
  try {
    await jwtVerify(token, SECRET)
    return true
  } catch {
    return false
  }
}

export async function middleware(request: NextRequest) {
  const { pathname } = request.nextUrl

  if (!pathname.startsWith('/dashboard')) {
    return NextResponse.next()
  }

  const token = request.cookies.get('auth-token')?.value

  if (!token || !(await verifyToken(token))) {
    return NextResponse.redirect(new URL('/login', request.url))
  }

  return NextResponse.next()
}

export const config = {
  matcher: ['/dashboard/:path*'],
}

Cas d'usage 3 : Réécriture d'URLs (URL Rewriting)

Le rewriting permet de modifier l'URL traitée en interne sans changer l'URL visible dans le navigateur — idéal pour l'A/B testing ou les redirections de domaines.

// middleware.ts
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'

export function middleware(request: NextRequest) {
  const { pathname } = request.nextUrl

  // Rediriger l'ancien blog vers les nouveaux slugs
  if (pathname.startsWith('/blog/old-')) {
    const newSlug = pathname.replace('/blog/old-', '/blog/')
    return NextResponse.redirect(new URL(newSlug, request.url), { status: 301 })
  }

  // A/B testing : router 50% du trafic vers une variante
  if (pathname === '/landing') {
    const bucket = Math.random() < 0.5 ? 'a' : 'b'
    return NextResponse.rewrite(new URL(`/landing-${bucket}`, request.url))
  }

  return NextResponse.next()
}

Différence entre redirect et rewrite

MéthodeURL visibleStatus HTTPCas d'usage
NextResponse.redirect()Change307/301Redirection permanente ou temporaire
NextResponse.rewrite()Ne change pas200A/B testing, routing interne

Cas d'usage 4 : Ajout d'headers personnalisés

Utile pour passer des données à vos Server Components via les headers, ou pour ajouter des headers de sécurité globaux.

// middleware.ts
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'

export function middleware(request: NextRequest) {
  const requestHeaders = new Headers(request.headers)

  // Passer l'URL courante aux Server Components
  requestHeaders.set('x-current-path', request.nextUrl.pathname)

  // Ajouter des headers de sécurité
  const response = NextResponse.next({
    request: { headers: requestHeaders },
  })

  response.headers.set('X-Frame-Options', 'DENY')
  response.headers.set('X-Content-Type-Options', 'nosniff')
  response.headers.set(
    'Strict-Transport-Security',
    'max-age=31536000; includeSubDomains'
  )

  return response
}

Vous pouvez ensuite lire ce header dans n'importe quel Server Component :

// app/dashboard/page.tsx
import { headers } from 'next/headers'

export default async function DashboardPage() {
  const headersList = await headers()
  const currentPath = headersList.get('x-current-path')

  return <div>Vous êtes sur : {currentPath}</div>
}

Bonnes pratiques et pièges à éviter

✅ Ce qu'il faut faire

❌ Ce qu'il faut éviter

Vérifier la compatibilité Edge

// Vérifiez la compatibilité des modules avec l'Edge Runtime
// en consultant : https://edge-runtime.vercel.app/features/available-apis

// ✅ Compatible Edge
import { jwtVerify } from 'jose'

// ❌ Non compatible Edge
import { verify } from 'jsonwebtoken' // dépend de Node.js crypto

Combiner avec les layouts et les Server Components

Une architecture solide combine le middleware (vérification rapide) avec la logique de session dans les Server Components (vérification complète) :

// app/dashboard/layout.tsx
import { redirect } from 'next/navigation'
import { getServerSession } from '@/lib/auth' // votre fonction de session

export default async function DashboardLayout({
  children,
}: {
  children: React.ReactNode
}) {
  const session = await getServerSession()

  if (!session) {
    redirect('/login')
  }

  return (
    <div>
      <nav>{/* Navigation avec infos utilisateur */}</nav>
      <main>{children}</main>
    </div>
  )
}

Le middleware fait office de première ligne de défense (rapide, Edge), et le layout assure la vérification complète avec accès à la base de données si nécessaire.

Conclusion

Le Middleware Next.js est un outil puissant, mais il faut comprendre ses contraintes (Edge Runtime, pas d'accès BDD direct) pour en tirer le meilleur parti. Utilisé correctement, il permet de centraliser toute la logique de protection des routes et de manipulation des requêtes en un seul endroit, avant même que vos composants soient rendus.

Pour aller plus loin, combinez-le avec les Server Actions vues dans un précédent article sur les Server Actions Next.js 15 pour avoir une stack d'authentification robuste et cohérente.


Vous travaillez sur un projet Next.js et vous souhaitez mettre en place une authentification solide ou une architecture frontend bien structurée ? N'hésitez pas à me contacter, je serai ravi d'en discuter avec vous.