Guide Next.js 16
Revalidation Next.js sur Vercel : invalider les données publiques en toute sécurité
Associez un événement de publication Acme Shop fiable à des tags et des chemins connus, puis pilotez l'ISR à la demande avec un comportement stale-while-revalidate plutôt que d'exposer un endpoint de purge générique.
Sur cette page
La mise en cache temporelle est un filet de sécurité utile, mais Acme Shop a besoin que son catalogue se rafraîchisse dès qu'un produit est publié. Dans le modèle de cache basé sur fetch de Next.js 16, les tags associent une cible d'invalidation aux données mises en cache. Un Route Handler protégé relie alors un événement de publication connu à ces cibles connues, ce qui transforme la revalidation Next.js en une opération d'ISR à la demande délibérée plutôt qu'en une purge de cache générique. Une conception sûre est allowlistée, authentifiée, observable et testée sur une preview.
À l'échelle d'une orchestration d'edge managée — de nombreuses origines, de nombreux CDN, des événements en continu — cette discipline évite que du « contenu obsolète » ne devienne silencieusement du « contenu faux », qu'Acme Shop l'exploite seul ou comme un maillon d'un programme plus large de Performance et de Visibilité.
N'exposez pas un endpoint de purge générique
Un Route Handler qui accepte n'importe quel tag ou chemin depuis Internet est un endpoint de purge de cache. Authentifiez l'appelant, n'acceptez que les types d'événements attendus et dérivez chaque tag et chemin dans le code applicatif côté serveur. Réservez ce pattern au contenu public, pas aux données de compte ou de commande.
Résultat visé et prérequis
Ce guide part d'un catalogue Acme Shop public. Quand Acme Shop publie solar-pack, ses fetches de collection et de produit sont marqués obsolètes via revalidateTag(..., "max"), les chemins connus sont marqués pour revalidation, et les visites suivantes se rafraîchissent en arrière-plan sans purge large.
Il vous faut : un projet Next.js 16 App Router qui n'utilise pas les Cache Components, une source de catalogue public, un déploiement de preview, ACME_SHOP_REVALIDATE_SECRET en variable d'environnement serveur uniquement, et un émetteur de confiance capable d'envoyer des requêtes HTTPS POST. Réservez ce pattern au contenu public ; un tableau de bord connecté doit récupérer des données privées et fraîches à chaque requête.
Cartographier un événement de revalidation Next.js vers des tags et des chemins
Une conception de revalidation Next.js disciplinée commence par la responsabilité : taguez les données selon qui en est responsable, pas selon le composant qui les affiche. Acme Shop utilise un tag de collection et un tag de produit stable ; le webhook associe l'événement connu product.published à des chemins littéraux et n'accepte jamais un tag ou un chemin fourni par l'appelant.
| Données mises en cache | Tag | Chemin marqué pour revalidation | Responsable |
|---|---|---|---|
| Collection publique | acme-shop:catalog | /shop | Service de publication produit |
| Un produit public | acme-shop:product:solar-pack | /shop/solar-pack | Service de publication produit |
revalidateTag s'applique à chaque usage mis en cache d'un tag. revalidatePath cible une route précise. Utilisez les deux ici, car l'événement modifie à la fois des données de catalogue partagées et deux chemins rendus connus.
revalidatePath accepte aussi un second argument optionnel, "page" ou "layout", pour invalider tout un motif de route dynamique tel que /shop/[slug] en un seul appel plutôt que de boucler sur des chemins littéraux ; le webhook mono-événement d'Acme Shop reste sur un chemin littéral, sans second argument, et la chaîne de chemin ne doit pas dépasser 1024 caractères. Si une route est atteinte via une réécriture (rewrite) Next.js, revalidez le chemin de destination, pas le chemin source vu par le visiteur : les entrées de cache sont indexées sur le fichier de route, pas sur l'URL de la requête.
Attacher les tags aux fetches publics
Rendez la politique de cache de données explicite. L'intervalle d'une heure est un filet de sécurité si un événement de publication échoue ; ce n'est pas une promesse que les données restent obsolètes pendant exactement une heure.
// lib/acme-shop/products.ts
export async function getProducts() {
const response = await fetch("https://catalog.acme-shop.example/v1/products", {
cache: "force-cache",
next: { revalidate: 3600, tags: ["acme-shop:catalog"] },
})
if (!response.ok) throw new Error("Products are unavailable")
return response.json()
}
export async function getProduct(slug: string) {
const response = await fetch(`https://catalog.acme-shop.example/v1/products/${slug}`, {
cache: "force-cache",
next: { revalidate: 3600, tags: ["acme-shop:catalog", `acme-shop:product:${slug}`] },
})
if (!response.ok) throw new Error("Product is unavailable")
return response.json()
}
Les tags sont sensibles à la casse et chacun ne doit pas dépasser 256 caractères. N'utilisez pas ce pattern pour des données qui varient selon la session, l'autorisation ou le panier.
next.tags sur fetch est l'une des deux façons dont Next.js 16 attache des tags aux données mises en cache. Avec les Cache Components, le même tag se déclare via cacheTag("acme-shop:catalog") dans une fonction 'use cache' ; seul l'endroit où le tag est déclaré change, la primitive revalidateTag reste identique.
Ajouter un webhook authentifié et strictement filtré
Le Route Handler rejette les identifiants manquants, le JSON malformé, les événements inconnus et les slugs non sûrs avant toute revalidation, et ne renvoie que des diagnostics non sensibles.
// app/api/revalidate-products/route.ts
import { revalidatePath, revalidateTag } from "next/cache"
import type { NextRequest } from "next/server"
type ProductEvent = { type: "product.published"; slug: string }
function isProductEvent(value: unknown): value is ProductEvent {
if (!value || typeof value !== "object") return false
const event = value as Record<string, unknown>
return event.type === "product.published" &&
typeof event.slug === "string" && /^[a-z0-9-]+$/.test(event.slug)
}
export async function POST(request: NextRequest) {
const expected = process.env.ACME_SHOP_REVALIDATE_SECRET
if (!expected || request.headers.get("authorization") !== `Bearer ${expected}`) {
return Response.json({ error: "Unauthorized" }, { status: 401 })
}
let body: unknown
try {
body = await request.json()
} catch {
return Response.json({ error: "Invalid JSON" }, { status: 400 })
}
if (!isProductEvent(body)) {
return Response.json({ error: "Unsupported event" }, { status: 400 })
}
const productTag = `acme-shop:product:${body.slug}`
const productPath = `/shop/${body.slug}`
revalidateTag("acme-shop:catalog", "max")
revalidateTag(productTag, "max")
revalidatePath("/shop")
revalidatePath(productPath)
return Response.json({
revalidated: true,
slug: body.slug,
tags: ["acme-shop:catalog", productTag],
paths: ["/shop", productPath],
})
}
La forme à un seul argument revalidateTag(tag) est dépréciée dans Next.js 16. Avec le profil recommandé "max", revalidateTag marque les entrées taguées comme obsolètes ; elle ne régénère pas toutes les pages et ne garantit pas que le visiteur suivant verra du contenu frais.
Le second argument ne se limite pas aux profils nommés : revalidateTag(tag, { expire: 0 }) est le pattern documenté par Next.js pour un tag qui doit expirer immédiatement plutôt que passer par le stale-while-revalidate. Le catalogue d'Acme Shop tolère une courte fenêtre obsolète, ce guide garde donc "max" — mais un prix proche du checkout qui ne peut jamais afficher un chiffre obsolète justifierait { expire: 0 } sur ce tag précis, au prix d'une régénération bloquante sur la requête suivante.
Comprendre le rayon d'action de l'ISR à la demande sur Vercel
Avant de valider le comportement, il faut connaître les limites de plateforme dans lesquelles ce webhook opère :
- Limité à un domaine et un déploiement. Un appel sur la preview ne touche jamais la production, et un appel en production ne touche jamais un déploiement plus ancien encore en trafic — la garantie derrière le flux preview-puis-promotion décrit plus loin.
- Le regroupement de requêtes absorbe les pics. Si
solar-packdevient viral à la publication, Vercel regroupe les requêtes concurrentes vers le même chemin en une seule invocation par région ; le webhook n'a besoin de se déclencher qu'une fois. - Les échecs restent sûrs par défaut. Si la régénération atteint un timeout ou un statut autre que
200, 301, 302, 307, 308, 404ou410, Vercel continue de servir la dernière réponse valide et retente environ 30 secondes plus tard. Un200du webhook signifie que le tag est marqué obsolète, pas que la régénération réussira du premier coup. - Les purges se propagent globalement. Une fois la régénération réussie, Vercel purge et remplace le HTML et les données associées sur toutes les régions CDN en une seule poussée atomique, typiquement en quelques centaines de millisecondes.
- Chaque déploiement possède son propre cache ISR. Ce cache n'est pas partagé avec le déploiement suivant, ce qui compte pour le retour arrière couvert plus loin.
Tester les événements valides, invalides et répétés en preview
Définissez le secret dans l'environnement de preview, configurez l'émetteur pour qu'il n'appelle que l'URL de preview, et utilisez une variable shell plutôt que de placer le secret en clair dans la commande ou l'historique shell.
curl -sS -i -X POST https://acme-shop-preview.example/api/revalidate-products \
-H "Authorization: Bearer $ACME_SHOP_REVALIDATE_SECRET" \
-H "Content-Type: application/json" \
--data '{"type":"product.published","slug":"solar-pack"}'
Attendez 200 avec exactement les tags acme-shop:catalog, acme-shop:product:solar-pack et les chemins /shop, /shop/solar-pack. Vérifiez qu'un en-tête d'autorisation absent renvoie 401, qu'un JSON malformé ou un événement inattendu renvoie 400, et qu'aucune des deux ne déclenche de revalidation. Répéter un événement valide est sûr et idempotent, mais ne réchauffe pas de manière synchrone toutes les pages. Ne journalisez jamais l'en-tête d'autorisation ni le corps du webhook.
| Validation | Preuve attendue | Corrigez si |
|---|---|---|
| Positive | 200 avec uniquement les deux tags et chemins connus. | Tag ou chemin inattendu dans la réponse. |
| Négative | Jeton absent → 401 ; événement non supporté → 400. | L'une des deux requêtes renvoie 200. |
| Échec | Un 503 côté catalogue peut renvoyer 200, mais la visite suivante doit montrer l'erreur de la route, pas une révision fabriquée. | Une fausse révision s'affiche, ou l'erreur est mise en cache comme un succès. |
Valider le timing du stale-while-revalidate
Utilisez une révision de produit publique telle que productRevision: "18" en preview. Capturez les corps de la collection et du produit avant publication, publiez la révision 19, déclenchez le webhook une seule fois et demandez chaque URL deux fois.
curl -sS https://acme-shop-preview.example/shop
curl -sS https://acme-shop-preview.example/shop/solar-pack
| Moment | Comportement attendu avec revalidateTag(tag, "max") |
|---|---|
| Avant le webhook | La révision 18 en cache peut être servie tant qu'elle reste valide. |
| Immédiatement après | Le tag est marqué obsolète ; aucun rafraîchissement global synchrone ne démarre. |
| Première visite concernée | La révision 18 peut être servie pendant que Next.js rend la 19 en arrière-plan. |
| Visite ultérieure | Le corps devrait afficher la 19. Sinon, inspectez la source, l'orthographe du tag et la correspondance de chemin. |
Ce timing privilégie la disponibilité, pas la lecture immédiate de sa propre écriture. Si l'utilisateur qui a fait une mutation doit la voir tout de suite, utilisez updateTag dans une Server Action plutôt qu'un webhook public transformé en rafraîchissement global synchrone.
Promouvoir et récupérer en toute sécurité
Après validation de la preview, ajoutez le même secret à la production sans l'exposer au code client, pointez l'émetteur vers le déploiement sain, puis déclenchez une mise à jour contrôlée pour solar-pack. Surveillez les échecs de webhook, les erreurs amont et les signalements de contenu obsolète.
Si un mauvais événement atteint la production, arrêtez d'abord l'émetteur, corrigez l'enregistrement source, restaurez le déploiement approuvé si besoin, puis livrez un événement connu et validez deux fois les corps de la collection et du produit. Ne commencez pas par une purge CDN large : elle ne corrige ni un enregistrement source erroné, ni une correspondance de tags fautive.
# Retour arrière immédiat, après validation uniquement.
vercel rollback https://acme-shop-known-good.example
Le retour arrière est rapide : le déploiement restauré réactive son propre cache ISR depuis sa dernière activité, rien n'a besoin d'être purgé. Promouvoir un nouveau déploiement est différent : il démarre à froid et n'hérite pas des entrées réchauffées du déploiement sortant — les premières requêtes vers /shop et /shop/solar-pack après une promotion régénèrent, ce n'est pas un pipeline cassé.
Traitez la revalidation comme un événement observable, pas comme un espoir
Un 200 du webhook prouve que le Route Handler a accepté l'événement — pas que le
tag de catalogue est devenu obsolète partout où il aurait dû, ni qu'un autre CDN
devant Acme Shop a servi la page rafraîchie à un visiteur réel. Une couche
d'orchestration managée comme MYO corrèle la livraison du webhook, les résultats
de revalidation et les signalements de contenu obsolète issus du trafic réel sous
un même événement, pour qu'un échec partiel apparaisse immédiatement plutôt que
comme un ticket de support sur un ancien prix.
Erreurs fréquentes
- Appeler la forme dépréciée
revalidateTag(tag)à un seul argument dans Next.js 16. - Laisser un corps de requête choisir des tags ou chemins arbitraires.
- Supposer qu'un tag invalidé réchauffe instantanément chaque route dans chaque région.
- Ne revalider qu'un chemin alors que les mêmes données apparaissent sur plusieurs pages.
- Revalider le chemin source d'une réécriture Next.js au lieu de son chemin de destination réel.
- Compter sur le regroupement de requêtes de Vercel pour protéger l'origine, au lieu de chemins littéraux réellement cacheables.
Dépannage
| Symptôme | Cause probable | Correction ciblée |
|---|---|---|
401 Unauthorized | Secret manquant, mauvais environnement, en-tête bearer malformé. | Vérifiez la variable d'environnement serveur ; ne journalisez jamais le secret. |
400 Unsupported event | Type d'événement, JSON ou slug qui viole l'allowlist. | Comparez le payload au schéma product.published et utilisez un slug sûr. |
200 mais la page affiche encore l'ancienne révision | "max" utilise délibérément le stale-while-revalidate. | Redemandez après régénération, puis vérifiez la source et les logs avant de réessayer. |
| La collection se rafraîchit mais pas la page produit (ou l'inverse) | Un tag ou un chemin littéral omis ou mal orthographié. | Inspectez les tags/paths renvoyés et corrigez la correspondance côté serveur. |
| L'événement de preview revalide mais la production reste ancienne | La revalidation est limitée à un domaine et un déploiement. | Confirmez l'URL cible de l'émetteur et redélivrez contre le déploiement voulu. |
| La régénération échoue en boucle sans erreur visible | Vercel sert du contenu obsolète et retente après 30 secondes environ. | Inspectez les logs de fonction ; un 200 du webhook ne garantit pas la régénération. |
Références faisant autorité
- Next.js
revalidateTag - Next.js
revalidatePath - Next.js
updateTag - Mise en cache et revalidation Next.js
- Vue d'ensemble du cache Vercel
- Vercel Incremental Static Regeneration
Rendez la revalidation Next.js prévisible à l'échelle
Optimi peut vous aider à concevoir des tags de cache sûrs, des webhooks d'ISR à la demande et des chemins de validation multi-fournisseurs, comme un des maillons d'un programme managé de Performance, Sécurité et Visibilité pour les workflows de publication à fort trafic.
Discuter de l'invalidation de cache