À chaque affichage d’une page web, des dizaines de fichiers texte transitent entre le serveur et le navigateur : du HTML, des feuilles de style, du JavaScript, du JSON, des SVG. Tous partagent une caractéristique précieuse : leur contenu est massivement redondant, donc massivement compressible. La compression HTTP est le mécanisme qui exploite cette redondance pour réduire le volume de données transférées, de manière totalement transparente pour l’utilisateur comme pour le code applicatif. C’est l’un des rares leviers de performance qui ne demande ni refonte, ni arbitrage éditorial : une fois configuré, il travaille pour chaque visiteur, à chaque requête.
Pendant longtemps, le sujet se résumait à une consigne unique : activer gzip. Puis Brotli est venu améliorer les ratios de compression, et depuis 2024, un troisième acteur s’est invité dans les navigateurs : Zstandard, dit zstd. Trois algorithmes coexistent désormais, chacun avec ses forces, et le choix entre eux n’est pas anecdotique : il influe directement sur le TTFB (Time To First Byte) de vos pages, donc sur leur LCP, donc sur les Core Web Vitals que Google mesure sur vos visiteurs réels.
Dans cet article, nous détaillons le fonctionnement de la compression HTTP, les spécificités de gzip, Brotli et Zstandard, puis la règle de décision que nous appliquons sur les sites de nos clients : Brotli pour les ressources statiques, Zstandard pour les contenus dynamiques. Nous terminons par la mise en œuvre concrète, sous Cloudflare et sur votre serveur d’origine, et par un aperçu de la prochaine évolution majeure du domaine, la compression par dictionnaire.
La compression HTTP, comment fonctionne-t-elle ?
Une négociation invisible entre navigateur et serveur
La compression HTTP repose sur un dialogue standardisé, la négociation de contenu, décrite par la RFC 9110 qui spécifie le protocole HTTP. À chaque requête, le navigateur annonce les algorithmes qu’il sait décompresser via l’en-tête Accept-Encoding. Le serveur choisit alors l’un d’entre eux, compresse la réponse et signale son choix via l’en-tête Content-Encoding. Le navigateur décompresse à la volée, et le moteur de rendu reçoit exactement le même contenu que si rien ne s’était passé.
# Requête du navigateur
GET /style.css HTTP/2
Accept-Encoding: gzip, deflate, br, zstd
# Réponse du serveur
HTTP/2 200
Content-Type: text/css
Content-Encoding: br
Ce mécanisme a une vertu essentielle : la rétrocompatibilité est structurelle. Un navigateur qui n’annonce pas zstd ne recevra jamais de réponse compressée en Zstandard. Il n’y a donc aucun risque à activer un algorithme récent : les clients qui ne le supportent pas retombent automatiquement sur Brotli ou gzip. C’est ce qui permet d’adopter les nouveaux encodages dès leur arrivée, sans attendre que 100 % du parc les comprenne.
Un détail technique mérite d’être connu quand des caches intermédiaires entrent en jeu : la réponse doit porter l’en-tête Vary: Accept-Encoding. Il indique aux caches que plusieurs variantes de la même URL existent, une par encodage, et qu’il ne faut jamais servir une version Brotli à un client qui ne demandait que du gzip. Les CDN et serveurs modernes le gèrent correctement, mais un proxy mal configuré qui l’omet peut produire des pages illisibles pour une fraction des visiteurs, un bug d’autant plus vicieux qu’il est invisible depuis le poste du développeur.
Ce qui se compresse, et ce qui ne se compresse pas
La compression HTTP ne concerne que les formats textuels : HTML, CSS, JavaScript, JSON, XML, SVG, flux RSS, WASM. Les images (JPEG, WebP, AVIF), les vidéos, les PDF et les polices au format woff2 embarquent déjà leur propre compression, spécifique à leur format : les recompresser au niveau HTTP n’apporte aucun gain, gaspille du CPU serveur et peut même légèrement augmenter la taille transférée. Cloudflare, par exemple, ne compresse que les types de contenu d’une liste fermée de formats textuels, et uniquement au-delà d’une taille minimale de quelques dizaines d’octets, en dessous de laquelle la compression coûterait plus qu’elle ne rapporte.
Sur le web réel, la marge de progression reste considérable : le Web Almanac de HTTP Archive montre que gzip reste l’encodage majoritaire, que l’adoption de Brotli progresse lentement, et qu’une part significative des ressources compressibles ne sont toujours pas compressées du tout. Autrement dit, avant même de parler d’arbitrage fin entre algorithmes, beaucoup de sites laissent dormir un gain immédiat.
Précisons aussi que la compression ne remplace pas la minification, et réciproquement : la minification supprime ce qui est inutile (espaces, commentaires, noms longs), la compression encode efficacement ce qui reste. Les deux se cumulent, et c’est bien la combinaison minification puis compression qui produit les fichiers les plus légers. Un bundle JavaScript non minifié mais compressé laisse encore des octets sur la table ; un fichier minifié mais servi sans compression en laisse bien davantage.
gzip, Brotli, Zstandard : trois générations d’algorithmes
gzip, l’increvable standard universel
Né en 1992 et spécifié par la RFC 1952, gzip s’appuie sur l’algorithme DEFLATE, qui combine deux techniques : la détection de séquences répétées (LZ77) et le codage entropique de Huffman, qui attribue les codes les plus courts aux motifs les plus fréquents. Trente ans plus tard, gzip reste supporté par absolument tous les clients HTTP en circulation, des navigateurs aux robots d’indexation en passant par le moindre script. C’est sa force unique : il est le plancher commun, le fallback de dernier recours qui garantit qu’une réponse compressée sera toujours lisible.
Sa limite est tout aussi claire : ses ratios de compression sont les plus faibles des trois. Servir du gzip à un navigateur qui sait faire mieux, c’est transférer des octets inutiles à chaque requête.
Brotli, le champion du ratio de compression
Développé par Google, d’abord pour compresser les polices au format WOFF2, Brotli a été généralisé au web en 2015 puis spécifié par la RFC 7932. Son arme secrète est un dictionnaire statique d’environ 120 Ko embarqué dans l’algorithme lui-même, entraîné sur un vaste corpus de contenus web : les mots, balises et fragments de code les plus courants du web y sont déjà répertoriés, ce qui permet de les encoder en quelques bits à peine. Brotli propose douze niveaux de compression, de 0 à 11 : les niveaux élevés compressent remarquablement bien, mais deviennent lents, ce qui aura son importance dans la suite de cet article.
Côté support, la question est réglée depuis des années : tous les navigateurs modernes annoncent br dans leur Accept-Encoding. Brotli est aujourd’hui l’encodage de référence pour le web, et c’est lui que servent par défaut la plupart des CDN quand le client le supporte.
Zstandard, le nouveau venu ultra rapide
Zstandard a été créé par Facebook en 2015 pour ses besoins internes de stockage et de réplication, où des pétaoctets de données sont compressés en continu. Spécifié par la RFC 8878 et enregistré comme encodage HTTP auprès de l’IANA en 2020, il a longtemps attendu son heure côté navigateurs. Elle est arrivée en 2024 : Chrome 123 l’active en mars, Firefox 126 en mai, comme le récapitule caniuse. Safari reste en retrait, avec un support qui ne s’amorce que fin 2025 : le fallback vers Brotli ou gzip demeure donc indispensable pour une partie du parc.
La promesse de Zstandard n’est pas de compresser mieux que Brotli : à niveaux comparables, leurs ratios sont proches. Sa promesse est de compresser beaucoup plus vite, à ratio quasi équivalent. Et c’est précisément cette caractéristique qui rebat les cartes.
Le compromis fondamental : ratio contre vitesse
Tout algorithme de compression arbitre entre deux grandeurs : le temps passé à compresser, et la taille du résultat. Chercher un meilleur ratio, c’est explorer plus de combinaisons, donc consommer plus de CPU et de millisecondes. Cet arbitrage est sans conséquence quand la compression a lieu une seule fois, à l’avance. Il devient critique quand elle a lieu à chaque requête, car le temps de compression s’ajoute alors directement au Time To First Byte : le navigateur ne peut rien afficher tant que le premier octet du HTML n’est pas arrivé. Chaque milliseconde passée à compresser retarde d’autant le début du rendu, et par ricochet le LCP, dont nous avons détaillé la mécanique dans notre article sur l’optimisation du Largest Contentful Paint.
Les chiffres publiés par Cloudflare, qui compresse une part significative du trafic web mondial, donnent la mesure de l’écart. Sur leurs données de production, Zstandard compresse en moyenne 42 % plus vite que Brotli (0,848 ms par réponse contre 1,544 ms) tout en conservant un ratio comparable, et compresse environ 11 % mieux que gzip à vitesse équivalente, selon le blog d’ingénierie de Cloudflare. Des analyses indépendantes, comme celle de Paul Calvano, confirment cette hiérarchie : Brotli gagne sur la taille finale aux niveaux élevés, Zstandard gagne nettement sur le débit de compression.
Comment mesurer l’impact chez vous ? Le TTFB se lit à deux endroits complémentaires : en données de terrain, dans le rapport CrUX que Google construit à partir des sessions Chrome réelles, et en laboratoire, via un simple test réseau reproductible. Nous avons consacré un article aux différences entre PageSpeed Insights et Lighthouse qui détaille cette distinction essentielle entre mesure synthétique et mesure terrain. Pour la compression, le terrain tranche : c’est la distribution des TTFB réels, sur toutes les pages et tous les visiteurs, qui révèle le gain d’un passage à Zstandard sur le HTML dynamique.
Harry Roberts, consultant en performance web reconnu, résume bien l’enjeu dans son article The Three Cs : Concatenate, Compress, Cache : la compression ne se juge pas dans l’absolu, mais dans son interaction avec le cache et le cycle de vie de chaque ressource. C’est exactement cette grille de lecture qui fonde la règle de décision qui suit.
Votre site est-il aussi rapide que vos visiteurs l’espèrent ?
Statique ou dynamique : la règle qui décide de tout
Une fois le compromis ratio contre vitesse posé, le bon algorithme se déduit d’une seule question : cette ressource est-elle compressée une fois, ou à chaque requête ? C’est la distinction entre ressources statiques cacheables et contenus dynamiques, et elle suffit à trancher tous les cas courants.
Ressources statiques : Brotli niveau 11, compressé au build
Un fichier CSS, un bundle JavaScript ou un SVG ne changent qu’au déploiement. Leur compression peut donc être effectuée une seule fois, au moment du build, puis mise en cache et servie des milliers ou des millions de fois. Dans ce scénario, le temps de compression est amorti à l’infini : peu importe que Brotli niveau 11 mette dix fois plus de temps qu’un niveau intermédiaire, puisque ce coût n’est payé qu’une fois, hors du chemin critique de toute requête. Seule compte alors la taille servie au visiteur, et c’est là que Brotli 11 est imbattable.
Concrètement, la précompression d’un fichier se fait en une commande, l’outil brotli étant disponible sur tous les systèmes :
# Précompression Brotli au niveau maximal (11, par défaut en CLI)
brotli --input theme.min.css --output theme.min.css.br
brotli --input app.bundle.js --output app.bundle.js.br
# Le serveur sert ensuite le .br directement,
# sans compresser quoi que ce soit à la volée
Sur un site derrière un CDN, ce travail est en général pris en charge par la couche d’edge : la première requête est compressée puis le résultat est mis en cache. L’important est de vérifier que les ressources statiques sortent bien en Brotli, et pas en gzip par défaut ni, pire, recompressées à chaque passage.
Contenus dynamiques : Zstandard pour préserver le TTFB
Le HTML généré par votre CMS, les réponses JSON de vos API, les flux RSS ou les sitemaps dynamiques racontent une toute autre histoire : chaque requête produit une réponse potentiellement unique, compressée à la volée, dans le chemin critique. Ici, le temps de compression entre intégralement dans le TTFB. C’est le terrain de jeu idéal de Zstandard : un ratio proche de Brotli, pour un coût CPU nettement moindre. Cloudflare le formule explicitement dans son annonce du support Zstandard : l’algorithme offre un excellent équilibre qui le rend particulièrement adapté aux contenus dynamiques comme le HTML et les données non cacheables.
Le gain se chiffre en dizaines, parfois en centaines de millisecondes sur des pages dynamiques lourdes ou des serveurs chargés : pages panier d’un site e-commerce, tableaux de bord utilisateur, listings filtrés. Pris isolément, c’est un gain modeste ; cumulé avec les autres leviers du TTFB (HTTP/3, cache HTML court, temps de génération serveur), il participe à la construction d’un chemin critique réellement optimisé.
Une nuance importante : cette règle ne vaut que pour les contenus réellement non cacheables. Une page éditoriale figée, cacheable plusieurs heures en edge, bascule en pratique dans la catégorie statique : elle bénéficiera davantage d’un Brotli à niveau élevé, compressé une fois puis servi depuis le cache.
Contenus déjà compressés : ne rien faire
Troisième ligne de la matrice, souvent négligée : les formats déjà compressés. Un JPEG, un WebP, un AVIF, une vidéo MP4, un PDF ou une police woff2 sont déjà passés par une compression spécialisée, bien plus efficace sur leur type de données qu’un algorithme généraliste. Leur appliquer gzip, Brotli ou Zstandard revient à payer du CPU pour un gain nul. Si votre serveur compresse ces formats, ce n’est pas un signe de zèle mais un défaut de configuration à corriger.
Le piège du base64 : la compression sabotée
Un dernier piège mérite sa place dans cette matrice, car il sabote la compression quel que soit l’algorithme retenu : l’intégration de ressources encodées en base64 dans le HTML, le CSS, le JavaScript ou les SVG. Toute la puissance de gzip, Brotli et Zstandard repose sur la redondance des contenus textuels ; or l’encodage base64 produit des chaînes uniques, très faiblement redondantes, sur lesquelles les algorithmes n’ont presque plus de prise. En transformant une image en texte pseudo-aléatoire, on détruit précisément ce qui fait leur force.
La sanction est triple. La chaîne base64 est par construction environ 33 % plus lourde que le fichier natif qu’elle encode. Elle traverse ensuite la compression presque intacte, là où le reste du document fond. Et son décodage consomme des ressources CPU supplémentaires côté navigateur, à chaque affichage. Embarqué dans un HTML généré dynamiquement, un lot de ressources en base64 gonfle la réponse compressée : l’impact sur le TTFB peut être majeur, précisément sur la métrique que le choix d’un bon algorithme cherchait à améliorer.
C’est l’une des raisons pour lesquelles nous refusons catégoriquement l’usage du base64 dans nos audits comme dans nos optimisations : une image appartient à un fichier image, servi dans son format natif, cacheable indépendamment et compressé par son propre codec. Les quelques requêtes HTTP économisées ne compensent jamais ce triple surcoût, surtout depuis le multiplexage apporté par HTTP/2.
Mise en œuvre concrète, de Cloudflare au serveur d’origine
Sur Cloudflare : deux Compression Rules complémentaires
Par défaut, Cloudflare compresse les types de contenu de sa liste en gzip ou Brotli selon l’en-tête Accept-Encoding du visiteur. Zstandard est supporté par l’infrastructure mais doit être activé explicitement via les Compression Rules. Pour appliquer la règle statique/dynamique décrite plus haut, nous déployons deux règles, dont l’ordre est décisif puisque Cloudflare applique la dernière règle qui correspond à la requête.
La première règle, globale, conserve la cible « types de contenu par défaut » et ordonne les algorithmes en privilégiant Brotli, avec Zstandard puis gzip en repli. Elle couvre notamment les ressources statiques. La seconde, placée en dernière position, cible uniquement les types MIME textuels dynamiques et inverse la priorité au profit de Zstandard. Son expression de filtrage s’appuie sur le champ media_type, qui expose le type MIME en minuscules et sans paramètres, ce qui évite de gérer les variantes de charset :
(http.response.content_type.media_type in {
"text/html"
"text/plain"
"text/xml"
"application/json"
"application/ld+json"
"application/xml"
"application/rss+xml"
"application/xhtml+xml"
})
Avec l’ordre d’algorithmes zstd, brotli, gzip sur cette seconde règle, Cloudflare sert automatiquement le meilleur encodage supporté par chaque visiteur : Zstandard pour Chrome et Firefox, Brotli pour Safari, gzip pour les clients anciens. Aucune réponse cassée possible, la négociation de contenu fait son travail. Nous avons détaillé les autres apports (et les pièges) d’un CDN dans notre article CDN et webperf.
Sur le serveur d’origine
Derrière un CDN, le serveur d’origine n’est sollicité que sur les cache MISS, mais sa configuration compte toujours : c’est lui qui compresse le HTML dynamique quand la page n’est pas en cache edge. À noter, une subtilité documentée par Cloudflare : leur infrastructure n’accepte que Brotli ou gzip en provenance de l’origine. Une origine qui envoie du Brotli est servie telle quelle au visiteur qui le supporte ; pour offrir du Zstandard au client final, c’est la recompression en edge, via les Compression Rules, qui s’en charge.
Sur un serveur Apache, l’activation de Brotli pour les types textuels se fait via mod_brotli, avec mod_deflate en repli pour les clients limités à gzip :
<IfModule mod_brotli.c>
AddOutputFilterByType BROTLI_COMPRESS text/html text/css
AddOutputFilterByType BROTLI_COMPRESS application/javascript
AddOutputFilterByType BROTLI_COMPRESS application/json image/svg+xml
</IfModule>
<IfModule mod_deflate.c>
AddOutputFilterByType DEFLATE text/html text/css
AddOutputFilterByType DEFLATE application/javascript
AddOutputFilterByType DEFLATE application/json image/svg+xml
</IfModule>
Le niveau de compression à la volée mérite d’être ajusté : un Brotli niveau 4 ou 5 offre un bien meilleur compromis pour du HTML dynamique que le niveau 11, réservé à la précompression. C’est typiquement le genre de réglage fin que nous appliquons sur notre offre d’hébergement WordPress optimisé pour la performance, où la chaîne complète, de PHP à l’edge, est configurée selon cette logique.
Un mot spécifique sur WordPress, qui motorise une bonne partie des sites que nous accompagnons : la compression HTTP ne relève pas d’un plugin. Elle se configure au niveau du serveur web ou du CDN, jamais dans PHP, où elle consommerait des ressources applicatives pour un travail que la couche HTTP fait mieux. Les extensions de cache qui proposent une option gzip ne font en réalité qu’écrire les directives serveur correspondantes ; mieux vaut le faire proprement, en connaissance de cause, dans la configuration du serveur. C’est l’un des points systématiquement passés en revue dans nos prestations d’optimisation de la performance WordPress.
Dans la même logique, méfiez-vous d’une option répandue dans les plugins de cache WordPress : la génération de fichiers .gz à côté des fichiers .html du cache statique. L’intérêt réel est faible : compresser à la volée un HTML déjà servi depuis un cache statique ne coûte qu’une fraction de milliseconde en gzip, alors que ces copies précompressées doublent l’espace disque occupé par le cache et, sur un site où le cache est purgé régulièrement, alourdissent chaque cycle de régénération en écritures et en CPU.
Le gaspillage n’est pas le seul problème : sur un serveur mal configuré, le fichier .gz peut être servi sans l’en-tête Content-Encoding correspondant, et le visiteur reçoit alors un contenu compressé illisible en guise de page. Notre recommandation est simple : désactiver cette option et laisser le serveur compresser le HTML à la volée avec gzip, ou mieux, selon la règle vue plus haut.
Vérifier ce que reçoivent réellement vos visiteurs
Comme toujours en web performance, la configuration ne vaut que si elle est vérifiée. Le plus simple est d’ouvrir l’onglet Réseau des DevTools et d’ajouter la colonne Content-Encoding : chaque ressource affiche alors l’algorithme réellement servi. En ligne de commande, curl permet de tester chaque scénario de négociation :
# Que reçoit un navigateur moderne ?
curl -sI -H "Accept-Encoding: zstd, br, gzip" https://exemple.fr/ | grep -i content-encoding
# Que reçoit un client limité à gzip ?
curl -sI -H "Accept-Encoding: gzip" https://exemple.fr/ | grep -i content-encoding
# Comparer les tailles réellement transférées
curl -so /dev/null -H "Accept-Encoding: gzip" -w "gzip : %{size_download} octets\n" https://exemple.fr/
curl -so /dev/null -H "Accept-Encoding: br" -w "brotli : %{size_download} octets\n" https://exemple.fr/
Attention à un piège classique derrière un CDN : la réponse peut différer selon que la ressource est servie depuis le cache edge ou depuis l’origine. Il faut donc tester les deux cas, et vérifier aussi les réponses de vos API, souvent oubliées alors qu’elles pèsent lourd dans les applications riches. Ce type de contrôle systématique de la chaîne de livraison fait partie intégrante de nos audits de web performance.
Compression Dictionary Transport : la prochaine frontière
La compression classique repart de zéro à chaque ressource : elle ne sait rien de ce que le navigateur possède déjà. Le Compression Dictionary Transport, standardisé par la RFC 9842, change ce paradigme : il permet d’utiliser une ressource déjà téléchargée comme dictionnaire de compression pour les suivantes. Le cas d’usage emblématique : servir la version 2 d’un bundle JavaScript en n’envoyant, pour l’essentiel, que le delta par rapport à la version 1 encore présente dans le cache du navigateur.
Le mécanisme repose sur deux nouveaux encodages, dcb (Brotli avec dictionnaire) et dcz (Zstandard avec dictionnaire), et sur l’en-tête Use-As-Dictionary, par lequel le serveur déclare qu’une ressource pourra servir de dictionnaire pour un motif d’URL donné :
# Réponse servant app.v1.js, déclaré comme futur dictionnaire
Use-As-Dictionary: match="/js/app.*.js", match-dest=("script")
# Requête suivante pour app.v2.js : le navigateur annonce le dictionnaire
Available-Dictionary: :pZGm1Av0IEBKARczz7exkNYsZb8LzaMrV7J32a2fFG4=:
# Réponse : delta compressé par rapport à app.v1.js
Content-Encoding: dcb
Les gains peuvent être spectaculaires sur des applications à base de code stable et à déploiements fréquents : l’essentiel du bundle ne change pas d’une version à l’autre, et le transfert se réduit au delta. Quelques contraintes tempèrent l’enthousiasme : les dictionnaires sont cloisonnés par origine (pas de partage cross-site, conséquence du partitionnement du cache HTTP), la CSP doit autoriser leur chargement, et la mise en œuvre demande une vraie discipline de versionnement. C’est un levier que nous évaluons au cas par cas, typiquement lors d’un audit de performance d’application monopage, où les bundles JavaScript volumineux et fréquemment redéployés en font les meilleures candidates.
Ce qu’il faut retenir
La compression HTTP n’est plus un simple interrupteur à activer, c’est un arbitrage à trois algorithmes qui se résout avec une règle simple. Pour les ressources statiques cacheables, Brotli au niveau maximal, compressé une fois au build ou en edge : seule la taille compte, le coût est amorti. Pour les contenus dynamiques, HTML généré et réponses d’API en tête, Zstandard : à ratio quasi égal, il rend des millisecondes de TTFB à chaque requête. Pour les formats déjà compressés, aucune compression HTTP. Et dans tous les cas, gzip reste le filet de sécurité universel que la négociation de contenu sert aux clients les plus anciens.
Ces réglages sont rapides à déployer, sans risque grâce à la négociation de contenu, et leurs bénéfices se cumulent avec tous les autres leviers du chemin critique. Si vous souhaitez savoir ce que votre site sert réellement à vos visiteurs, et ce qu’il pourrait leur servir de mieux, nos experts intègrent cette analyse dans chaque prestation d’optimisation de la web performance. La compression est souvent le levier au meilleur rapport effort/gain de toute la liste.