# Cache-Control explained: headers, max-age and immutable

Cache-Control decides how long a browser and a CDN reuse a response. HTML with no-cache, hashed assets as immutable: the setup that holds.

> Page: https://ui.eneawork.it/blog/caching-http-header-guida
> Markdown: https://ui.eneawork.it/blog/caching-http-header-guida.md

*2026-11-01*

Cache-Control dice al browser e al CDN per quanto tempo possono riusare una risposta senza tornare al server. La configurazione che regge è una: HTML con no-cache, asset con hash nel nome e max-age=31536000, immutable.

Abbiamo misurato gli header di https://ui.eneawork.it il 1 novembre 2026 alle 14:17 UTC con curl -I. L'HTML risponde Cache-Control: no-cache con cf-cache-status: DYNAMIC; l'asset /assets/index-B90Ihhpp.js risponde public, max-age=31536000, immutable con cf-cache-status: HIT e Age: 1287; il font inter-400-latin.woff2 da 48.256 byte risponde immutable con Age: 78263. La domanda «What's the difference between Cache-Control: max-age=0 and no-cache?» vale 748 voti su Stack Overflow, letta lo stesso giorno con la Stack Exchange API. Il catalogo EneUI misura 111.985 componenti, 1.556 blocchi e 12.808 icone da 1.472 librerie, letti da /api/stats il 1 novembre 2026. Le definizioni vengono da MDN, licenza CC BY-SA 2.5.

## Le direttive che contano

## Il comando

curl -sS -o /dev/null -D - -H "Accept-Encoding: br, gzip" https://ui.eneawork.it/assets/index-B90Ihhpp.js

L'output mostra la risposta dell'edge, non quella del server: -D - stampa gli header e -o /dev/null scarta il corpo.

## Perché il nome del file decide tutto

Un file con hash nel nome cambia URL a ogni build, quindi può essere immutabile. Un file con nome stabile, come /css/app.css, deve scadere o essere invalidato dal CDN a ogni deploy.

if (/\.(woff2?|ttf)$/i.test(filePath)) { res.setHeader("Cache-Control", "public, max-age=31536000, immutable"); } else if (/\.(css|js|mjs)$/i.test(filePath)) { res.setHeader("Cache-Control", "public, max-age=2592000, stale-while-revalidate=604800"); } else { res.setHeader("Cache-Control", "no-cache"); }

È il codice che serve questo sito: font immutabili per un anno, CSS e JS senza hash per 30 giorni con rivalidazione in background, tutto il resto in no-cache. Il deploy deve invalidare il CDN per i file senza hash.

## no-cache non significa «non salvare»

Con no-cache il browser conserva la copia e chiede conferma al server. Con no-store non conserva nulla. Con max-age=0 la copia è subito vecchia e viene rivalidata: la differenza con no-cache è più formale che pratica.

## La limitazione

La misura descrive l'edge, non l'origine. Cloudflare ha risposto public, max-age=0 a una HEAD senza Accept-Encoding e no-cache alla GET con compressione: stessa URL, due header. Un HIT su un punto di presenza non dice nulla sugli altri.

## Dove continuare

Il catalogo EneUI indicizza 111.985 componenti e 1.556 blocchi con preview. Il record spotlight card è una card con immagine: se la cambi, l'URL resta e la cache va invalidata. Per la compressione leggi Brotli contro gzip, per i livelli di cache di Next.js i quattro livelli, per il peso del bundle un import che aumenta il bundle.

## FAQ

### Che differenza c'è tra no-cache e no-store?

no-cache permette di salvare la risposta ma impone la rivalidazione prima di riusarla. no-store vieta di salvarla, quindi è la scelta per i dati personali.

### Posso usare immutable su tutto?

No. immutable è sicuro solo se l'URL cambia quando cambia il contenuto, quindi serve un hash nel nome del file. Su un nome stabile blocca gli aggiornamenti per un anno.

### Serve ancora ETag con immutable?

Su un asset immutabile l'ETag resta innocuo ma inutile, perché nessun client lo userà. Sull'HTML l'ETag riduce i byte della rivalidazione, quindi conviene tenerlo.

### Meglio max-age o s-maxage?

s-maxage vale per le cache condivise, come un CDN, e ha la precedenza su max-age. Usa s-maxage quando il CDN deve conservare più a lungo del browser.

### Perché il browser rivalida anche con max-age lungo?

Succede con un reload forzato o quando la cache locale è piena e il browser elimina le voci. Con immutable il reload non produce richiesta, ma solo se la risposta precedente è ancora in cache.
