Éviter les requêtes réseau inutiles avec le cache HTTP

Ilya Grigorik

La récupération de ressources via le réseau est à la fois lente et coûteuse:

  • Les réponses volumineuses nécessitent de nombreux allers-retours entre le navigateur et le serveur.
  • Votre page ne se charge pas tant que toutes ses ressources critiques n'ont pas été téléchargées entièrement.
  • Si une personne accède à votre site avec un forfait de données mobiles limité, chaque requête réseau inutile représente un gaspillage d'argent.

Comment éviter les requêtes réseau inutiles ? Le cache HTTP du navigateur est votre première ligne de défense. Il ne s'agit pas nécessairement de l'approche la plus puissante ou la plus flexible. De plus, vous avez un contrôle limité sur la durée de vie des réponses mises en cache, mais elle est efficace, compatible avec tous les navigateurs et ne nécessite pas beaucoup de travail.

Ce guide vous présente les principes de base d'une implémentation efficace de la mise en cache HTTP.

Compatibilité du navigateur

Il n'y a pas réellement une seule API appelée le cache HTTP. C'est le nom général d'un ensemble d'API de plateforme Web. Ces API sont compatibles avec tous les navigateurs:

Fonctionnement du cache HTTP

Toutes les requêtes HTTP effectuées par le navigateur sont d'abord acheminées vers le cache du navigateur pour vérifier s'il existe une réponse mise en cache valide pouvant être utilisée pour traiter la requête. En cas de correspondance, la réponse est lue à partir du cache, ce qui élimine à la fois la latence du réseau et les coûts de données induits par le transfert.

Le comportement du cache HTTP est contrôlé par une combinaison d'en-têtes de requête et d'en-têtes de réponse. Dans un scénario idéal, vous contrôlerez à la fois le code de votre application Web (qui déterminera les en-têtes de requête) et la configuration de votre serveur Web (qui déterminera les en-têtes de réponse).

Consultez l'article Mise en cache HTTP de MDN pour une présentation conceptuelle plus détaillée.

En-têtes de requête: conservez (généralement) les valeurs par défaut

Bien qu'un certain nombre d'en-têtes importants doivent être inclus dans les requêtes sortantes de votre application Web, le navigateur se charge presque toujours de les définir à votre place lorsqu'il envoie des requêtes. Les en-têtes de requête qui affectent la vérification de l'actualisation, tels que If-None-Match et If-Modified-Since, s'affichent seulement en fonction de la compréhension par le navigateur des valeurs actuelles dans le cache HTTP.

Bonne nouvelle : vous pouvez continuer à inclure des balises telles que <img src="my-image.png"> dans votre code HTML, et le navigateur se charge automatiquement de la mise en cache HTTP, sans effort supplémentaire de votre part.

En-têtes de réponse: configurer votre serveur Web

La partie de la configuration de la mise en cache HTTP qui importe le plus est les en-têtes que votre serveur Web ajoute à chaque réponse sortante. Les en-têtes suivants sont tous pris en compte dans le comportement efficace de la mise en cache:

  • Cache-Control : le serveur peut renvoyer une instruction Cache-Control pour spécifier comment et pendant combien de temps le navigateur et les autres caches intermédiaires doivent mettre en cache la réponse individuelle.
  • ETag. Lorsque le navigateur trouve une réponse mise en cache arrivée à expiration, il peut envoyer un petit jeton (généralement un hachage du contenu du fichier) au serveur pour vérifier si le fichier a été modifié. Si le serveur renvoie le même jeton, le fichier reste le même. Il n'est donc pas nécessaire de le télécharger à nouveau.
  • Last-Modified : cet en-tête remplit le même objectif que ETag, mais il utilise une stratégie basée sur le temps pour déterminer si une ressource a été modifiée, par opposition à la stratégie basée sur le contenu de ETag.

Certains serveurs Web permettent de définir ces en-têtes par défaut, tandis que d'autres les omettent complètement, sauf si vous les configurez explicitement. Les détails spécifiques de la configuration des en-têtes varient considérablement selon le serveur Web que vous utilisez. Pour obtenir des informations plus précises, nous vous recommandons de consulter la documentation de votre serveur.

Pour vous éviter des recherches, voici les instructions de configuration de quelques serveurs Web courants:

Le fait d'omettre l'en-tête de réponse Cache-Control ne désactive pas la mise en cache HTTP. Au lieu de cela, les navigateurs devinent efficacement le type de comportement de mise en cache le plus logique pour un type de contenu donné. Vous souhaitez sûrement bénéficier d'un contrôle accru, alors prenez le temps de configurer vos en-têtes de réponse.

Quelles valeurs d'en-tête de réponse devez-vous utiliser ?

Deux scénarios importants sont à prendre en compte lors de la configuration des en-têtes de réponse de votre serveur Web.

Mise en cache de longue durée pour les URL avec versions gérées

