La richiesta più veloce è quella che non viene fatta. La cache HTTP permette al browser, e ai server intermedi come i CDN, di riutilizzare risposte già ricevute invece di scaricarle di nuovo. Configurata bene, rende istantanee le visite successive e riduce il carico sul server. Configurata male, produce il problema opposto e più fastidioso: utenti che vedono una versione vecchia del sito dopo un aggiornamento.
Due meccanismi: freschezza e validazione
La cache HTTP si basa su due idee distinte.
- Freschezza: per un certo periodo la risposta in cache è considerata valida e viene usata senza contattare il server.
- Validazione: scaduta la freschezza, il browser chiede al server "è cambiato qualcosa?". Se no, il server risponde
304 Not Modifiedsenza corpo, e il browser riusa la copia locale.
La validazione fa comunque una richiesta, ma molto leggera. La freschezza evita del tutto la richiesta.
L'header Cache-Control
È l'header principale per controllare la cache. Le direttive più importanti:
| Direttiva | Significato |
|---|---|
max-age=N |
la risposta è fresca per N secondi |
s-maxage=N |
come max-age, ma solo per cache condivise (CDN, proxy) |
no-cache |
si può memorizzare, ma va sempre validata prima dell'uso |
no-store |
non memorizzare affatto |
public |
può essere memorizzata anche da cache condivise |
private |
solo nella cache del browser dell'utente |
immutable |
il contenuto non cambierà mai durante la freschezza |
must-revalidate |
scaduta la freschezza, non usare la copia senza validarla |
stale-while-revalidate=N |
per N secondi usa la copia scaduta mentre la aggiorna in background |
L'equivoco più comune riguarda no-cache: non significa "non mettere in cache", ma "metti in cache e chiedi sempre conferma". Per vietare la memorizzazione si usa no-store.
Validazione con ETag e Last-Modified
Per validare, il server fornisce un identificativo della versione:
HTTP/1.1 200 OK
Cache-Control: no-cache
ETag: "5f3a-1b2c"
Alla richiesta successiva il browser lo rimanda:
GET /index.html
If-None-Match: "5f3a-1b2c"
Se l'ETag coincide, la risposta è 304 senza contenuto. In alternativa all'ETag esiste Last-Modified, con la richiesta condizionale If-Modified-Since, basato sulla data di modifica. Molti server e hosting statici generano entrambi automaticamente.
La strategia che funziona: asset con hash e HTML sempre validato
Il problema di fondo è questo: vuoi che CSS, JavaScript e immagini restino in cache a lungo, ma vuoi anche che un aggiornamento arrivi subito. La soluzione standard è il versioning tramite nome di file.
Gli strumenti di build generano nomi che contengono un'impronta (hash) del contenuto:
<link rel="stylesheet" href="/assets/style.3f9a2c1b.css">
<script src="/assets/app.8d41e0a7.js" defer></script>
Quando il file cambia, cambia anche l'hash e quindi l'URL. Il vecchio file in cache non viene più richiesto: l'HTML punta già a quello nuovo. Per questo gli asset con hash possono avere una cache lunghissima:
Cache-Control: public, max-age=31536000, immutable
L'HTML, invece, è il punto d'ingresso che contiene i riferimenti agli asset: deve essere sempre aggiornato. Per l'HTML conviene:
Cache-Control: no-cache
oppure una freschezza molto breve. Così ogni visita controlla se la pagina è cambiata (con una richiesta leggera grazie all'ETag) e, in caso di aggiornamento, scarica il nuovo HTML con i nuovi riferimenti.
Il caso degli asset senza hash
Se un file ha un nome fisso, come /logo.png o /style.css, una cache di un anno è pericolosa: dopo una modifica gli utenti continuerebbero a vedere la vecchia versione. Per questi file usa una freschezza moderata (per esempio qualche ora) unita alla validazione, oppure introduci il versioning.
Riepilogo per tipo di risorsa
| Risorsa | Cache-Control consigliato |
|---|---|
| HTML | no-cache (con ETag) |
| CSS/JS/font con hash nel nome | public, max-age=31536000, immutable |
| Immagini con nome fisso | public, max-age=86400 più validazione |
| Risposte API personali | private, no-cache oppure no-store |
| Dati sensibili (area riservata) | no-store |
Cache condivise e dati personali
Un CDN o un proxy serve la stessa copia a più utenti. Se una risposta contiene dati personali (il nome dell'utente, il contenuto del carrello) e viene memorizzata da una cache condivisa, un altro utente potrebbe riceverla. Per le risposte che dipendono dall'utente usa sempre private o no-store, e verifica la configurazione del CDN, che a volte applica regole proprie.
L'header Vary indica alle cache quali header della richiesta cambiano la risposta, per esempio Vary: Accept-Encoding per le versioni compresse. Usalo con parsimonia: valori come Vary: Cookie riducono drasticamente l'efficacia della cache condivisa.
Come verificare
- Negli strumenti per sviluppatori, scheda Rete, osserva la colonna dimensione: "memory cache" o "disk cache" indicano una risposta servita localmente, mentre
304indica una validazione riuscita. - Disattiva temporaneamente l'opzione che disabilita la cache negli strumenti per sviluppatori, altrimenti non vedrai il comportamento reale.
- Controlla gli header con
curl -I, anche per le risposte servite dal CDN.
In sintesi
- La cache combina freschezza (nessuna richiesta) e validazione (richiesta leggera con risposta 304).
no-cachesignifica "valida sempre",no-storesignifica "non memorizzare".- Dai agli asset con hash nel nome una cache di un anno con
immutable. - Mantieni l'HTML sempre validato, così gli aggiornamenti arrivano subito.
- Non permettere alle cache condivise di memorizzare risposte con dati personali.