La cache è un acceleratore e un nascondiglio. Se non sai cosa stai cachando, stai servendo ieri e chiamandolo performance.
La cache HTTP accelera quando sai cosa stai conservando, per chi, e per quanto. Nasconde i bug quando conservi HTML sbagliato: un prezzo vecchio, una pagina account servita a un altro utente, un carrello che "funziona" perché stai guardando ieri. Prima si decide la policy. Poi si misura. Il colpo di fortuna sul hit-ratio non è una strategia.
In breve: La caching HTTP è nella RFC 9111. Si governa con Cache-Control: max-age, s-maxage, public, private, no-store, no-cache. L'ETag serve a rivalidare, non a "andare più veloci" in astratto. La cache del browser e quella della CDN non sono la stessa cosa. HTML di checkout e account non si mette in una cache condivisa.
Cosa trovi in questo articolo
Cosa dice la RFC, in pratica
La RFC 9111 descrive come un'implementazione HTTP può conservare una risposta e riutilizzarla. Non è un plugin. È il protocollo. MDN riassume le direttive in Cache-Control e il quadro in HTTP caching. web.dev ha una guida operativa: Prevent unnecessary network requests with the HTTP Cache.
Due idee, e basta. Una risposta è fresh o è stale. Se è fresh, una cache può servirla senza chiedere all'origine. Se è stale, può rivalidare (spesso con ETag) o deve andare a origin. Tutto il resto, "ottimizzatore", "cache booster", layer sul CMS, o rispetta queste regole o le viola. Quando le viola, il sito sembra veloce e si comporta a caso.
Non c'è un hit-ratio "giusto" da inseguire. Dipende da cosa è pubblico e immutabile (un CSS con hash nel nome) e da cosa è personale (una dashboard). Confrontare il 98% di hit di un CDN su statici con l'HTML di un ecommerce è un confronto tra due mestieri. Nel cluster Performance la cache è uno strumento, non un punteggio.
Cache-Control: max-age, s-maxage, private, public
max-age è la freschezza in secondi per la cache che sta considerando la risposta. s-maxage vale per le cache condivise (CDN, reverse proxy), e se c'è vince su max-age per quelle. È il modo corretto di dire: "il browser tenga poco, la CDN tenga di più", o il contrario, senza fingere che siano lo stesso attore.
public autorizza una cache condivisa a conservare. private dice il contrario: risposta per un singolo utente, non per un proxy in mezzo. no-store vieta di conservare. no-cache non significa "non cachare": significa che, prima di riusare, si deve rivalidare con l'origine. Confondere no-cache e no-store è l'errore più comune, e produce o cache inutili o cache pericolose.
L'ETag è un validatore. Il client (o la CDN) rimanda If-None-Match; se la rappresentazione non è cambiata, l'origine risponde 304 senza corpo. Utile su HTML che cambia di rado e su asset versionati male. Inutile se ogni risposta ha un ETag diverso perché ci hai messo un timestamp. Un validatore che cambia sempre è rumore.
Browser e CDN non cachano lo stesso oggetto
La cache del browser è, di default, privata rispetto agli altri utenti di internet. Sta sul dispositivo. Serve le visite successive di quella persona, su quell'installazione. Non aiuta la prima visita. Non aiuta chi arriva da un altro telefono. Se misuri solo utenti di ritorno con disco caldo, stai progettando per te stesso. Sulla performance di un sito conta anche, e soprattutto, chi non ha ancora niente in locale.
La cache della CDN è condivisa. Serve la stessa risposta a molti client. Per questo s-maxage e public/private non sono dettagli. Un HTML con cookie di sessione messo public su una CDN è una falla, non un'ottimizzazione. Un CSS con fingerprint nel filename, max-age lungo e immutable se lo usi, è il caso per cui la CDN esiste. DNS, origine e CDN sono tre strati: la mappa sta in DNS, hosting, CDN. Qui interessa solo la policy HTTP che quella CDN rispetta.
Il header Vary dice su cosa la cache deve distinguere le rappresentazioni. Vary: Accept-Encoding è normale. Vary: Cookie su HTML pubblico è spesso il modo di non cachare, o di cachare male. Se la risposta dipende dal cookie, o è private, o non passa dalla cache condivisa. Non si "sistema" con un TTL più corto.
Cosa non cachare in pubblico
HTML di checkout, account, carrello autenticato, aree riservate, risposte che contengono prezzi personalizzati o scorte legate alla sessione: non vanno in una cache condivisa. Cache-Control: private, no-store è il default onesto quando non hai dimostrato il contrario. "Dimostrato" vuole dire: la rappresentazione è identica per tutti, non dipende dal cookie, non contiene CSRF token legati all'utente, non mostra dati altrui se la cache sbaglia chiave.
Su un ecommerce lento la tentazione è cachare anche le schede per far scendere l'LCP. Le schede prodotto pubbliche, sì, con invalidazione quando cambia prezzo o disponibilità. Il checkout, no. Il mini-carrello in HTML full-page, no. Un fragment edge-side che "indovina" lo stato utente, solo se sai invalidare e isolare. Altrimenti stai servendo il carrello di ieri, o quello di qualcun altro.
Le API JSON seguono la stessa logica. Un GET di catalogo può essere pubblico. Un GET /me o /cart no. Authorization header, cookie di sessione, token: se la risposta li presuppone, la cache condivisa sta fuori. RFC 9111 è esplicita sul fatto che le richieste autenticate sono un caso speciale. Non serve una interpretazione creativa.
Quando la cache nasconde i bug
Il caso classico: rilasci un fix, l'origine è giusta, l'utente (o la CDN) continua a vedere la versione precedente. Non è "il browser dell'utente". È una policy che non prevede invalidazione. Asset senza fingerprint e max-age lungo: il JS vecchio resta. HTML con s-maxage e purge dimenticato: il banner della campagna resta dopo la fine. Debug in staging senza i layer di cache: in produzione il bug "non si riproduce" perché stai guardando una copia.
Altro caso: cache che maschera un'origine lenta. L'homepage è veloce, le URL rare no. Il TTFB buono è quello della cache. L'applicazione sotto è ancora lenta, e lo scopri al primo miss, al primo purge di massa, al primo crawl su URL profonde. La cache ha fatto il suo mestiere. Non ha reso sana l'origine. Per la performance va misurato anche il miss, non solo il hit.
Il terzo caso è di sicurezza, non di velocità: pagina privata finita in cache pubblica per un header sbagliato, un plugin "optimizer" che riscrive Cache-Control, un CDN con "cache everything" sul path sbagliato. Si vede tardi, e si vede brutto. Qui la cache non è un acceleratore. È un canale di fuga.
Errori da evitare
Cachare "tutto" e poi invalidare a caso. Prima la classificazione: statico immutabile, HTML pubblico, HTML privato, API autenticate.
Usare no-cache pensando di aver spento la cache. Hai chiesto una rivalidazione. Per non conservare serve
no-store.Mettere HTML di checkout o account in una CDN come se fosse una scheda prodotto. Non lo è.
Misurare solo il hit-ratio. Senza dire quale path, quale status, quale cookie, il numero non parla.
Dimenticare il miss e l'origine. Una cache piena su un'app lenta è un sipario, non una cura.
Cambiare HTML e lasciare JS/CSS con lo stesso URL e max-age lungo. I clienti restano sulla release precedente.
In sintesi
La cache HTTP è un contratto: cosa si può riusare, chi può riusarlo, per quanto, e come si capisce che è cambiato. RFC 9111 e Cache-Control bastano, se li usi alla lettera. Browser e CDN sono due cache diverse. Checkout e account non sono contenuti pubblici. Quando la policy è vaga, la cache non "ottimizza": serve ieri e lo chiama performance. Si scrive l'header, si prova il miss, si invalida di proposito. Il resto è rumore.
Fonti
IETF, RFC 9111 (HTTP Caching).
MDN, Cache-Control.
MDN, HTTP caching.
MDN, ETag.
web.dev, Prevent unnecessary network requests with the HTTP Cache.
Questo contenuto è stato realizzato con il supporto di sistemi di intelligenza artificiale e successivamente verificato secondo le linee editoriali della redazione.