En quoi les URL avec versions gérées peuvent vous aider dans votre stratégie de mise en cache
Les URL avec versions gérées sont recommandées, car elles facilitent l'invalidation des réponses mises en cache.

Supposons que votre serveur demande aux navigateurs de mettre en cache un fichier CSS pendant un an (Cache-Control: max-age=31536000), mais que votre graphiste vient d'effectuer une mise à jour d'urgence que vous devez déployer immédiatement. Comment demandez-vous aux navigateurs de mettre à jour la copie mise en cache "obsolète" du fichier ? Vous ne pouvez pas le faire, du moins pas sans modifier l'URL de la ressource. Une fois que le navigateur a mis en cache la réponse, la version mise en cache est utilisée jusqu'à ce qu'elle ne soit plus à jour, comme déterminé par max-age ou expires, ou jusqu'à ce qu'elle soit supprimée du cache pour une autre raison (par exemple, lorsque l'utilisateur vide le cache de son navigateur). Par conséquent, différents utilisateurs peuvent se retrouver avec des versions différentes du fichier lors de la construction de la page: ceux qui viennent de récupérer la ressource utilisent la nouvelle version, tandis que ceux qui ont mis en cache une copie antérieure (mais toujours valide) utilisent une ancienne version de sa réponse. Comment tirer le meilleur de ces deux mondes: mise en cache côté client et mises à jour rapides ? Vous modifiez l'URL de la ressource et forcez l'utilisateur à télécharger la nouvelle réponse chaque fois que son contenu change. Pour ce faire, vous devez généralement intégrer une empreinte du fichier ou un numéro de version dans son nom de fichier (par exemple, style.x234dff.css).

Lorsque vous répondez à des requêtes pour des URL contenant "fingerprint" ou des informations de gestion des versions, et dont le contenu n'est jamais destiné à changer, ajoutez Cache-Control: max-age=31536000 à vos réponses.

Définir cette valeur indique au navigateur que lorsqu'il doit charger la même URL à tout moment au cours de l'année suivante (31 536 000 secondes, la valeur maximale acceptée), il peut immédiatement utiliser la valeur présente dans le cache HTTP, sans avoir à envoyer de requête réseau à votre serveur Web. C'est super ! Vous avez immédiatement gagné en fiabilité et en rapidité en évitant le réseau.

Des outils de création tels que webpack peuvent automatiser le processus d'attribution des empreintes de hachage aux URL de vos éléments.

Nouvelle validation du serveur pour les URL sans version

Malheureusement, les versions des URL que vous chargez ne sont pas toutes gérées par version. Vous ne pouvez peut-être pas inclure une étape de compilation avant de déployer votre application Web. Vous ne pouvez donc pas ajouter de hachage aux URL d'éléments. De plus, chaque application Web a besoin de fichiers HTML. Ces fichiers n'incluront (presque) jamais d'informations de gestion des versions, car personne n'utilisera votre application Web s'il a besoin de se rappeler que l'URL à consulter est https://example.com/index.34def12.html. Que pouvez-vous donc faire pour ces URL ?

Dans ce scénario, vous devez admettre la défaite. La mise en cache HTTP seule n'est pas assez puissante pour éviter complètement le réseau. (Ne vous inquiétez pas, vous allez bientôt découvrir les service workers, qui nous fourniront l'aide dont nous avons besoin pour faire revenir la bataille en votre faveur.) Toutefois, vous pouvez prendre quelques mesures pour vous assurer que les requêtes réseau sont aussi rapides et efficaces que possible.

Les valeurs Cache-Control suivantes peuvent vous aider à définir où et comment les URL sans versions sont mises en cache:

  • no-cache. Cela indique au navigateur qu'il doit procéder à une nouvelle validation auprès du serveur à chaque fois avant d'utiliser une version mise en cache de l'URL.
  • no-store. Cela indique au navigateur et aux autres caches intermédiaires (tels que les CDN) de ne jamais stocker de version du fichier.
  • private. Les navigateurs peuvent mettre en cache le fichier, mais pas les caches intermédiaires.
  • public : la réponse peut être stockée par n'importe quel cache.

Consultez l'annexe: organigramme Cache-Control pour visualiser le processus de sélection des valeurs Cache-Control à utiliser. Notez également que Cache-Control peut accepter une liste d'instructions séparées par une virgule. Consultez l'annexe: exemples Cache-Control.

Parallèlement, la définition de l'un des deux en-têtes de réponse supplémentaires peut également être utile : ETag ou Last-Modified. Comme indiqué dans la section En-têtes de réponse, ETag et Last-Modified ont tous deux la même fonction: déterminer si le navigateur doit télécharger à nouveau un fichier mis en cache qui a expiré. Nous vous recommandons d'utiliser l'approche ETag, car elle est plus précise.

Exemple d'ETag

