---
title: "Cache Next.js sur Vercel : couches de cache et réglages sûrs"
description: "Un guide pratique du cache Next.js sur Vercel : mettre en cache le catalogue public et les lectures en base d'Acme Shop grâce à des limites de cache et des valeurs par défaut de cache sûres, sans jamais exposer de réponse personnalisée dans un cache partagé."
canonical_url: https://optimi.com/fr/guides/nextjs-vercel-cache-model
md_url: https://optimi.com/fr/guides/nextjs-vercel-cache-model.md
last_updated: 2026-07-15
---

# Cache Next.js sur Vercel : des valeurs par défaut sûres pour le contenu public

Mettez en cache une route publique du catalogue d'Acme Shop de manière délibérée, vérifiez chaque couche, et gardez toute réponse personnalisée hors du stockage partagé.

La mise en cache sur Vercel n'est pas un unique interrupteur. Une application Next.js peut mettre en cache un fetch côté serveur, prérendre la sortie d'une route, et laisser le CDN de Vercel mettre en cache une réponse HTTP complète : ce sont des couches distinctes, aux comportements d'invalidation distincts. Ce guide s'appuie sur le catalogue public d'Acme Shop ; il ne met jamais en cache ses pages de compte, son panier, son tunnel de paiement, ni ses réponses d'API authentifiées.

À l'échelle où fonctionne une couche d'orchestration d'edge managée, une directive de cache mal appliquée n'est jamais un simple bug local : c'est une politique qui continue de s'appliquer silencieusement à chaque visiteur, dans chaque région, jusqu'à ce que quelqu'un le remarque. C'est pourquoi les limites de cache se décident route par route, avant même d'écrire un en-tête ou un tag.

> **Un cache partagé exige une sortie publique**
>
> Un cache partagé peut servir une même réponse stockée à de nombreux visiteurs.
>   N'ajoutez jamais de politique de cache partagé à une sortie qui varie selon la
>   session, l'autorisation, un cookie, un panier, un droit ou l'état d'un autre
>   utilisateur. Gardez ces données dynamiques ou explicitement privées.

## Ce que vous allez mettre en place

Ce guide met en cache la liste publique `/shop` d'Acme Shop pendant cinq minutes dans le cache de données Next.js, et distingue une réponse JSON délibérément publique mise en cache par le CDN de Vercel. Il utilise les options explicites de `fetch`, ce qui permet de l'utiliser sans activer les Cache Components de Next.js.

**Résultat attendu :** `/shop` utilise une politique de cache de données de cinq minutes, tandis qu'un endpoint JSON délibérément public reçoit une politique CDN Vercel courte et séparée ; vous pourrez prouver que la route publique est cacheable et que la route de compte ne l'est pas.

**Préalables :** un projet Next.js 16 App Router sans Cache Components, une preview Vercel liée, un catalogue amont public, et une révision inoffensive telle que `catalogRevision: "2026-07-14T10:00Z"`. Vérifiez toujours sur la preview avant la production.

| Couche | Ce qu'elle stocke | Politique de départ |
| --- | --- | --- |
| Cache de données Next.js | Le résultat d'un `fetch` côté serveur ou d'une lecture `unstable_cache` | Revalidation toutes les 300 secondes |
| Sortie de route Next.js | La page rendue ou le résultat d'un Route Handler | Laisser les besoins de données de la route décider |
| CDN Vercel | Une réponse HTTP publique complète | TTL partagé court et revalidation en arrière-plan |
| Navigateur | La réponse locale d'un visiteur | Séparer du TTL CDN partagé |

## Le cache Next.js sur Vercel commence par une frontière claire

Le catalogue est identique pour tous les visiteurs et peut avoir jusqu'à cinq minutes de retard ; un résumé de compte ne l'est pas, même si deux visiteurs demandent la même URL. Notez ces frontières route par route avant d'écrire un en-tête ou un tag : « cette réponse peut-elle être partagée ? » est une question différente de « ces données sont-elles lentes à récupérer ? », et seule la première décide si un cache partagé est sûr. Le CDN Vercel peut mettre en cache la réponse complète de `GET /api/public-catalog` ; le cache de données Next.js conserve séparément le fetch serveur tagué `acme-shop:catalog` ; `/account` suit un fetch `no-store` qui n'entre jamais dans un cache partagé. Un `HIT` sur l'une de ces couches ne prouve pas un hit sur l'autre.

