A website may download the same CSS, images or data repeatedly even when the content has not changed. HTTP caching reuses earlier responses. To use it well, answer three questions: who may store a response, how long may it be reused, and when must a newer version be checked?
1. Where does the cache live?
A browser cache serves one user; a shared cache such as a CDN may serve many. A response suitable for the browser is not necessarily safe to share across the website. Account pages and company introductions need different policies. A request cookie alone does not guarantee that its response is treated as private. See MDN's HTTP caching guide.
The durations in this article are design examples. Choose them according to how much stale data the product can tolerate instead of applying one configuration to every route.
2. Read Cache-Control correctly
| Response directive | Practical meaning |
|---|---|
max-age=120 | The response is fresh while its cache age is below 120 seconds. |
s-maxage=600 | A shared cache uses a 600-second lifetime instead of max-age. |
private | Shared caches must not store it; browser caching may still be allowed. |
public | Permits shared storage subject to the other applicable conditions. |
no-cache | Storage is possible, but every reuse requires successful validation. |
no-store | Instructs caches not to store this response. |
No-cache does not mean no storage. That distinction matters when you want smaller transfers while still checking for updates. The definitions are in RFC 9111's response directives.
3. How does an ETag validate a version?
An ETag is a server-issued version label for a representation. A client can return it in If-None-Match. The label must change when the corresponding content version changes; never use one constant for every response. The ETag reference covers strong tags and weak tags prefixed with W/.
This abbreviated HTTP exchange represents a public category list with no user-specific data:
GET /api/public/categories HTTP/1.1
Host: demo.example
HTTP/1.1 200 OK
Cache-Control: public, max-age=120
ETag: "categories-v8"
Content-Type: application/json
{"items":["Guides","Programming"]}
When validation is needed, the client sends a conditional request:
GET /api/public/categories HTTP/1.1
Host: demo.example
If-None-Match: "categories-v8"
HTTP/1.1 304 Not Modified
Cache-Control: public, max-age=120
ETag: "categories-v8"
For GET, a matching tag can produce 304 Not Modified without a body. The client reuses its stored body. If the version changed, the normal result is 200 with the new body. A 304 still involves a network exchange, unlike serving a fresh browser-cache entry locally. See RFC 9110 on 304.
4. Choose a policy for each resource type
These are starting points for testing, not universal framework or CDN defaults:
- HTML that must be checked each time: try
Cache-Control: no-cachewith an ETag. - A public list that tolerates delayed updates: try
public, max-age=120, s-maxage=600. Account for the CDN's stale-data window when planning updates. - A personal page allowed in browser storage: consider
private, no-cache; authentication and authorization still need to work correctly. - Sensitive responses that should not be stored: use
no-storeand check CDN or service-worker configuration as well.
Do not mark an entire API public just to raise its cache-hit rate. Consult the Cache-Control reference for directive behavior.
A content-fingerprinted asset such as app.a8c31f.css can use public, max-age=31536000, immutable if every content change creates a new URL. The immutable directive indicates no changes during freshness; it cannot fix a deployment that overwrites that URL. See RFC 8246. Keep old asset versions available long enough for pages that still reference them.
5. Account for Vary and content variants
If one URL changes language according to Accept-Language, it may need Vary: Accept-Language. Vary distinguishes cached variants using request headers; it is not an authorization mechanism. Include only headers that actually affect the content, because excessive variants reduce reuse. See the Vary reference.
For a bilingual website, separate language URLs often make cached versions easier for operators to identify. Whatever design you choose, test both languages consecutively in the same environment to detect incorrect reuse.
6. Verify with a repeatable procedure
- Open DevTools Network and inspect response headers and response sources. Uncheck Disable cache to test normal caching; enable it when comparing against requests without browser caching. See the Chrome DevTools Network reference.
- Send an initial GET to a test endpoint you control and record its ETag.
- Repeat GET with that exact tag in If-None-Match. If the content is unchanged and the endpoint supports validation, expect 304.
- Change the test data and repeat the request with the old tag. Expect 200, an updated body and a new ETag.
- Use two independent accounts to verify that personal responses are never shared incorrectly.
Example commands for Bash or zsh; replace the URL and ETag with values from your test environment:
curl -sS -D - -o /dev/null 'https://demo.example/api/public/categories'
curl -sS -D - -o /dev/null -H 'If-None-Match: "categories-v8"' 'https://demo.example/api/public/categories'
These curl commands do not automatically store and reuse bodies like a browser. They inspect headers and status codes. A HEAD check alone does not demonstrate correct GET behavior.
7. Evaluate the result after deployment
Compare the same URL groups before and after the change: transferred bytes, latency, origin requests and stale-content incidents. Do not maximize 304 responses blindly; excessive validation still adds latency when fresh local reuse would suffice. If the server builds the entire body before calculating its ETag, fewer transferred bytes do not necessarily mean proportionally less computation.
Start with one group of public resources, verify both initial and repeat loads, then expand. A useful caching policy saves resources while meeting update expectations and keeping each user's data within the correct scope.




No comments yet. Be the first to share your thoughts.