Quand on parle de vitesse, on pense au cache de page, aux CDN, à la compression, aux images légères et à moins de JavaScript. Mais un mécanisme beaucoup moins visible travaille discrètement à chaque visite de retour et à chaque passage d’un robot d’exploration : le code de statut HTTP 304 Not Modified. Il ne change pas l’aspect de la page et n’ajoute aucune fonctionnalité, mais il évite de transférer ce que le navigateur ou le robot a déjà. Bien configuré, il réduit le trafic et la charge du serveur, aide le TTFB et rend plus efficace l’exploration de votre site.
Ce qu’est le 304 Not Modified
Un 304 est la réponse à une question précise : « la ressource a-t-elle changé depuis la dernière fois que je l’ai téléchargée ? ». Si la réponse est non, le serveur ne renvoie pas le contenu, seulement les en-têtes avec le statut 304, et le client continue d’utiliser la copie qu’il a dans son cache. Si la ressource a changé, le serveur répond par un 200 OK normal et le nouveau contenu.
Ce n’est pas un cache arbitraire, mais le résultat d’une requête conditionnelle. À la première requête, le serveur envoie la ressource avec un identifiant de version, l’en-tête ETag, et la date de la dernière modification, Last-Modified. Aux requêtes suivantes, le client les renvoie avec If-None-Match et If-Modified-Since, et le serveur les compare à l’état actuel.
Expiration et validation : deux choses différentes
Pour bien utiliser le 304, il faut distinguer deux mécanismes souvent confondus. L’expiration, régie par Cache-Control: max-age, fixe combien de temps le client peut utiliser la copie sans rien demander au serveur : pendant cette période, aucune requête n’est envoyée. La validation intervient ensuite : quand la copie a expiré, le client demande confirmation par une requête conditionnelle et obtient un 304 si rien n’a changé.
Une configuration efficace combine les deux : des durées d’expiration cohérentes avec la fréquence de mise à jour du contenu, et des identifiants de version fiables pour valider quand il le faut. L’expiration seule, avec des durées longues, risque d’afficher un contenu périmé ; la validation seule, sans expiration, génère une requête pour chaque ressource sur chaque page. Attention aussi à un malentendu courant : Cache-Control: no-cache ne veut pas dire « ne pas mettre en cache », mais « toujours valider avant d’utiliser la copie ». Si l’on veut vraiment empêcher le stockage, on utilise no-store.
ETag ou Last-Modified ?
Last-Modified est simple : une date, comparée à celle que renvoie le client. Sa précision est d’une seconde et il dépend de la date du fichier, qui peut changer même quand le contenu ne change pas, par exemple après un nouveau déploiement. L’ETag identifie la version réelle, souvent avec un hash du contenu, et il est plus précis.
Dans les environnements multi-serveurs, cependant, l’ETag demande de l’attention. Certains serveurs web le calculent aussi à partir de données du système de fichiers, comme l’inode, qui diffère d’un nœud à l’autre : le même fichier reçoit des ETag différents selon le serveur qui répond, et les validations échouent. Dans un cluster, il vaut mieux générer l’ETag uniquement à partir de la taille et de la date de modification, ou au niveau de l’application, du CDN ou du build, de façon cohérente sur tous les nœuds.
# Nginx : ETag et Last-Modified activés pour les fichiers statiques
etag on;
if_modified_since exact;
# Apache : ETag sans inode, cohérent entre plusieurs serveurs
FileETag MTime Size
Pourquoi le 304 aide le TTFB
Une réponse 304 est minuscule : des en-têtes seulement, pas de corps. Pour le client, cela signifie pouvoir utiliser tout de suite la copie locale ; pour le serveur, moins d’octets à envoyer, moins d’E/S et moins de CPU. Mais il faut être précis : le 304 n’abaisse pas automatiquement le TTFB. Si, pour décider qu’une page n’a pas changé, l’application doit quand même démarrer PHP, interroger la base de données et charger tous les plugins, le temps de réponse reste à peu près le même : on n’économise que le transfert. Le vrai bénéfice arrive quand la validation a lieu dans les couches les plus légères de l’infrastructure.
Un CDN peut valider directement sur ses propres nœuds, un reverse proxy comme Varnish peut répondre depuis la mémoire sans solliciter l’application, et Nginx gère très efficacement les fichiers statiques avec ETag et Last-Modified. Plus la validation est proche de la couche HTTP et loin du code applicatif, plus le 304 est rentable. C’est le même principe que le cache de page, dont nous avons parlé dans le TTFB et le cache de page.
304 et budget d’exploration
Les moteurs de recherche consacrent à chaque site une quantité limitée de ressources d’exploration. Chaque inefficacité, comme des réponses lentes ou des contenus retéléchargés sans raison, peut signifier moins de pages visitées et des mises à jour indexées plus tard. Google a confirmé que ses robots prennent en charge les requêtes conditionnelles avec ETag et Last-Modified : quand une ressource déjà connue n’a pas changé, un 304 lui permet de la vérifier sans la retélécharger, avec moins de travail pour le robot comme pour le serveur.
Dans le rapport Statistiques sur l’exploration de la Search Console, les réponses 304 figurent parmi les codes de statut, et elles montrent à quelle fréquence Googlebot trouve des ressources inchangées. Le 304 n’améliore pas à lui seul le classement et n’augmente pas par magie le budget d’exploration, mais sur les grands sites, comme les catalogues e-commerce ou les archives éditoriales, il contribue à une exploration plus ordonnée et à un serveur moins sollicité.
Ressources statiques : là où le 304 rapporte le plus
Le CSS, le JavaScript, les images et les polices changent rarement mais sont demandés un très grand nombre de fois, par les utilisateurs et par les robots. Sans les bons en-têtes, le navigateur risque de les retélécharger à chaque visite avec des réponses 200 complètes ; avec une bonne configuration, il les réutilise depuis le cache et, au besoin, les valide par un 304. Le gain se ressent lors des visites de retour et pendant les pics de trafic, quand des milliers de requêtes identiques entrent en concurrence avec les requêtes dynamiques.
Pour les fichiers dont le nom contient la version, comme app.8f3a21.css, on peut faire encore mieux : une expiration très longue et la directive immutable. Tant que le nom ne change pas, le fichier n’est même pas validé ; à chaque nouvelle version, le nom change et le navigateur le télécharge. C’est la stratégie idéale, et le 304 reste l’outil pour tout ce qui ne peut pas être versionné ainsi.
# Fichiers avec la version dans le nom : jamais validés tant que le nom ne change pas
Cache-Control: public, max-age=31536000, immutable
# Fichiers sans la version dans le nom : expiration courte, puis validation par un 304
Cache-Control: public, max-age=3600
ETag: "a1b2c3"
HTML dynamique : à utiliser avec discernement
Avec les pages HTML, la question est plus délicate. Une page d’accueil au contenu changeant souvent, avec des bannières, des widgets et des parties personnalisées, n’est pas une bonne candidate pour le 304, du moins sans couche de cache bien conçue : si l’application doit générer toute la page pour savoir si elle a changé, le 304 arrive trop tard pour être utile, et une validation naïve risque de traiter comme inchangé un contenu qui varie selon l’utilisateur, le panier ou la zone géographique. Cela fonctionne en revanche très bien pour les pages stables servies par un reverse proxy : articles, documentation, pages d’atterrissage, fiches produit qui changent rarement.
Les erreurs les plus courantes
- Compter les 304 comme un signe de bonne santé. Beaucoup de 304 sur des fichiers qui ne changent jamais indiquent une expiration trop courte : mieux vaut les servir directement depuis le cache local, sans aucune requête.
- Des ETag différents d’un nœud à l’autre. Dans un cluster, le même fichier avec des identifiants différents est retéléchargé encore et encore.
- Une validation au mauvais endroit. Un 304 produit par le CMS après avoir généré toute la page n’économise que de la bande passante, pas du temps.
- Des en-têtes contradictoires. Des plugins et des couches qui définissent des directives contradictoires produisent des flux de requêtes inefficaces et difficiles à comprendre.
- Un contenu personnalisé validé comme statique. Tout ce qui dépend de la session, du panier ou de la zone géographique doit être séparé du contenu partageable.
Comment le vérifier
Partez des en-têtes : une première requête pour lire ETag et Last-Modified, une seconde, conditionnelle, pour vérifier que la réponse est bien un 304.
# Lire les en-têtes de cache
curl -sI https://www.votresite.fr/css/style.css | grep -iE "etag|last-modified|cache-control"
# Requête conditionnelle avec l’ETag qui vient d’être lu
curl -sI -H 'If-None-Match: "a1b2c3"' https://www.votresite.fr/css/style.css | head -1
HTTP/2 304
Il vaut la peine de répéter le test plusieurs fois, pour vérifier que des nœuds différents renvoient le même ETag, et de contrôler le comportement au rechargement de la page dans le panneau Réseau de DevTools. Les journaux du serveur web montrent combien de requêtes se terminent par un 304, sur quelles ressources et avec quel temps de réponse : si un 304 coûte presque autant qu’un 200, la validation a lieu trop haut dans la pile. Côté SEO, les mêmes journaux et les statistiques sur l’exploration de la Search Console montrent dans quelle mesure Googlebot utilise les requêtes conditionnelles.
En résumé
Le 304 Not Modified permet d’éviter de renvoyer ce que le client a déjà : moins d’octets, moins de travail pour le serveur, des visites de retour plus rapides et une exploration plus efficace pour les moteurs de recherche. Il fonctionne au mieux avec une bonne politique d’expiration, avec des ETag cohérents ou un Last-Modified fiable, et avec une validation confiée aux couches les plus légères de l’infrastructure : CDN, reverse proxy et serveur web, pas l’application.
Ce n’est ni une astuce ni un raccourci, et il ne remplace pas une architecture de cache bien conçue. C’est plutôt l’application d’un principe simple qui traverse toute la performance web : le travail le plus rapide est celui qu’on ne fait pas. Ne pas renvoyer un fichier, ne pas générer de nouveau une réponse, ne pas utiliser de bande passante et de CPU pour rien : sur des milliers de requêtes par jour, cela fait la différence.