Tous les articles
·Ingénierie·7 min

Caching dans Next.js App Router : fetch, use cache et revalidation

Comprendre et configurer le cache Next.js 15 : fetch options, use cache, unstable_cache et stratégies de revalidation pour maximiser les performances.

Le caching dans Next.js App Router est l'un des sujets les plus mal compris par les équipes qui migrent depuis le Pages Router. Depuis Next.js 15, la directive use cache unifie une grande partie des mécanismes de mise en cache, mais fetch, unstable_cache et les fonctions de revalidation restent incontournables pour gérer les performances web et la fraîcheur des données.

Schéma d'architecture de cache dans une application Next.js App Router
Le cache dans Next.js opère à plusieurs niveaux simultanément : données, rendu de route et navigation côté client.

Les quatre couches de cache dans Next.js App Router

Next.js implémente quatre mécanismes de cache distincts, qui opèrent à des niveaux différents de la pile applicative :

MécanismePortéeStockageInvalidation
Request MemoizationRequête uniqueMémoire viveAutomatique à la fin de la requête
Data CacheCross-requêtesPersistant (disque/CDN)revalidate, revalidateTag, revalidatePath
Full Route CacheRoute complètePersistantRedéploiement ou revalidation
Router CacheNavigation clientMémoire navigateurNavigation ou expiration TTL

La confusion vient souvent de la superposition de ces quatre couches. Une page statique peut être servie depuis le Full Route Cache alors que les données qu'elle affiche proviennent du Data Cache avec un TTL différent. Identifier quel niveau s'applique à chaque situation est la base d'une stratégie de performance solide.

fetch et le Data Cache : configuration par requête

Next.js étend l'API native fetch pour exposer des options de cache spécifiques au framework. Chaque appel fetch côté serveur configure son comportement de façon indépendante, ce qui permet une granularité fine sans configuration globale.

// Données mises en cache indéfiniment (statique)
const res = await fetch('https://api.exemple.com/produits', {
  cache: 'force-cache',
});

// Données revalidées toutes les 60 secondes (ISR)
const res = await fetch('https://api.exemple.com/articles', {
  next: { revalidate: 60, tags: ['articles'] },
});

// Données jamais mises en cache (dynamique)
const res = await fetch('https://api.exemple.com/stock-temps-reel', {
  cache: 'no-store',
});

L'option next.tags attache des étiquettes à une réponse mise en cache. Ces étiquettes servent ensuite de point d'entrée pour une invalidation précise via revalidateTag. C'est la fondation de la revalidation à la demande dans les applications modernes avec Next.js.

unstable_cache pour les sources de données non-fetch

Quand on interroge une base de données directement via Prisma, ou qu'on appelle un SDK tiers qui n'utilise pas fetch, les options de cache de fetch ne s'appliquent pas. C'est là qu'intervient unstable_cache, qui encapsule n'importe quelle fonction asynchrone dans la couche Data Cache.

import { unstable_cache } from 'next/cache';
import { db } from '@/lib/db';

export const getArticles = unstable_cache(
  async () => {
    return await db.article.findMany({
      where: { published: true },
      orderBy: { publishedAt: 'desc' },
    });
  },
  ['articles-list'],
  {
    revalidate: 300, // 5 minutes
    tags: ['articles'],
  }
);

Les trois paramètres sont : la fonction à mettre en cache, une clé de cache unique sous forme de tableau de chaînes, et les options de revalidation. Le préfixe unstable_ ne signifie pas que la fonction est instable en production. Il indique que l'API peut encore évoluer entre deux versions mineures du framework.

use cache : la directive unifiée de Next.js 15

Next.js 15 introduit use cache comme primitive unifiée destinée à remplacer progressivement unstable_cache. Elle fonctionne comme use client ou use server : on la place en tête de fichier ou de fonction, et Next.js prend en charge la sérialisation des arguments et la gestion du cache.

'use cache';

import { cacheTag, cacheLife } from 'next/dist/server/use-cache/cache-tag';
import { db } from '@/lib/db';

export async function getArticles() {
  cacheTag('articles');
  cacheLife('hours'); // profil prédéfini : stale=0, revalidate=3600, expire=86400
  return await db.article.findMany({ where: { published: true } });
}

La fonction cacheLife accepte des profils prédéfinis (seconds, minutes, hours, days, weeks) ou des objets personnalisés avec les propriétés stale, revalidate et expire. Cette approche est plus lisible que de jongler avec des entiers en secondes dispersés dans le code.

Le taux de cache hit attendu dépend directement du TTL configuré. Pour un TTL de TT secondes et un taux de requêtes λ\lambda (en req/s), le nombre moyen de requêtes servies depuis le cache avant une revalidation est :

Ncache=λTN_{\text{cache}} = \lambda \cdot T

Pour un article de blog consulté 5 fois par seconde avec un TTL d'une heure (T=3600T = 3600), on sert en moyenne 5×3600=180005 \times 3600 = 18\,000 requêtes depuis le cache pour une seule revalidation, soit un ratio très favorable.

revalidatePath et revalidateTag : invalidation à la demande

La revalidation à la demande invalide le cache immédiatement, sans attendre l'expiration du TTL. Next.js expose deux fonctions distinctes : revalidateTag et revalidatePath.

revalidateTag invalide toutes les entrées de cache portant un tag donné, quelle que soit la page qui les utilise. C'est la méthode recommandée quand on connaît exactement quelle donnée a changé.

'use server';
import { revalidateTag } from 'next/cache';
import { db } from '@/lib/db';

export async function createArticle(data: ArticleInput) {
  await db.article.create({ data });
  revalidateTag('articles'); // invalide tous les caches taggés 'articles'
}

revalidatePath invalide le cache d'un chemin de route complet. C'est moins précis car cela régénère l'ensemble de la page, mais c'est utile quand plusieurs sources de données composent une même page et qu'on veut garantir la fraîcheur globale.

import { revalidatePath } from 'next/cache';

revalidatePath('/blog');              // invalide la liste des articles
revalidatePath('/blog/[slug]', 'page'); // invalide toutes les pages article
ContexteRecommandation
Mutation d'une entité précise (CRUD)revalidateTag
Mise à jour globale d'une sectionrevalidatePath
Webhook CMS headlessrevalidateTag via Route Handler
Purge complète après déploiementrevalidatePath('/', 'layout')

En pratique

Une stratégie de cache Next.js efficace se construit en trois étapes. Premièrement, on définit les profils de fraîcheur de chaque ressource : statique (pas de revalidation), semi-dynamique (TTL entre 1 minute et 24 heures) ou dynamique (pas de cache). Deuxièmement, on choisit la primitive adaptée : fetch avec next.revalidate pour les appels HTTP simples, use cache avec cacheTag et cacheLife pour les fonctions de récupération via ORM ou SDK tiers. Troisièmement, on câble les invalidations à la demande dans les Server Actions ou les Route Handlers qui mutent les données.

Cette approche évite les deux écueils classiques : le sur-cache, qui sert des données périmées sans que l'équipe s'en aperçoive, et le sous-cache, qui régénère à chaque requête ce qui pourrait rester en cache pendant des heures sans impact sur la cohérence.

Pour aller plus loin, la documentation officielle Next.js sur le caching reste la référence la plus complète et la plus à jour.

Sources

  1. Getting Started: Caching | Next.js
  2. Directives: use cache | Next.js
  3. Mastering Next.js Caching: revalidatePath vs revalidateTag with unstable_cache