**Figure 1. Les couches de cache d'Acme Shop et la frontière de sûreté**

1. Visiteur public — Demande la représentation publique du catalogue.
2. CDN Vercel — Peut mettre en cache la réponse complète de GET /api/public-catalog.
3. Cache de données Next.js — Conserve le fetch serveur du catalogue avec le tag acme-shop:catalog.
4. API catalogue d'Acme Shop — Reste la source publique faisant autorité pour les produits.

*Un visiteur authentifié passe par /account via un fetch no-store vers l'API de compte ; cette réponse n'entre jamais dans un cache partagé.*

**Les requêtes du catalogue public et du compte privé suivent des chemins de cache différents**

![Un diagramme de séquence montrant un visiteur qui demande le catalogue public via le CDN Vercel, une route Next.js et le cache de données tagué avant que l'API catalogue ne soit appelée en cas de défaut de cache de données. Une requête de compte authentifiée passe au contraire directement par un fetch no-store vers l'API de compte et renvoie une réponse propre au visiteur.](/diagrams/nextjs-vercel-cache-model/public-and-private-request-paths.svg)

*Le cache de réponses publiques et le cache de données côté serveur sont distincts ; les données de compte contournent ces deux chemins partagés.*

## Étape 1 : vérifier qu'une route est sûre à mettre en cache

Notez la première URL à mettre en cache et répondez à ces questions avant de modifier le code :

1. La sortie est-elle identique pour tous les visiteurs qui peuvent la demander ?
2. Évite-t-elle `Set-Cookie`, l'autorisation, les lectures de session et les paramètres de requête spécifiques à un utilisateur ?
3. Un visiteur peut-il tolérer jusqu'à cinq minutes de données de catalogue anciennes ?
4. Le responsable applicatif peut-il nommer l'événement qui devra rafraîchir le contenu plus tôt ?

Pour ce guide, utilisez la liste publique `/shop` d'Acme Shop. Gardez `/account`, `/cart`, `/checkout` et toute route API authentifiée hors du périmètre.

## Étape 2 : mettre en cache le fetch du catalogue public de manière explicite

Next.js 16 ne met pas les requêtes `fetch` en cache par défaut dans ce modèle. Rendez explicites la persistance, la fraîcheur et la cible d'invalidation prévues pour Acme Shop. `force-cache` autorise un stockage persistant ; `revalidate` limite sa durée de vie ; le tag reste réservé à une invalidation étroite à la demande.

```ts
// lib/acme-shop/catalog.ts
export type Product = { slug: string; name: string; priceCents: number }

export async function getPublicCatalog(): Promise<Product[]> {
  const response = await fetch("https://catalog.acme-shop.example/v1/products", {
    cache: "force-cache",
    next: { revalidate: 300, tags: ["acme-shop:catalog"] },
  })

  if (!response.ok) throw new Error("Public catalog is unavailable")
  return response.json()
}
```

```tsx
// app/shop/page.tsx
import { getPublicCatalog } from "@/lib/acme-shop/catalog"

export default async function ShopPage() {
  const products = await getPublicCatalog()

  return (
    <ul>
      {products.map((product) => (
        <li key={product.slug}>{product.name}</li>
      ))}
    </ul>
  )
}
```

`revalidate: 300` est une durée de vie maximale, pas une garantie que chaque région se réchauffe au même instant. La plus petite valeur de revalidation utilisée par une route peut aussi abaisser la fréquence de revalidation de cette route entière.

Ne supposez pas que l'absence de `cache: "force-cache"` signifie « toujours en direct ». Sans option explicite, le fetch se relance à chaque requête une fois qu'une API de requête (`cookies()`, `headers()`, `searchParams`) rend la route dynamique ; mais sur une route où aucune de ces API n'est atteinte, il ne s'exécute qu'une seule fois, au moment de `next build`, et son résultat est réutilisé indéfiniment plutôt que rafraîchi selon un planning. Le `revalidate: 300` explicite d'Acme Shop évite cette ambiguïté : le fetch se rafraîchit selon un rythme connu, que `/shop` reste statique ou non.

