Cache de routes

Sur cette page

Le cache de routes d’Astro stocke la réponse rendue d’une page à la demande, de sorte qu’un succès de cache évite le rendu. EmDash fournit des balises de cache pour le contenu qu’une page affiche et invalide ces balises lorsque le contenu change. Les pages peuvent rester en cache pendant des jours, tandis qu’une publication devient généralement visible en quelques secondes.

Le cache d’objets stocke des résultats de requêtes et continue de rendre chaque page. Lorsqu’un fournisseur de cache est configuré, les rendus anonymes contournent le cache d’objets et lisent depuis la base de données, afin qu’un rendu qui remplit à nouveau une page invalidée ne provienne pas d’un instantané plus ancien (voir Ce qui est mis en cache). Le cache de routes sert alors les visiteurs anonymes, et le cache d’objets accélère surtout les rendus pour les éditeurs connectés.

Choisir un fournisseur de cache

Le cache de routes nécessite un fournisseur de cache. Utilisez celui qui correspond à votre déploiement :

DéploiementFournisseurImporté depuis
Node.jsmemoryCache()astro/config
CloudflarecacheCloudflare()@astrojs/cloudflare/cache

memoryCache() conserve les entrées dans la mémoire du processus serveur. cacheCloudflare() est un fournisseur CDN : il stocke les réponses dans le Cloudflare Workers Cache, et un succès de cache n’exécute jamais le Worker. Les adaptateurs Netlify et Vercel d’Astro fournissent eux aussi des fournisseurs CDN ; voir Fournisseurs de cache des adaptateurs. EmDash invalide via l’API cache.invalidate() d’Astro, indépendante du fournisseur, de sorte que ses balises fonctionnent avec n’importe quel fournisseur. Différences entre fournisseurs décrit le comportement qui dépend du fournisseur. astro dev ne met jamais rien en cache : testez donc le cache avec un build de production.

Configurer le cache de routes

Enregistrez le fournisseur et déclarez quelles routes peuvent être mises en cache, pour combien de temps et sous quelles balises. Cet exemple utilise le fournisseur Cloudflare ; sur Node, utilisez cache: { provider: memoryCache() } avec memoryCache importé depuis astro/config :

import { defineConfig } from "astro/config";
import { cacheCloudflare } from "@astrojs/cloudflare/cache";

export default defineConfig({
	// ...votre output, adaptateur et intégrations existants
	cache: {
		provider: cacheCloudflare(),
	},
	routeRules: {
		"/": { maxAge: 86_400, swr: 604_800, tags: ["posts", "pages", "all"] },
		"/blog": { maxAge: 86_400, swr: 604_800, tags: ["posts", "all"] },
		"/blog/[slug]": { maxAge: 86_400, swr: 604_800, tags: ["posts", "all"] },
		"/sitemap.xml": { maxAge: 86_400, swr: 604_800, tags: ["posts", "pages", "all"] },
		"/sitemap-[collection].xml": { maxAge: 86_400, swr: 604_800, tags: ["posts", "pages", "all"] },
	},
});
  • maxAge est la durée pendant laquelle le cache sert l’entrée avant que la route ne soit rendue à nouveau. EmDash invalide les entrées périmées à la publication (voir ci-dessous), mais une invalidation peut échouer ; maxAge borne donc aussi la durée pendant laquelle une page peut rester périmée, plus une réponse périmée pendant que swr la revalide. Consultez Différences entre fournisseurs avant de choisir une valeur élevée.
  • swr permet au cache de continuer à servir une entrée périmée pendant qu’il la revalide en arrière-plan, de sorte que les visiteurs attendent rarement un rendu une fois la page en cache.
  • tags nomment l’entrée pour l’invalidation. Balisez chaque route avec les collections qu’elle affiche, en utilisant les noms de collections : l’invalidation intégrée d’EmDash utilise les noms de collections, de sorte que les balises correspondantes sont invalidées automatiquement. Une balise supplémentaire large sur chaque route en cache (ici all, qui n’est pas un nom de collection et qu’EmDash n’invalide donc jamais de lui-même) sert d’interrupteur d’urgence : l’invalider, par exemple avec context.cache.invalidate({ tags: ["all"] }) dans un point de terminaison que vous protégez vous-même, vide tout d’un coup.

