Tra tutte le ottimizzazioni di un sito web, la compressione delle risorse testuali è probabilmente quella con il miglior rapporto tra sforzo e risultato. HTML, CSS, JavaScript, JSON e SVG sono testo, e il testo si comprime molto bene: un file JavaScript da 450 kilobyte può viaggiare in rete come poco più di 100. Il browser lo riceve compresso, lo decomprime in pochi millisecondi e lo usa come se fosse arrivato intero. Meno dati da trasferire significa pagine che arrivano prima, soprattutto sulle reti mobili.
Per anni lo standard è stato Gzip. Poi è arrivato Brotli, oggi supportato ovunque, e più di recente Zstandard, un algoritmo nato per comprimere dati nei sistemi di Facebook e ora supportato dai principali browser basati su Chromium e da Firefox. In questo articolo vediamo come funziona la compressione HTTP, in cosa si distinguono i tre formati, come scegliere livelli e modalità, come configurarli e quale effetto aspettarsi sulle Core Web Vitals.
Come funziona la compressione HTTP
La compressione HTTP si basa su una negoziazione semplice. Il browser, in ogni richiesta, indica con l’intestazione Accept-Encoding quali formati sa decomprimere: per esempio gzip, deflate, br, zstd. Il server sceglie uno dei formati accettati, comprime la risposta e lo dichiara nell’intestazione Content-Encoding. Il browser legge l’intestazione, decomprime e usa il contenuto. Se il server non comprime, o se il browser non accetta alcun formato, la risorsa viaggia così com’è.
Un dettaglio importante riguarda le cache intermedie, come CDN e proxy. Poiché la stessa URL può essere servita in formati diversi a seconda del browser, la risposta deve includere Vary: Accept-Encoding, che indica alle cache di conservare versioni separate per ciascun formato. È lo stesso meccanismo che abbiamo visto per la negoziazione dei formati delle immagini: senza, una cache potrebbe servire una risposta compressa in Zstandard a un browser che non sa decomprimerla.
Gzip: lo standard universale
Gzip, basato sull’algoritmo Deflate, è supportato da qualsiasi browser e da qualsiasi server da oltre vent’anni. Offre livelli di compressione da 1 a 9: i livelli bassi sono molto veloci e comprimono meno, quelli alti comprimono di più ma richiedono più tempo di CPU. Il livello 6, predefinito in molte configurazioni, è un buon compromesso. Su file di testo tipici, Gzip riduce la dimensione del 65-75%.
Oggi Gzip è soprattutto il formato di riserva: quello da offrire ai client che non supportano nulla di meglio, come alcuni strumenti automatici, vecchie librerie HTTP e alcuni bot. Un server moderno dovrebbe sempre supportarlo, ma servirlo ai browser attuali significa rinunciare a qualche punto percentuale di compressione.
Brotli: più compressione per il web
Brotli è stato sviluppato da Google e pensato specificamente per il web. Oltre a un algoritmo più efficiente, include un dizionario predefinito di parole e frammenti comuni in HTML, CSS e JavaScript, che gli permette di comprimere meglio soprattutto i file di piccole e medie dimensioni. È supportato da tutti i browser moderni, ma solo su connessioni HTTPS, e ha livelli da 0 a 11.
A parità di velocità, Brotli comprime tipicamente il 15-20% meglio di Gzip. Ai livelli massimi, 10 e 11, il guadagno aumenta ancora, ma la compressione diventa molto lenta: decine o centinaia di volte più lenta dei livelli intermedi. Questo porta alla distinzione più importante nella configurazione della compressione, quella tra compressione statica e dinamica, su cui torneremo tra poco. La decompressione, invece, è veloce a qualsiasi livello: per il browser, un file compresso al livello 11 non costa più di uno compresso al livello 5.
Zstandard: il nuovo arrivato
Zstandard, abbreviato in zstd, è stato creato da Yann Collet in Facebook e pubblicato nel 2016. È diventato rapidamente uno standard in molti ambiti al di fuori del web: sistemi di archiviazione, database, pacchetti delle distribuzioni Linux, file system, trasferimenti di dati tra server. È descritto nella RFC 8878, che lo registra anche come formato di codifica HTTP.
La sua caratteristica distintiva è la velocità. Ai livelli bassi e intermedi, Zstandard comprime molto più rapidamente di Gzip ottenendo rapporti di compressione simili o migliori, e decomprime ancora più velocemente. Ai livelli alti, fino a 19 e oltre con le modalità estreme, si avvicina a Brotli, pur senza in genere superarlo sui tipici file del web, dove il dizionario integrato di Brotli fa la differenza. In sintesi: Brotli resta il campione del rapporto di compressione sulle risorse web statiche, Zstandard offre il miglior equilibrio tra compressione e velocità quando si comprime al volo.
Sul fronte del supporto, Chrome e gli altri browser basati su Chromium accettano Zstandard dalla primavera del 2024, e Firefox lo ha seguito poco dopo. Safari, al momento della scrittura, non lo annuncia nell’intestazione Accept-Encoding. Grazie alla negoziazione, questo non è un problema: i browser che lo supportano ricevono Zstandard, gli altri Brotli o Gzip. Sul lato server il supporto è in crescita: alcune CDN, come Cloudflare, lo offrono già verso i browser; server web come Caddy lo includono nativamente; per nginx e Apache esistono moduli di terze parti. È ragionevole aspettarsi che diventi sempre più comune nei prossimi anni.
Compressione statica e dinamica
La scelta del formato è solo metà della questione. L’altra metà è quando comprimere, e la risposta cambia a seconda del tipo di risorsa.
Le risorse statiche, come fogli di stile, script, font SVG e file JSON che non cambiano tra una richiesta e l’altra, possono essere compresse una sola volta, in anticipo, al livello massimo. Il costo di compressione, anche se alto, si paga una volta sola al momento della pubblicazione, e ogni richiesta successiva riceve il file più piccolo possibile. È la compressione statica, o precompressione: accanto a app.js si generano app.js.br, app.js.zst e app.js.gz, e il server sceglie il file giusto in base all’intestazione del browser.
# Precompressione al momento della pubblicazione
for f in dist/*.{js,css,svg,json}; do
brotli --best --keep "$f" # app.js.br, livello 11
zstd -19 --quiet --keep "$f" # app.js.zst, livello 19
gzip -9 --keep "$f" # app.js.gz, livello 9
done
Le risorse dinamiche, come l’HTML generato da WordPress per ogni richiesta o le risposte delle API, devono invece essere compresse al volo, mentre l’utente aspetta. Qui il tempo di compressione si somma al TTFB, e un livello troppo alto può annullare il vantaggio di un file più piccolo. Per la compressione dinamica si usano livelli intermedi: Brotli tra 4 e 6, Gzip intorno a 6, Zstandard tra 3 e 6. È qui che Zstandard mostra il suo punto di forza: a parità di tempo di CPU, comprime più di Gzip e spesso quanto Brotli ai livelli intermedi.
Le pagine servite da una cache di pagina sono un caso intermedio: l’HTML è dinamico, ma viene generato una volta e servito molte volte. Molte soluzioni di cache e molte CDN memorizzano la pagina già compressa, così il costo di compressione si paga solo alla generazione. In questi casi si può usare un livello più alto rispetto alla compressione a ogni richiesta.
Configurazione del server
La configurazione dipende dal software. Un esempio per nginx con il modulo Brotli, che è il caso più diffuso, combina compressione statica e dinamica:
# Compressione dinamica
gzip on;
gzip_comp_level 6;
gzip_min_length 1024;
gzip_vary on;
gzip_types text/css application/javascript application/json image/svg+xml;
brotli on;
brotli_comp_level 5;
brotli_types text/css application/javascript application/json image/svg+xml;
# Compressione statica: usa i file .br e .gz già presenti
brotli_static on;
gzip_static on;
Con Caddy la configurazione è ancora più semplice, perché supporta nativamente Zstandard e Gzip e sceglie il formato in base alle preferenze del browser:
esempio.it {
encode zstd gzip
file_server {
precompressed br zstd gzip
}
}
Su un hosting gestito o dietro una CDN, spesso non c’è nulla da configurare: la compressione viene applicata automaticamente, e la CDN può comprimere con Brotli o Zstandard anche quando il server d’origine usa solo Gzip. Vale comunque la pena verificare cosa arriva effettivamente al browser, perché non sempre le impostazioni predefinite sono quelle ottimali, come abbiamo visto parlando di Cloudflare.
Cosa comprimere e cosa no
La compressione HTTP va applicata alle risorse testuali: HTML, CSS, JavaScript, JSON, XML, SVG, file di testo e font in formati non compressi, come TTF. Non va applicata ai formati già compressi internamente: immagini JPEG, PNG, WebP e AVIF, font WOFF2, video, audio, archivi ZIP e PDF. Comprimerli di nuovo non riduce quasi nulla e spreca CPU sia sul server sia sul dispositivo. Anche i file molto piccoli, sotto il kilobyte, non traggono beneficio: l’intestazione della compressione può pesare più del risparmio.
Un caso particolare riguarda la sicurezza. Comprimere risposte che contengono insieme dati segreti, come token di protezione dei moduli, e contenuti controllabili da un aggressore, come parametri della richiesta riflessi nella pagina, può esporre a una famiglia di attacchi nota come BREACH, che deduce il segreto osservando la dimensione delle risposte compresse. Le contromisure non consistono nel rinunciare alla compressione, ma nel non riflettere input dell’utente vicino ai segreti e nel variare i token a ogni richiesta, come fanno i framework moderni.
L’effetto sulle Core Web Vitals
La compressione agisce sul tempo di trasferimento delle risorse, e quindi su tutte le metriche che dipendono dal loro arrivo. L’HTML compresso arriva prima e il browser può iniziare prima a costruire la pagina; il CSS che blocca il rendering arriva prima e il primo disegno si anticipa; gli script necessari arrivano prima. L’effetto è più evidente sull’LCP e sul First Contentful Paint, e cresce con la lentezza della rete: su una connessione mobile lenta, risparmiare 300 kilobyte di JavaScript può valere diverse centinaia di millisecondi.
C’è però un limite da tenere presente: la compressione riduce i byte trasferiti, non il lavoro che il browser deve fare dopo. Un file JavaScript da 450 kilobyte compresso a 100 viene comunque decompresso a 450 kilobyte, analizzato, compilato ed eseguito. Il costo sul main thread, e quindi sull’INP e sul Total Blocking Time, resta invariato. Per questo la compressione non sostituisce la riduzione del codice: la completa. Meno codice, e quel poco compresso al meglio.
Il passaggio da Gzip a Brotli o Zstandard vale in genere un ulteriore 15-20% sulle risorse testuali. Non è un salto paragonabile a quello tra nessuna compressione e Gzip, ma su pagine con molto JavaScript è un guadagno concreto e gratuito, che non richiede di modificare il codice del sito.
Oltre: i dizionari di compressione
L’evoluzione più interessante della compressione sul web riguarda i dizionari condivisi. L’idea è semplice: quando un sito pubblica una nuova versione di un file JavaScript, gran parte del contenuto è identica alla versione precedente, che il browser ha già in cache. Con il meccanismo Compression Dictionary Transport, il browser può usare la vecchia versione come dizionario, e il server può inviare solo le differenze, compresse con Brotli o Zstandard. Il risultato, nei casi favorevoli, è una riduzione di un ordine di grandezza rispetto alla compressione tradizionale.
Il meccanismo è supportato nelle versioni recenti di Chrome e richiede una configurazione specifica sul server, per cui oggi interessa soprattutto applicazioni con aggiornamenti frequenti e grandi pacchetti JavaScript. Ma mostra la direzione: la compressione non è un problema risolto, e i formati moderni come Zstandard, con il loro supporto nativo ai dizionari, sono pensati anche per questi scenari.
Come verificare
Nel pannello Network dei DevTools, la colonna Size mostra due valori per ogni risorsa: la dimensione trasferita e quella effettiva dopo la decompressione. Aggiungendo l’intestazione di risposta Content-Encoding come colonna, si vede subito quale formato è stato usato per ciascuna risorsa. Se un file JavaScript o CSS mostra la stessa dimensione trasferita ed effettiva, non è compresso.
Da riga di comando, curl permette di simulare le richieste di un browser con diverse intestazioni:
# Quale formato sceglie il server?
curl -sI -H 'Accept-Encoding: zstd, br, gzip' https://esempio.it/app.js | grep -i 'content-encoding\|vary'
# Quanto pesa ogni versione?
for enc in gzip br zstd; do
printf '%s: ' "$enc"
curl -s -H "Accept-Encoding: $enc" https://esempio.it/app.js | wc -c
done
Lighthouse segnala le risorse testuali non compresse con un avviso dedicato. È un controllo semplice, ma sorprendentemente spesso rivela file dimenticati: fogli di stile serviti da un sottodominio non configurato, file JSON di traduzione, SVG di grandi dimensioni, risposte della REST API.
Una configurazione consigliata
- Risorse statiche: precompresse al livello massimo in Brotli, e se il server lo supporta anche in Zstandard, con Gzip come riserva.
- HTML e API dinamici: compressione al volo con Brotli ai livelli 4-6 o Zstandard ai livelli 3-6, Gzip 6 come riserva.
- Pagine in cache: memorizzate già compresse, con un livello più alto se la generazione è rara.
- Sempre:
Vary: Accept-Encoding, nessuna compressione per immagini, font WOFF2 e video, soglia minima intorno al kilobyte.
In sintesi
La compressione HTTP riduce di tre quarti o più il peso delle risorse testuali, con un effetto diretto sui tempi di caricamento e sulle metriche che ne dipendono. Gzip è lo standard universale da offrire come riserva; Brotli offre la compressione migliore per le risorse web, soprattutto se precompresse al livello massimo; Zstandard, supportato ormai dalla maggior parte dei browser, offre il miglior equilibrio tra rapporto e velocità per la compressione al volo.
La regola pratica è comprimere al massimo in anticipo ciò che è statico, comprimere con livelli intermedi ciò che è dinamico, non comprimere ciò che è già compresso e verificare nei DevTools cosa arriva davvero al browser. E ricordare che la compressione riduce i byte, non il lavoro: resta indispensabile, ma non sostituisce un sito che carica meno codice.