## Étape 3 : définir des valeurs par défaut de cache sûres pour les lectures hors fetch

Toutes les lectures ne passent pas par `fetch`. La boutique d'Acme Shop interroge aussi directement une base de données pour le badge de stock faible affiché sur `/shop`. [`unstable_cache`](https://nextjs.org/docs/app/api-reference/functions/unstable_cache) donne à cette requête le même cycle de vie borné dans le temps et piloté par tag que le fetch du catalogue, afin que les deux chemins partagent un seul jeu de valeurs par défaut de cache sûres plutôt que de laisser la requête s'exécuter à chaque appel sans limite.

```ts
// lib/acme-shop/inventory.ts
import { unstable_cache } from "next/cache"
import { db } from "@/lib/acme-shop/db"

export const getLowStockBadges = unstable_cache(
  async () => db.query.products.findMany({ columns: { slug: true, stockCount: true } }),
  ["acme-shop:low-stock-badges"],
  { revalidate: 300, tags: ["acme-shop:catalog"] }
)
```

Réutiliser le tag `acme-shop:catalog` signifie qu'un seul événement de publication invalide ensemble la liste de produits et le badge de stock ; voir le [guide de revalidation](/fr/guides/nextjs-vercel-revalidation) pour cet événement. Donnez à `unstable_cache` un tableau de clé explicite : sa clé dérive sinon des arguments de la fonction, et la même requête appelée avec un filtre différent devient silencieusement une entrée séparée avec sa propre horloge.

Par ailleurs, des appels `fetch` `GET` identiques effectués pendant un seul passage de rendu sont automatiquement dédupliqués : appeler le même fetch depuis deux composants ne coûte qu'une seule requête. Cette mémoïsation se réinitialise à la requête suivante, n'a aucun lien avec `revalidate`, et ne s'applique pas dans les Route Handlers.

## Étape 4 : maintenir les données privées hors du chemin partagé

N'essayez pas de faire d'un token de session une partie d'une clé de cache partagée. Acme Shop récupère les données de compte du visiteur courant à chaque requête.

```ts
// lib/acme-shop/account.ts
export async function getAccount(accessToken: string) {
  const response = await fetch("https://accounts.acme-shop.example/v1/me", {
    cache: "no-store",
    headers: { Authorization: `Bearer ${accessToken}` },
  })

  if (!response.ok) throw new Error("Account is unavailable")
  return response.json()
}
```

Lire `cookies()` ou `headers()` ne transforme pas une réponse privée en représentation partagée sûre. Alignez la récupération des données, le comportement de la page et les en-têtes HTTP avec l'exigence de confidentialité.

## Étape 5 : mettre une réponse HTTP publique en cache au CDN

Le cache de données ne rend pas automatiquement une réponse HTTP entière cacheable par le CDN. Pour la seule API catalogue publique d'Acme Shop, renvoyez une représentation complète avec un TTL spécifique à Vercel et un tag CDN : le navigateur reçoit la politique navigateur courte, tandis que Vercel consomme `Vercel-CDN-Cache-Control` et retire `Vercel-Cache-Tag` avant de transmettre la réponse en aval.

```ts
// app/api/public-catalog/route.ts
import { getPublicCatalog } from "@/lib/acme-shop/catalog"

export async function GET() {
  const products = await getPublicCatalog()

  return Response.json(
    { catalogRevision: "2026-07-14T10:00Z", products },
    {
      headers: {
        "Cache-Control": "public, max-age=0, must-revalidate",
        "Vercel-CDN-Cache-Control": "public, s-maxage=60, stale-while-revalidate=300",
        "Vercel-Cache-Tag": "acme-shop:catalog-response",
      },
    }
  )
}
```

