Comprendre la directive « use cache » et la révalidation basée sur des balises de Next.js 16
Découvrez comment fonctionne la directive « use cache » dans Next.js 16, ses fonctions de révalidation associées, ainsi que la manière d’appliquer un cache conscient du locataire dans les applications multi-locataires.
Le cacheage dans Next.js a traditionnellement été perçu comme une sorte de boîte noire — un mélange hétéroclite de paramètres au niveau des fichiers, d’options transmises à fetch, ainsi que de paramètres par défaut du framework qui changeaient suffisamment d’une version à l’autre pour que même les développeurs expérimentés gardent la documentation ouverte par précaution. La directive "use cache" introduite dans Next.js 16, faisant partie du modèle plus large des composants de cache, change cela en permettant de déclarer explicitement le comportement de cacheage au niveau d’un composant ou d’une fonction, plutôt que de l’hériter implicitement des paramètres par défaut dont on attend qu’on s’en souvienne.
Ci-dessous est une explication détaillée de ce que fait réellement cette directive et des situations où il est judicieux de l’utiliser.
Ce que fait réellement la directive
En plaçant "use cache" à l’intérieur d’une fonction ou d’un composant, on indique à Next.js de mettre en cache tout ce que cette fonction renvoie. Conceptuellement, cela joue un rôle similaire à "use client", sauf que plutôt que de délimiter le côté client, cela définit une frontière de mise en cache :
async function getDashboardStats(tenantId: string) {
"use cache";
const stats = await db.query.stats.findMany({ where: { tenantId } });
return stats;
}
La sortie de la fonction est mises en cache et identifiée en fonction des arguments fournis, puis réutilisée lors des requêtes suivantes jusqu’à ce que quelque chose la rende invalide. Cela représente un véritable changement par rapport à l’approche ancienne qui consistait à mettre en cache une requête fetch individuelle ou tout un segment de route : désormais, on peut mettre en cache au niveau de granularité qui convient le mieux à ses données, jusqu’au niveau d’une fonction spécifique.
Les trois fonctions complémentaires
En plus de cette directive, Cache Components introduit un petit ensemble d’API permettant de gérer délibérément les données mises en cache, au lieu d’attendre simplement que le délai expire :
revalidateTag(tag)— efface toutes les entrées de cache associées à une donnée spécifique. Cela est utile lorsque une seule modification affecte des données sur lesquelles dépendent plusieurs fonctions en cache différentes.updateTag(tag)— une version plus ciblée de la même idée, conçue pour mettre à jour uniquement les entrées de cache liées à une donnée précise plutôt que tout ce qui se trouve sous elle.refresh()— met à jour les données en cache relatives à la requête actuelle.
C’est le principe qui fait fonctionner tout le système : associer une étiquette significative à chaque fonction mémorisée — comme tenant-stats ou invoice-list — et chaque fois qu’une modification affecte ces données de base, appeler revalidateTag avec l’étiquette correspondante, plutôt que de tenter de définir une fenêtre d’expiration basée sur le temps, qui soit trop courte (annulant l’intérêt du cache) ou trop longue (fournissant des données obsolètes).
async function getInvoices(tenantId: string) {
"use cache";
cacheTag(`invoices-${tenantId}`);
return db.query.invoices.findMany({ where: { tenantId } });
}
// After creating an invoice:
async function createInvoice(data: InvoiceInput) {
await db.insert(invoices).values(data);
revalidateTag(`invoices-${data.tenantId}`);
}
Cette combinaison — étiquetage lors de la lecture, révalidation lors de l’écriture — représente en réalité l’essence même du système en miniature. Presque tout ce que vous ferez avec ce système est une variation de cette même association.
Ponts de confusion fréquents
La erreur la plus fréquente commise par les équipes est de penser que "use cache" constitue un substitut simple à l’option next: { revalidate } dans fetch(). En réalité, ces deux éléments traitent de problèmes différents, bien que liés. L’option au niveau de fetch() gère une seule requête réseau. Quant à "use cache", il met en cache le résultat d’une fonction ou d’un composant entier, qui peut effectuer bien plus qu’une seule action en interne — consulter une base de données, effectuer des calculs, voire appeler fetch lui-même au cours du processus. Si vous avez besoin de mettre en cache uniquement une requête API externe, utiliser le mécanisme de mise en cache au niveau de fetch() reste généralement le choix le plus simple. "use cache" devient avantageux lorsque vous souhaitez mettre en cache tout le résultat d’un calcul, et non seulement une seule requête qui y contribue.
La deuxième erreur fréquente consiste à omettre complètement l’étape de mise en étiquette, ce qui provoque des confusions par la suite lorsque une mutation échoue à supprimer les données mises en cache qu’elle aurait dû effacer. Sans étiquette associée, le seul mécanisme de invalidation disponible est le temps, ce qui remet en question l’un des principaux avantages de l’adoption de ce modèle : vous avez pris en charge la complexité supplémentaire du cache explicite sans obtenir de contrôle réel sur son invalidation.
Étiquettes conscientes du locataire pour les applications multi-locataires
Si vous travaillez sur un système multi-locataires, il y a un détail important à souligner : les étiquettes doivent encoder le locataire, et non seulement le type de données mises en cache. Une étiquette générique comme invoices partagée entre tous les locataires signifie que la invalidation des données d’un locataire les rend également invalides pour tous — ce qui entraîne soit un problème de correction (un locataire voyant des résultats obsolètes parce qu’une mutation d’un autre locataire a déclenché une révalidation commune), soit un problème de performance (le cache étant vidé bien plus souvent que nécessaire). Une forme comme invoices-${tenantId}, telle que utilisée dans l’exemple ci-dessus, n’est pas une simple préférence stylistique : c’est ce qui distingue une stratégie d’invalidation fonctionnelle d’une stratégie défaillante.
Pourquoi il est avantageux de bien configurer cela dès le début
Certains kits de démarrage pour tableaux de bord s’appuient précisément sur cette discipline pour structurer leur couche de données. Par exemple, un modèle comme le modèle de tableau de bord d’Ovyqen pour les projets Next.js et SaaS applique systématiquement la combinaison « tag à la lecture, révalidation à l’écriture », intégrant dès le départ des identifiants de locataire dans chaque tag plutôt que d’y apporter des corrections après un problème de mise en cache inter-locataires. Si vous envisagez des modèles de tableaux de bord et que l’invalidation du cache est la partie de votre application existante en qui personne ne a vraiment confiance, c’est une raison valable de partir d’une base qui gère déjà correctement ce problème.
Questions fréquentes
Faut-il migrer chaque appel à fetch() vers "use cache" ? Pas nécessairement — ces deux outils se recoupent mais ne sont pas interchangeables. Le cache au niveau de fetch() reste adapté aux scénarios simples à une seule requête. Préférez "use cache" lorsque vous devez stocker en cache un résultat calculé ou la sortie d’une composante entière.
"use cache" est-il suffisamment mature pour les tableaux de bord en production ? Abordez-le de la même manière que n’importe quel mécanisme de cache relativement récent — testez en détail vos stratégies d’invalidation, en particulier pour les données multi-locataires, avant de compter sur lui pour tout élément destiné aux clients où la date de mise à jour est importante.
Que se passe-t-il si une fonction mise en cache n’est pas marquée ? La mise en cache a toujours lieu, mais vous perdez la possibilité de la invalider délibérément en réponse à un événement spécifique. Vous êtes alors contraint de vous fier uniquement à l’expiration basée sur le temps, ce qui n’est rarement le comportement souhaité.
La mise en cache explicite exige plus d’efforts au départ que de se fier simplement aux paramètres par défaut d’un framework. Mais pour les données de tableau de bord où afficher des informations obsolètes a un coût réel, cet effort supplémentaire est presque toujours le bon compromis à faire.
Lectures complémentaires
- Comprendre les composants cache et le préchargement partiel dans Next.js 16.3 — Explique comment la fonctionnalité Instant Navigations de Next.js 16.3 utilise des coquilles de route partagées et des décisions de streaming explicites pour donner l’impression d’applications générées côté serveur en temps réel.