Les helpers de requête renvoient aussi un cacheHint que vous pouvez passer à Astro.cache.set() dans la page. Ses balises désignent l’entrée exacte (getEmDashEntry), la collection (getEmDashCollection) ou la ressource de chrome (getMenuWithCacheHint, getSiteSettingsWithCacheHint, getTaxonomyTermsWithCacheHint, getWidgetAreaWithCacheHint). Ce sont les mêmes balises qu’EmDash invalide lors des écritures : une page qui transmet ses hints est donc invalidée lorsqu’une entrée ou une ressource de chrome qu’elle affiche est écrite, y compris celles que sa règle de route ne nomme pas :

---
import { getEmDashEntry } from "emdash";

const { slug } = Astro.params;
if (!slug) return Astro.rewrite("/404");

const { entry, cacheHint } = await getEmDashEntry("posts", slug);
if (Astro.cache?.enabled) Astro.cache.set(cacheHint);
---

Astro.cache.set() fusionne avec la règle de route, et les balises s’accumulent d’un appel à l’autre. Les hints de cache portent des balises mais pas de maxAge : c’est donc la règle de route qui décide combien de temps une page est mise en cache.

Donnez à chaque route publique une règle explicite. Les règles correspondent au chemin de la requête ; listez donc vos routes publiques individuellement : un paramètre rest à la racine du site, comme /[...slug], correspond aussi aux routes d’administration et d’API sous /_emdash/.

L’invalidation à la publication est intégrée

Les routes de l’API de contenu d’EmDash invalident le cache de routes chaque fois que le contenu en ligne change : publier, dépublier, supprimer ou restaurer une entrée depuis la corbeille, ainsi qu’enregistrer des modifications qui atteignent l’entrée en ligne, invalident les balises [collection, id]. Créer ou dupliquer une entrée n’invalide rien, car la nouvelle entrée est un brouillon. Toute règle de route balisée avec le nom de la collection (comme ci-dessus) est donc généralement actualisée en quelques secondes après une publication, même avec un maxAge élevé.

Le chrome du site est également couvert : enregistrer les paramètres du site invalide emdash:settings, les modifications de menu invalident emdash:menu:{name}, et les écritures de taxonomies et de zones de widgets invalident leurs balises correspondantes emdash:taxonomy:{name} / emdash:widget-area:{name}. Les pages récupèrent automatiquement ces balises lorsqu’elles utilisent le cacheHint de getMenuWithCacheHint(), getSiteSettingsWithCacheHint(), getTaxonomyTermsWithCacheHint() ou getWidgetAreaWithCacheHint().

Les balises de menu ne couvrent que les modifications du menu. Publier, dépublier, mettre à la corbeille ou restaurer une entrée vers laquelle pointe un menu, ou modifier le slug d’une entrée liée ou d’un terme de taxonomie, n’invalide pas la balise du menu. Une page qui affiche le menu prend en compte un tel changement lorsque ses balises mentionnent aussi cette entrée, sa collection ou la taxonomie de ce terme, et sinon à l’expiration de la page.

Créer, modifier ou supprimer une redirection invalide la page en cache au chemin source de la redirection, avec et sans barre oblique finale, de sorte que la redirection s’applique dès la requête suivante.

Restaurer une ancienne révision, ainsi que les modifications des signatures d’auteur, de la médiathèque et des commentaires, n’invalide rien. Les pages en cache qu’elles affectent sont mises à jour une fois expirées, ou lorsque l’entrée qui les affiche est enregistrée à nouveau. La publication planifiée invalide les balises des entrées publiées uniquement sur Cloudflare ; voir Différences entre fournisseurs.

C’est aussi pourquoi la convention de balises compte : EmDash invalide les noms de collections, les identifiants d’entrées et les balises de chrome emdash:*. Si vos règles de route utilisent d’autres noms de balises, l’invalidation intégrée ne les trouvera pas et les entrées périmées subsisteront jusqu’à l’expiration de maxAge.