N'ajoutez jamais ces en-têtes si la requête porte un `Authorization`, si la réponse pose un cookie, si la sortie varie selon le visiteur, ou si le corps contient des données sensibles. Les critères de cacheabilité de Vercel sont plus étroits qu'« ajouter un en-tête » : `GET`/`HEAD` uniquement, aucun `Range` ni `Authorization` en requête, statut `200`, `404`, `410`, `301`, `302`, `307` ou `308`, corps de moins de 10 Mo (20 en streaming), et aucun `Set-Cookie`, `private`, `no-cache`, `no-store` ni `Vary: *`. Un seul échec disqualifie la réponse, quel que soit le TTL fixé.

Pour une politique partagée avec un autre CDN ou WAF, utilisez `CDN-Cache-Control` plutôt que `Vercel-CDN-Cache-Control` : celui-ci est consommé au proxy de Vercel et n'en sort jamais.

> **La mise en cache multi-fournisseurs a besoin d'une seule source de vérité**
>
> Chaque fournisseur présent sur ce chemin rapporte son propre taux de hit et son
>   propre TTL pour la même URL, et aucun d'entre eux, isolément, ne montre ce qu'un
>   visiteur a réellement reçu. Une couche d'orchestration managée comme MYO réconcilie
>   l'état `x-vercel-cache` de Vercel avec celui de chaque autre fournisseur sous une
>   seule requête, afin qu'une décision prise ici n'entre jamais silencieusement en
>   conflit avec une politique fixée ailleurs dans la chaîne.

Pour la résilience, `stale-if-error` peut accompagner `s-maxage` : `stale-if-error=3600` continue de servir le dernier catalogue valide pendant jusqu'à une heure si l'API amont commence à renvoyer des erreurs. Vercel plafonne ces directives à un an ; la mise en cache reste du meilleur effort par région, si bien qu'une route rarement demandée peut être évincée avant la fin de son TTL.

## Étape 6 : valider les comportements public, privé et d'échec

Déployez le changement examiné vers une URL de preview. `vercel curl` gère automatiquement le contournement de la protection de déploiement sur une preview protégée ; c'est actuellement une commande CLI en beta. Pour une preview publique, un simple `curl` suffit.

```bash
vercel curl /api/public-catalog --deployment https://acme-shop-preview.example
vercel curl /api/public-catalog --deployment https://acme-shop-preview.example
vercel httpstat /api/public-catalog --deployment https://acme-shop-preview.example
```

| Validation | Preuve attendue | Arrêter si |
| --- | --- | --- |
| Route publique positive | Deux réponses `GET /api/public-catalog` portent la révision attendue ; la seconde peut annoncer `HIT` ou `STALE`. | Le corps contient un champ de compte, un cookie ou une valeur spécifique à un visiteur. |
| Route privée négative | Une réponse `/account` authentifiée est fraîche pour l'utilisateur connecté et sans politique `s-maxage` partagée. | Deux comptes de test reçoivent le marqueur ou la réponse l'un de l'autre. |
| Comportement d'échec | Une API amont de test hors production renvoie `503` ; la route rapporte l'erreur attendue sans corps fabriqué. | Un échec amont est stocké silencieusement comme une réponse réussie. |

Le CDN de Vercel est régional : un résultat chaud depuis une localisation ne prouve pas que chaque région est chaude. Conservez l'URL, l'heure, les en-têtes, la révision du corps et la localisation de la requête avec l'enregistrement de changement.

Après vérification de la preview, déployez le changement examiné en production :

```bash
vercel deploy --prod
```

## Étape 7 : mesurer le bon résultat

Suivez plus qu'un en-tête de statut de cache. Pour la route modifiée, comparez :

- la latence de requête sur l'URL exposée aux visiteurs ;
- le volume de requêtes vers l'origine ou l'API amont ;
- le taux d'erreur et la justesse des réponses ;
- les signalements de contenu obsolète après une mise à jour du catalogue ; et
- le taux de requêtes dynamiques qui ne doivent jamais être partagées.

Testez un trafic représentatif sur plusieurs régions et conservez un petit objet de test public pour les futurs contrôles.

## Reprise sans purge large

