Cache-Control è un'intestazione HTTP che definisce le regole di caching delle risorse lato client, server proxy e CDN utilizzando un insieme di direttive. A differenza dell'obsoleta intestazione Expires, Cache-Control supporta decine di combinazioni: max-age imposta la durata in secondi, private e public controllano la disponibilità della cache, no-cache e no-store — verifica forzata. Secondo Google Web Dev (2025), una corretta configurazione di Cache-Control può ridurre i tempi di caricamento delle pagine del 50-80% per le visite ripetute. Ciò rende l'intestazione criticamente importante per le prestazioni delle applicazioni web e mobili.
Punti chiave
Cache-Control è un'intestazione HTTP, standardizzata in HTTP/1.1 (RFC 7234), che consente al server di specificare come e per quanto tempo client, proxy e CDN possono memorizzare nella cache la risposta. A differenza di Expires (HTTP/1.0), Cache-Control utilizza direttive — comandi testuali combinati con virgole: Cache-Control: public, max-age=3600, must-revalidate. L'intestazione fornisce un controllo granulare su ogni anello della catena di caching.
Il caching è uno dei meccanismi fondamentali delle prestazioni delle applicazioni web e mobili. Senza di esso, ogni richiesta utente andrebbe direttamente al server, causando carico eccessivo e latenza. Cache-Control definisce tre livelli di cache: browser/applicazione (cache privata), server proxy (cache condivisa) e CDN (cache distribuita). Ogni livello interpreta le direttive in modo diverso.
Una configurazione errata di Cache-Control è una delle cause più comuni di problemi di prestazioni. Un caching troppo aggressivo fa sì che gli utenti vedano dati obsoleti. Un caching troppo debole porta a richieste eccessive al server e caricamento lento. Secondo Akamai (2025), ottimizzare Cache-Control per i contenuti statici riduce il carico del server del 70-90% e migliora il tempo di caricamento del 40-60% per gli utenti mobili.
Cache-Control è apparso in HTTP/1.1 (RFC 2616, 1999) come sostituto di Expires. Expires aveva un problema fondamentale: utilizzava una data assoluta che dipendeva dai fusi orari del server e del client. Cache-Control ha risolto questo problema passando al tempo relativo (max-age in secondi dal momento della ricezione della risposta). Successivamente, in RFC 7234 (2014), sono state aggiunte nuove direttive: immutable per le risorse statiche, stale-while-revalidate e stale-if-error per la convalida differita.
Cache-Control include più di 10 direttive suddivise in tre gruppi: direttive di richiesta (client → server), direttive di risposta (server → client) ed estensioni. In pratica, lo sviluppo mobile utilizza 6-7 direttive di risposta principali che coprono il 95% degli scenari di caching. Esaminiamo ciascuna con esempi e raccomandazioni.
| Direttiva | Significato | Esempio |
|---|---|---|
| max-age | Durata in secondi dal momento della risposta | max-age=3600 — 1 ora |
| s-maxage | max-age per cache condivisa (proxy, CDN) | s-maxage=86400 — 1 giorno per CDN |
| public | Consente il caching a tutti (inclusi i proxy) | public, max-age=3600 |
| private | Consente la cache solo per il browser/applicazione | private, max-age=600 |
| no-cache | Non usare senza convalida (304 richiesto) | no-cache |
| no-store | Vietare completamente il caching | no-store |
| must-revalidate | Dopo max-age, deve ricontrollare con l'origine | max-age=3600, must-revalidate |
| immutable | La risorsa non cambierà (per risorse statiche versionate) | max-age=31536000, immutable |
max-age è la direttiva più importante. Vieta al client di effettuare una richiesta al server per il tempo specificato. Per le risorse statiche (CSS, JS, immagini), max-age viene solitamente impostato da 1 giorno a 1 anno. Per le risposte API — da 0 secondi (dati sempre freschi) a 5-10 minuti (dati di riferimento). s-maxage consente di impostare durate diverse per CDN e browser: il CDN conserva una copia per 1 giorno, il browser per 1 ora.
Queste due direttive vengono spesso confuse. no-cache non vieta il caching — richiede di convalidare la copia cache a ogni utilizzo tramite una richiesta condizionale (If-Modified-Since o If-None-Match). Se il server risponde con 304 — il client usa la cache. Se 200 — la aggiorna. no-store, invece, vieta completamente di salvare la risposta in qualsiasi cache, incluso il disco e la memoria. Usa no-store solo per i dati sensibili — token, dati di pagamento, documenti personali.
L'intestazione Expires (HTTP/1.0) specifica anch'essa la durata della risorsa ma utilizza una data assoluta: Expires: Thu, 03 Jul 2026 12:00:00 GMT. Cache-Control max-age utilizza il tempo relativo dal momento della risposta. La differenza è critica per i sistemi distribuiti: se server e client si trovano in fusi orari diversi, Expires può essere interpretato erroneamente. Cache-Control non ha questo problema — 3600 secondi sono sempre 3600 secondi.
Quando entrambe le intestazioni sono presenti, Cache-Control ha priorità su Expires. Ciò è definito in RFC 7234: “Se una risposta include un campo Cache-Control con la direttiva max-age, il destinatario DEVE ignorare il campo Expires.” In pratica, si raccomanda di non restituire Expires per i client moderni, poiché Cache-Control copre tutti gli scenari di Expires. Tuttavia, per la compatibilità all'indietro con proxy e browser vecchi, entrambe le intestazioni possono essere restituite.
Expires è sopravvissuto principalmente per i contenuti statici su Nginx e Apache — questi server aggiungono automaticamente entrambe le intestazioni. Se il tuo progetto incontra Expires senza Cache-Control, sostituiscilo con Cache-Control con max-age: la precisione del controllo della cache migliora e la dipendenza dal fuso orario viene eliminata. Per la migrazione, è sufficiente configurare il server per aggiungere Cache-Control invece di Expires.
# Nginx: Cache-Control per file statici
location ~* \.(jpg|jpeg|png|gif|ico|css|js)$ {
expires 30d;
add_header Cache-Control "public, immutable, max-age=2592000";
}
# Politiche diverse per diversi tipi di contenuto
location /api/config {
expires -1;
add_header Cache-Control "no-cache, must-revalidate";
}
location /api/static-data {
expires 5m;
add_header Cache-Control "public, max-age=300";
}
Nella configurazione Nginx, i file statici (CSS, JS, immagini) vengono impostati con Cache-Control per 30 giorni con l'attributo immutable — questo attributo comunica al browser che la risorsa non cambia mai a questo URL (versionamento tramite hash nel nome del file). Gli endpoint API utilizzano no-cache per i dati dinamici e public con un max-age breve per i dati di riferimento — elenchi frequentemente richiesti e raramente modificati.
Nelle applicazioni mobili, Cache-Control gioca un ruolo speciale a causa dei limiti delle reti mobili: alta latenza, connessione instabile, limiti di traffico. Un caching appropriato consente di visualizzare i dati all'utente istantaneamente, anche offline, e di aggiornarli in background. OkHttp su Android e URLSession su iOS dispongono di sistemi di cache integrati che rispettano Cache-Control.
OkHttp utilizza CacheInterceptor, che legge Cache-Control dalla risposta e gestisce automaticamente il caching. Se il server ha restituito Cache-Control: max-age=3600, OkHttp non effettuerà una richiesta al server per un'ora. Dopo la scadenza di max-age, OkHttp invia una richiesta condizionale con If-Modified-Since e If-None-Match. Configurazione della cache in OkHttp: OkHttpClient.Builder().cache(Cache(directory, maxSize)).
fun createCachedClient(cacheDir: File): OkHttpClient {
return OkHttpClient.Builder()
.cache(Cache(cacheDir, 10L * 1024 * 1024))
.addNetworkInterceptor { chain ->
val response = chain.proceed(chain.request())
response.newBuilder()
.header("Cache-Control",
"public, max-age=300")
.removeHeader("Pragma")
.build()
}
.build()
}
Il codice crea un OkHttpClient con una cache di 10 MB e sovrascrive Cache-Control tramite NetworkInterceptor. Se il server non restituisce Cache-Control o utilizza Expires, l'interceptor aggiunge public, max-age=300 (5 minuti). L'interceptor rimuove l'intestazione obsoleta Pragma (HTTP/1.0) per compatibilità. Il caching su iOS funziona in modo simile tramite URLCache.shared con le impostazioni memoryCapacity e diskCapacity.
La direttiva stale-while-revalidate consente di mostrare all'utente una cache obsoleta mentre l'applicazione recupera i dati aggiornati in background. Ciò fornisce un effetto di risposta immediata: l'utente vede il contenuto immediatamente e, dopo un secondo, viene aggiornato alla versione corrente. Supportato da OkHttp a partire dalla versione 3.10 e URLCache su iOS 14+. Esempio: Cache-Control: max-age=3600, stale-while-revalidate=300 — 1 ora di cache aggiornata, poi 5 minuti di visualizzazione di dati obsoleti con aggiornamento in background.
Diversi tipi di risorse richiedono diverse strategie di caching. Vediamo le configurazioni ottimali per scenari tipici nello sviluppo mobile. Per i contenuti statici con hash nel nome del file (bundle.abc123.js), puoi impostare max-age fino a 1 anno con immutable. Per gli elenchi API raramente aggiornati (directory, categorie) — max-age da 5 minuti a 1 ora con stale-while-revalidate.
| Tipo di risorsa | Cache-Control | Spiegazione |
|---|---|---|
| Risorse statiche versionate | public, max-age=31536000, immutable | 1 anno, i file non cambiano (hash nell'URL) |
| Risorse statiche non versionate | public, max-age=86400, must-revalidate | 1 giorno con successiva ricontrollo forzato |
| API: dati di riferimento | public, max-age=600, stale-while-revalidate=60 | 10 minuti di cache + 1 minuto obsoleto |
| API: dati utente | private, max-age=60 | 1 minuto, solo per un utente specifico |
| API: dati sensibili | no-store | Divieto completo di caching |
| Pagine HTML | no-cache, must-revalidate | Convalida a ogni richiesta, 304 se invariato |
È importante ricordare la sicurezza: per le risposte contenenti dati personali dell'utente, imposta sempre private. Senza questa direttiva, un proxy pubblico (ad esempio, aziendale) può memorizzare nella cache la risposta e fornirla a un altro utente. Per i token di autenticazione e le informazioni di pagamento, usa no-store — nemmeno una cache privata dovrebbe memorizzare questi dati sul disco.
Per verificare la correttezza di Cache-Control, utilizza l'intestazione Age (da quanti secondi la cache è stata memorizzata) e X-Cache (hit/miss sul CDN). Nel browser — la scheda Network, la colonna Size mostra “from disk cache” o “304 Not Modified”. Se una risorsa dovrebbe essere memorizzata nella cache ma viene caricata ogni volta, verifica se il server sta aggiungendo Cache-Control: no-cache o Pragma: no-cache insieme alle tue direttive.
Domande frequenti
max-age si applica a tutte le cache (inclusi i browser), s-maxage si applica solo alle cache condivise (proxy, CDN). Se viene specificato s-maxage, il CDN ignora max-age e utilizza s-maxage. Ciò consente di impostare durate diverse per il browser e il CDN.
No, dopo aver inviato una risposta con max-age, il client non effettuerà una richiesta fino alla scadenza del timer. Per invalidare immediatamente la cache, devi modificare l'URL della risorsa (aggiungere versione/hash) e inviare notifiche push o messaggi WebSocket per un reset forzato.
La direttiva immutable (RFC 8246) comunica al browser che la risorsa non cambierà mai a questo URL. Il browser non tenta nemmeno di effettuare una richiesta condizionale durante l'aggiornamento della pagina — utilizza la cache fino alla scadenza di max-age. Funziona solo con file versionati.
Googlebot tiene conto di Cache-Control: un caching lungo accelera la scansione ripetuta. noindex con cache veloce è accettabile. no-store può rallentare l'indicizzazione perché Googlebot caricherà la pagina da zero ogni volta. Un max-age troppo breve aumenta il carico del server durante la scansione.
Tramite helmet o middleware: res.set('Cache-Control', 'public, max-age=3600'). Per i file statici, usa express.static con il parametro maxAge: express.static('public', {maxAge: '1y'}). Per le route dinamiche — individualmente in ogni gestore.
Riepilogo
Svilupperemo un'applicazione mobile chiavi in mano
IT Sectr crea applicazioni iOS e Android per startup e aziende dal 2017. Ti consulteremo e ti proporremo la soluzione migliore.
Leggi anche