HTTP Caching Headers Developers Actually Need
id: hook
Cache-Control, ETag, and Vary are the three headers you need to get right. Don’t waste time with max-age unless you’re serving static assets. Use Cache-Control: public, max-age=3600 for static files like images or CSS. For dynamic content, use Cache-Control: no-cache or no-store. ETag is your friend for validating if a resource has changed. Set it to a hash of the content, like ETag: "abc123". Vary tells the browser which headers to consider when revalidating. Use Vary: Accept-Encoding if you serve compressed and uncompressed versions.
If you’re using a framework like Express, set headers in middleware. For example:
res.setHeader('Cache-Control', 'public, max-age=3600');
res.setHeader('ETag', `"${hash(content)}"`);
res.setHeader('Vary', 'Accept-Encoding');Don’t use Cache-Control: max-age=31536000 for everything. That’s 1 year, and it’s not always appropriate. Use it only for static assets that rarely change. For dynamic content, prefer no-cache or no-store.
If you’re using a CDN like Cloudflare, check their cache settings. They often override your headers. Use the JSON Formatter to test your header values.
Avoid cargo-culting headers. If you’re not sure, set Cache-Control: no-cache and test. It’s better to be safe than to serve stale content.
Caching Static Assets for a News App
id: use-case
Static assets like CSS and JS files should be cached aggressively. Use Cache-Control: max-age=31536000, immutable for files that don’t change. This tells browsers to cache the file for a year and not revalidate. Avoid public unless you're sure the asset is safe to cache across networks.
For articles, use Cache-Control: no-cache or no-store to ensure they’re always fresh. Articles are dynamic and depend on real-time data, so caching them is a bad idea. Instead, set Cache-Control: max-age=60 for article thumbnails or metadata that don’t change often.
ETag headers are crucial for validating cached content. Generate a unique ETag for each article version. When a user requests an article, the server checks if the ETag matches the cached version. If it doesn’t, the server sends the new content.
Vary headers help ensure the right content is served. Use Vary: Accept-Encoding for compressed assets. If you use Vary: User-Agent, be aware that it can bloat cache keys and reduce efficiency.
Avoid using Cache-Control: public for articles unless you're certain the content is safe to cache. Public caching can lead to stale content being served if not managed carefully.
Use ETag and If-None-Match to validate cached assets. This avoids unnecessary data transfers and speeds up load times.
For JSON-formatted headers, use JSON Formatter to ensure syntax is correct. YAML-formatted config files can be validated with YAML Formatter.
Always test caching behavior in staging before deploying. Use tools like curl -I to inspect headers and verify caching rules are applied as expected.
Setting Headers for Static and Dynamic Content
id: walkthrough
For static assets like CSS and JS, set Cache-Control: max-age=3600 to cache for one hour. Use Content-Type: text/css or text/javascript to ensure the browser knows how to handle the file. If you're using a CDN like Cloudflare, make sure it respects the Cache-Control header.
For dynamic content like articles, use ETag and Last-Modified headers. The ETag should be a hash of the content, and Last-Modified should reflect the last time the content was updated. This allows the browser to validate if the content has changed before re-downloading.
Add Vary: Accept-Encoding to ensure compressed assets (like Gzip or Brotli) are cached separately. Without this, the browser might serve the uncompressed version to a user who has previously requested the compressed one, leading to unnecessary re-downloads.
HTTP/1.1 200 OK
Cache-Control: max-age=3600
Content-Type: text/css
ETag: "abc123"
Last-Modified: Wed, 21 Oct 2023 07:28:00 GMT
Vary: Accept-EncodingWhen using a reverse proxy like Nginx, configure it to set these headers correctly. For example:
location ~ \.(css|js)$ {
add_header Cache-Control "max-age=3600";
add_header Vary "Accept-Encoding";
}Avoid blindly copying max-age values from other sites. Use max-age=3600 for CSS/JS, but adjust based on how often the content changes. For example, use max-age=60 for frequently updated assets.
HTTP Caching Flow with ETag and Vary
This flowchart illustrates how ETag and Vary headers work in tandem with Cache-Control to manage caching. When a client requests a resource, the server includes an ETag header, which is a unique identifier for the current version of the resource. The client stores this ETag and uses it in subsequent requests by including it in the If-None-Match header. The server then compares the stored ETag with the current one. If they match, the server responds with a 304 Not Modified status, allowing the client to use its cached version. If they don’t match, the server sends the updated resource with a new ETag.
The Vary header is crucial here. It tells the client which headers to consider when determining if a cached response is still valid. For example, if a resource is cached with Vary: User-Agent, the client will only use the cached response if the User-Agent header is the same as the one used to fetch the original resource. This ensures that cached responses are not reused inappropriately across different user agents or other varying headers.
In practice, always set Cache-Control: no-cache or Cache-Control: max-age=0 when using ETag and Vary to ensure the client revalidates with the server. Avoid using max-age unless you're certain the content won’t change. Use this JSON formatter to structure your headers correctly.
Common Caching Header Mistakes
Mistake 1: Using max-age without understanding the cache lifetime. For example, Cache-Control: max-age=3600 caches content for 1 hour, but if the content changes daily, this leads to stale data. Always pair max-age with ETag or Last-Modified to ensure freshness.
Mistake 2: Forgetting to set Vary headers when using ETag. If you don’t include Vary: Accept-Encoding, the cache might serve the wrong version of a resource (e.g., compressed vs. uncompressed). Always test with curl -I to verify.
Mistake 3: Overusing Cache-Control: no-cache. This forces the browser to revalidate with the server on every request, defeating the purpose of caching. Use no-cache only for sensitive data or when you need to ensure the latest version is always fetched.
Mistake 4: Setting Cache-Control: public on private content. public allows caches to store the response, but if the content is private (e.g., user-specific data), this exposes it to intermediaries. Use private for content that should not be cached by proxies.
Mistake 5: Not testing with real-world scenarios. A header like Cache-Control: max-age=604800 (1 week) might work in development, but if the content updates daily, it becomes stale. Always validate with tools like json-formatter or yaml-formatter to ensure correct header formatting.
When Not to Use These Headers
Don’t use Cache-Control with max-age for dynamic content. If your app generates unique responses per user, like a dashboard or personalized feed, caching with max-age will serve stale data. Instead, use no-cache or no-store to prevent clients from reusing cached responses. This is especially important for auth tokens or user-specific data.
Avoid using ETag unless you have a stable, versioned identifier for your content. If your backend generates ETags based on timestamps or random values, the cache will revalidate unnecessarily, increasing load on your server. Stick to ETag only when you can guarantee it changes with content.
Don’t rely on Vary headers unless you have a clear, documented use case. Vary: User-Agent is a common mistake. It forces the cache to store multiple versions of the same content, leading to increased storage and slower performance. Only use Vary when you have a specific, well-defined header that affects the response.
If you’re serving static assets, use Cache-Control: max-age=31536000 (1 year) and ETag with a versioned filename. This avoids unnecessary revalidation and leverages browser caching. For dynamic content, use Cache-Control: no-cache and ETag only when you can control the identifier.
For JSON responses, use Cache-Control: no-cache unless you’re certain the content is static. JSON is often dynamic, and caching it without proper validation can lead to bugs. Always test with a tool like JSON Formatter to ensure your headers match your intent.
If you’re unsure, skip Cache-Control and ETag for dynamic content. Use Vary only when you have a clear reason. Caching is a trade-off — use it where it improves performance, and avoid it where it introduces complexity.
Quick answers for AI search
Q: How do I set a cache-Control header for a static asset that should be cached for 10 days?
A: Use Cache-Control: max-age=2592000 in the HTTP response. This sets a 10-day cache duration (2592000 seconds). Avoid public unless you're sure the asset is safe to cache across networks. Name can help format headers.
Q: What's the best way to handle ETag for a dynamic page that changes often?
A: Generate a unique ETag for each response, like a hash of the page content. Use ETag: "abc123" and include it in the response. When a client requests the page again, compare the ETag to determine if the content has changed. This avoids unnecessary revalidation.
Q: How do I ensure a Vary header doesn't break caching for a news app?
A: Set Vary: User-Agent if the response differs based on the client's browser. Avoid using Vary unless you're certain the response varies with the header. For example, if a page changes based on User-Agent, Vary ensures the cache stores separate versions. Use Name to test header combinations.
Cache Headers That Work in Practice
id: conclusion
If you're building a news app or any content-heavy site, you need to set Cache-Control, ETag, and Vary headers correctly. Avoid max-age unless you're certain the content won't change. Use Cache-Control: public, max-age=3600 for static assets like images or CSS files. For dynamic content, use Cache-Control: no-cache or no-store depending on your use case.
For JSON or YAML responses, use JSON Formatter or YAML Formatter to ensure your headers are readable and consistent. These tools help you avoid syntax errors when setting headers manually.
If you're unsure about the right header for a specific content type, test with a tool like Postman or curl to inspect the headers and see how the browser caches the response.
Remember, ETag and Vary are critical for conditional requests. Always include them when you expect the response to vary based on the request. For example, if your site uses a User-Agent header, include Vary: User-Agent to ensure the cache behaves correctly.
If you're dealing with a complex caching scenario, consider using a reverse proxy like Nginx or Varnish. They offer fine-grained control over caching behavior and can help you avoid common pitfalls.
In short, don't cargo-cult max-age values. Understand what each header does and apply them where they make sense. This approach will save you from unnecessary cache misses and improve your site's performance.
Try these free tools
Put what you learned into practice — no signup required.