Garder des réponses hors du cache

Les règles de route correspondent au chemin de la requête ; elles s’appliquent donc aussi aux réponses que vous ne voulez pas mettre en cache avec un TTL élevé. EmDash garde automatiquement hors du cache de routes les réponses rendues pour un utilisateur connecté, les réponses envoyées avec Cache-Control: private ou no-store, ainsi que les réponses 404. Les autres réponses d’erreur et vos propres redirections de middleware relèvent du template, qui les exclut requête par requête avec Astro.cache.set(false).

Astro.cache.set(false) ne tient que jusqu’au prochain appel à Astro.cache.set(). Un layout est rendu après le frontmatter de la page : un layout qui définit un hint de cache réactive donc la mise en cache d’une page qui s’en était exclue. Transmettez l’exclusion au layout (par exemple comme prop) et ignorez ses appels à Astro.cache.set() lorsqu’elle est définie.

Les liens d’aperçu et de modification sont gérés automatiquement

Le middleware d’EmDash exclut lui-même ces réponses du cache de routes, aucun code de template n’est donc nécessaire : les requêtes portant un jeton ?_preview= (valide ou non, car ces URL sont propres à chaque jeton et un brouillon en cache resterait sinon accessible après l’expiration du jeton), et les réponses dans lesquelles la barre d’outils de l’éditeur est injectée. Avec toolbar: "client", il en va de même pour les URL ?_edit. Les réponses d’aperçu valides et les réponses avec barre d’outils injectée sont en outre marquées Cache-Control: private, no-store pour les navigateurs et les CDN en aval.

Réponses d’erreur

EmDash garde chaque 404 hors du cache de routes, de sorte que publier plus tard une entrée manquante l’affiche immédiatement. Les autres réponses d’erreur, comme un 500 rendu sur une route en cache, héritent du TTL de la règle. Excluez-les avec Astro.cache.set(false).

Redirections dans un middleware

Un middleware qui court-circuite avec une redirection (par exemple, la canonicalisation de l’hôte) doit s’exclure avec context.cache?.set(false). Sinon, la redirection est mise en cache avec le TTL de la règle de route, et sur un fournisseur CDN qui ne sépare pas les entrées de cache par nom d’hôte, elle peut aussi être servie aux requêtes destinées à l’hôte canonique, provoquant immédiatement une boucle de redirection. Utilisez le chaînage optionnel : context.cache est undefined pour certaines réponses, comme la page 404 d’une URL qui ne correspond à aucune route.

Éditeurs et pages en cache

Le cache de routes stocke une variante partagée par URL, et sa clé de cache ignore les cookies : un succès de cache ne reflète donc jamais la session d’un éditeur. Avec la barre d’outils rendue côté serveur par défaut, un éditeur qui parcourt le site public reçoit généralement la variante anonyme en cache, sans la barre d’outils, et le cookie emdash-edit-mode n’a aucun effet sur une URL en cache. Une page rendue pour un éditeur connecté n’est jamais stockée dans le cache partagé ; c’est le visiteur anonyme suivant qui le remplit.

Définissez toolbar: "client" pour les sites qui utilisent le cache de routes. Un petit script affiche alors le bouton de modification aux éditeurs connectés sur les pages en cache, et la vue d’édition se charge depuis une URL ?_edit toujours rendue à neuf. Les liens d’aperçu contournent le cache dans les deux modes.

Différences entre fournisseurs

Node.js avec memoryCache()

  • Chaque processus serveur conserve son propre cache, et une invalidation ne vide que le processus qui a traité l’écriture. Si vous exécutez plusieurs processus ou instances, les autres continuent de servir leurs pages en cache jusqu’à l’expiration de maxAge.
  • Un redémarrage vide le cache.
  • memoryCache() ne stocke qu’une réponse dotée d’un maxAge : une route sans règle n’est donc jamais mise en cache, même si la page appelle Astro.cache.set() avec un hint de cache.
  • La publication planifiée n’invalide pas le cache de routes sur Node. Une page qui liste une entrée planifiée ne l’affiche qu’une fois sa copie en cache expirée ; gardez donc un maxAge court sur les routes qui listent du contenu planifié.