Supposons que 120 secondes se soient écoulées depuis la récupération initiale et que le navigateur ait lancé une nouvelle requête pour la même ressource. Tout d'abord, le navigateur vérifie le cache HTTP et trouve la réponse précédente. Malheureusement, le navigateur ne peut pas utiliser la réponse précédente, car elle a expiré. À ce stade, le navigateur peut envoyer une nouvelle requête et récupérer la nouvelle réponse complète. Cependant, cette approche est inefficace, car si la ressource n'a pas changé, il n'y a aucune raison de télécharger les informations déjà présentes dans le cache. C'est le problème que les jetons de validation, comme spécifié dans l'en-tête ETag, sont conçus pour résoudre. Le serveur génère et renvoie un jeton arbitraire, qui est généralement un hachage ou une autre empreinte du contenu du fichier. Le navigateur n'a pas besoin de savoir comment l'empreinte est générée. Il lui suffit de l'envoyer au serveur lors de la prochaine requête. Si l'empreinte reste la même, cela signifie que la ressource n'a pas changé et que le navigateur peut ignorer le téléchargement.

En définissant ETag ou Last-Modified, vous rendez la requête de revalidation beaucoup plus efficace. Ils finissent par déclencher les en-têtes de requête If-Modified-Since ou If-None-Match mentionnés dans En-têtes de requête.

Lorsqu'un serveur Web correctement configuré voit ces en-têtes de requête entrants, il peut confirmer si la version de la ressource que le navigateur possède déjà dans son cache HTTP correspond à la dernière version disponible sur le serveur Web. En cas de correspondance, le serveur peut répondre par une réponse HTTP 304 Not Modified, ce qui équivaut à "Hey, continue à utiliser ce que tu as déjà !". Ce type de réponse implique très peu de données à transférer. Il est donc généralement beaucoup plus rapide que de renvoyer une copie de la ressource demandée.

Schéma d&#39;un client demandant une ressource et réponse du serveur avec un en-tête 304.
Le navigateur demande /file au serveur et inclut l'en-tête If-None-Match pour indiquer au serveur de ne renvoyer le fichier complet que si l'ETag du fichier sur le serveur ne correspond pas à la valeur If-None-Match du navigateur. Dans ce cas, les deux valeurs correspondaient. Le serveur renvoie donc une réponse 304 Not Modified avec des instructions indiquant la durée de mise en cache du fichier (Cache-Control: max-age=120).

Résumé

Le cache HTTP est un moyen efficace d'améliorer les performances de chargement, car il réduit les requêtes réseau inutiles. Il est compatible avec tous les navigateurs et demande peu de travail de configuration.

Les configurations Cache-Control suivantes constituent un bon début:

  • Cache-Control: no-cache pour les ressources qui doivent être revalidées avec le serveur avant chaque utilisation.
  • Cache-Control: no-store pour les ressources qui ne doivent jamais être mises en cache.
  • Cache-Control: max-age=31536000 pour les ressources avec versions gérées.

L'en-tête ETag ou Last-Modified peut vous aider à revalider plus efficacement les ressources de cache expirées.

En savoir plus

Si vous souhaitez approfondir l'utilisation de l'en-tête Cache-Control, consultez le guide Caching best practices & max-age gotchas de Jake Archiibald.

Consultez la section Analyser votre cache pour savoir comment optimiser son utilisation pour les visiteurs connus.

Annexe: Autres conseils

Si vous disposez de plus de temps, voici d'autres moyens d'optimiser votre utilisation du cache HTTP:

  • Utilisez des URL cohérentes. Si vous diffusez le même contenu sur des URL différentes, ce contenu sera récupéré et stocké plusieurs fois.
  • Réduisez la perte d'utilisateurs. Si une partie d'une ressource (comme un fichier CSS) se met à jour fréquemment, contrairement au reste du fichier (comme le code de bibliothèque), envisagez de diviser le code fréquemment mis à jour dans un fichier distinct et d'utiliser une stratégie de mise en cache de courte durée pour le code fréquemment mis à jour et une stratégie de mise en cache longue pour le code qui ne change pas souvent.
  • Si votre règle Cache-Control contient un certain degré d'obsolescence, consultez la nouvelle directive stale-while-revalidate.

Annexe: Organigramme Cache-Control

Organigramme

Annexe: Exemples Cache-Control

Valeur Cache-Control Explication
max-age=86400 La réponse peut être mise en cache par les navigateurs et les caches intermédiaires pendant une durée maximale d'une journée (60 secondes x 60 minutes x 24 heures).
private, max-age=600 La réponse peut être mise en cache par le navigateur (mais pas les caches intermédiaires) pendant 10 minutes au maximum (60 secondes x 10 minutes).
public, max-age=31536000 La réponse peut être stockée par n'importe quel cache pendant un an.
no-store La réponse ne peut pas être mise en cache et doit être extraite entièrement à chaque requête.