Si une réponse publique est incorrecte, retirez d'abord l'éligibilité au cache non sûre ou restaurez le déploiement connu comme bon, puis vérifiez le corps et un parcours authentifié. Ce n'est qu'une fois la réponse corrigée que vous invalidez le tag CDN public étroit, via une session liée au projet et examinée :

```bash
vercel cache invalidate --tag acme-shop:catalog-response
```

`vercel cache invalidate --tag` marque la réponse comme obsolète : la requête suivante la sert encore pendant qu'une copie fraîche se charge en arrière-plan. `vercel cache dangerously-delete --tag` la supprime purement et simplement, si bien que la requête suivante reçoit un `MISS` et bloque sur l'origine ; réservez cette commande à une réponse qui ne doit plus jamais être servie.

Pour une valeur incorrecte du cache de données Next.js, utilisez le workflow de revalidation applicative authentifié du [guide de revalidation](/fr/guides/nextjs-vercel-revalidation) ; une invalidation CDN ne corrige ni une donnée source erronée ni une conception de route non sûre. Évitez `vercel cache purge` comme première réponse : elle vide le CDN et le cache de données pour l'ensemble du projet et peut augmenter la charge d'origine sur chaque route à la fois.

## Diagnostic rapide

| Symptôme | Cause probable | Contrôle étroit ou reprise |
| --- | --- | --- |
| `x-vercel-cache: MISS` sur une page dynamique | Cet en-tête décrit la réponse CDN, pas le fetch côté serveur. | Ajoutez une révision inoffensive au corps avant de changer un TTL. |
| Le `Cache-Control` du navigateur ne porte pas `s-maxage` | Vercel consomme ses directives propres avant de transmettre la réponse. | Inspectez `x-vercel-cache` et le corps, pas le seul en-tête navigateur. |
| Le catalogue ne devient jamais cacheable | La réponse porte `Set-Cookie`, `Authorization`, `private`, `no-store`, `Vary: *`, ou est trop volumineuse. | Retirez l'éligibilité partagée ou gardez la route dynamique. |
| Une mise à jour produit reste ancienne | Le repli de cinq minutes n'a pas expiré ou l'événement n'a pas revalidé le tag. | Confirmez la source, puis invoquez une fois le webhook authentifié. |
| Le badge de stock faible et la liste de produits divergent | `unstable_cache` a utilisé une clé ou un tag différent du fetch. | Alignez les deux sur le tag `acme-shop:catalog` et une clé explicite. |

## Avant d'élargir la politique

- Chaque route cacheable a un responsable de données publiques et une cible de fraîcheur documentés.
- Les réponses privées et authentifiées utilisent `no-store` ou une conception privée équivalente.
- Chaque réponse partagée dispose d'un chemin d'invalidation testé et d'un tag cohérent entre `fetch` et `unstable_cache`.
- Les contrôles d'en-tête incluent le corps de la réponse ou un checksum, pas seulement un statut de cache.
- Chaque réponse partagée respecte les critères de cacheabilité de Vercel : méthode, statut, taille et absence de cookie.
- La vérification de preview précède le déploiement de production.

## Références faisant autorité

- [Mise en cache et revalidation Next.js](https://nextjs.org/docs/app/guides/caching-without-cache-components)
- [Référence de l'API `fetch` Next.js](https://nextjs.org/docs/app/api-reference/functions/fetch)
- [Référence de l'API `unstable_cache` Next.js](https://nextjs.org/docs/app/api-reference/functions/unstable_cache)
- [Vercel CDN Cache](https://vercel.com/docs/caching/cdn-cache)
- [En-têtes Cache-Control Vercel](https://vercel.com/docs/caching/cache-control-headers)
- [Vercel CLI : `vercel cache`](https://vercel.com/docs/cli/cache)

[Discuter de l'architecture de cache](/fr/contact): Faites du cache Next.js sur Vercel une discipline d'edge managée — Optimi orchestre le cache Next.js sur Vercel avec chaque autre fournisseur de votre chaîne : des valeurs par défaut de cache sûres, une invalidation cohérente, et la visibilité MYO sur la Performance, la Sécurité et la Visibilité de chaque réponse mise en cache.