Cloudflare avec cacheCloudflare()

  • Le fournisseur écrit les en-têtes de réponse Cloudflare-CDN-Cache-Control et Cache-Tag. Le Workers Cache consomme les deux ; les clients ne les voient jamais, et tout en-tête Cache-Control que vos pages définissent pour les navigateurs reste inchangé.
  • Lorsqu’un fournisseur de cache est configuré, l’adaptateur @astrojs/cloudflare active automatiquement le Workers Cache : le build écrit "cache": { "enabled": true } dans la configuration Wrangler générée que wrangler deploy récupère.
  • Une route sans règle est marquée no-store, mais seulement si rien n’appelle Astro.cache.set() pendant son rendu. Un hint de cache sans maxAge, comme ceux de getSiteSettingsWithCacheHint() et getMenuWithCacheHint(), produit Cloudflare-CDN-Cache-Control: public sans durée de vie : le Workers Cache se rabat alors sur ses durées de cache par défaut (2 heures pour une réponse 200). Si votre layout définit ces hints, chaque page qui utilise ce layout devient mettable en cache, avec ou sans règle.
  • Par défaut, la version du Worker fait partie de la clé de cache : chaque déploiement démarre donc avec un cache vide, et les premières requêtes suivantes sont rendues dans le Worker. Pour conserver les entrées d’un déploiement à l’autre, définissez "cache": { "enabled": true, "cross_version_cache": true } dans votre configuration Wrangler (voir Cache entre versions). Les modifications de template ne s’affichent alors qu’une fois les entrées concernées expirées ou invalidées.
  • La publication planifiée invalide les balises des entrées qu’elle publie depuis le Cron Trigger.

Limites de débit de purge

Le fournisseur Cloudflare invalide en purgeant le Workers Cache. Les purges du Workers Cache utilisent toujours les limites de purge du plan Free de Cloudflare, quel que soit le plan de votre compte : 5 requêtes par minute avec une rafale de 25, par compte (voir Limites de débit de purge). La plupart des écritures de contenu coûtent chacune une requête de purge : les enregistrements qui modifient le contenu en ligne (y compris les enregistrements automatiques dans les collections qui enregistrent directement dans l’entrée en ligne), la publication, la dépublication, la suppression, la restauration, la planification et l’attribution de termes de taxonomie à une entrée, ainsi que chaque modification de paramètres, de menu, de taxonomie ou de widget. L’écriture d’une redirection coûte une requête de purge par variante du chemin source, donc jusqu’à quatre lorsque la source d’une redirection change.

Une purge limitée en débit échoue sans erreur dans l’administration, et les pages concernées conservent leur version en cache jusqu’à l’expiration de maxAge. Lors de modifications en masse, ou lorsque plusieurs éditeurs travaillent en même temps, choisissez un maxAge que vous pouvez accepter comme pire cas de contenu périmé.

Le cache du navigateur est distinct

Le cache de routes peut se permettre des TTL élevés parce qu’EmDash l’invalide ; les caches des navigateurs ne sont pas invalidés. Les hints d’EmDash fournissent des tags de purge, pas des validateurs Last-Modified automatiques ni l’en-tête implicite Cache-Control: no-cache d’Astro. La date de modification d’une seule entrée ne couvre ni les réglages, ni les listes associées, ni les entrées supprimées ; l’utiliser comme validateur de page peut donc conserver un corps obsolète via des réponses 304 conditionnelles.

Définissez la mise en cache du navigateur avec Astro.response.headers.set("Cache-Control", ...) : utilisez no-cache pour une vérification fraîche à chaque visite, ou une durée de vie courte (des minutes, pas des jours). Pour activer les requêtes conditionnelles, fournissez votre propre hint lastModified dont l’horodatage avance chaque fois qu’une dépendance de la réponse change.

Pour la date de modification affichée d’une entrée, utilisez entry.data.updatedAt plutôt que cacheHint.lastModified, qu’EmDash ne renseigne pas automatiquement.