Caching for Static Sites: A Practical Guide
Caching is the single most impactful performance optimization for static content sites—and the most commonly misconfigured. A well-cached site serves pages in milliseconds from the nearest edge node. A poorly cached site re-downloads identical content on every visit, wasting bandwidth and frustrating readers. This guide covers how HTTP caching works, the Cache-Control directives that matter, CDN edge caching strategies, and the invalidation patterns that let you update content without breaking the cache. You will get practical configurations, not abstract theory. For the broader performance picture, see our Fast Sites for Readers guide.
How HTTP Caching Works
HTTP caching is built into the protocol itself. When a browser requests a resource, the server responds with the content and cache-related headers. On subsequent requests, the browser checks its local cache: if the cached version is still valid, it uses it without contacting the server. If the cached version might be stale, it sends a conditional request to check. If there is no cached version, it fetches fresh content.
The elegance of this system is that it operates at every layer: browser cache, proxy cache, CDN edge cache, and origin server. Each layer can store and serve content independently, creating a hierarchy that dramatically reduces load on origin servers and latency for end users.
Cache-Control: The Essential Directive
The Cache-Control header is the primary mechanism for controlling caching behavior. For static sites, the most important directives are:
max-age=31536000— Cache this resource for one year. Used for fingerprinted assets (CSS, JS, images with hash in filename) that never change at a given URL.max-age=3600, must-revalidate— Cache for one hour, then check with the server before reusing. Good for HTML pages that update occasionally.no-cache— Always check with the server before using the cached version. The name is misleading—it does not prevent caching, it prevents using cached content without revalidation.public— Allow any cache (including CDNs and proxies) to store the response. Essential for CDN edge caching.immutable— Tell the browser this resource will never change at this URL. Eliminates revalidation requests entirely.
The Cloudflare Cache-Control documentation provides detailed explanations of each directive and how they interact with CDN edge caching specifically.
Caching Strategy for Static Content Sites
The optimal caching strategy for a static site follows a simple principle: cache aggressively what does not change, and cache carefully what does.
Assets with content hashes in their filenames (like main.a1b2c3d4.css) can be cached forever—literally. Set Cache-Control: public, max-age=31536000, immutable. The hash guarantees that when the content changes, the URL changes, so the old cached version is naturally abandoned.
HTML pages are different. They need to reflect content updates relatively quickly. A common pattern is Cache-Control: public, max-age=300, must-revalidate—cache for five minutes, then revalidate. For sites that update infrequently (like this one), you might extend that to an hour or even a day, using CDN cache purging to force updates when needed.
CDN Edge Caching
CDN edge caching adds a layer between your origin server and the end user. Edge nodes around the world store copies of your content. When a reader in Tokyo requests your page, they get it from a Tokyo edge node instead of a server in Virginia. The round-trip time savings can be dramatic—from 200ms to 10ms.
For static sites, CDN caching is especially powerful because the entire site can be served from the edge. There is no dynamic content generation, no database queries, no per-request computation. The CDN becomes your primary serving infrastructure, and the origin server is only contacted when the cache needs refreshing.
Cache Invalidation: The Hard Problem
Phil Karlton famously said there are only two hard things in computer science: cache invalidation and naming things. For static sites, cache invalidation is manageable if you design for it:
- Content-addressed assets: Filename hashes mean you never need to invalidate—new content gets new URLs.
- Purge-on-deploy: When you deploy updated HTML, purge the CDN cache for those specific URLs. Most CDNs offer API-driven purging.
- Short TTL + stale-while-revalidate: Set short max-age values for HTML, combined with stale-while-revalidate to serve cached content immediately while fetching a fresh version in the background.
The worst approach is setting long cache durations on HTML without any invalidation strategy. You will end up with readers seeing stale content for hours or days after updates, with no way to force a refresh.
ETags and Conditional Requests
ETags provide a fingerprint for cached resources. When the browser revalidates a cached response, it sends the ETag in an If-None-Match header. If the server's current ETag matches, it responds with 304 Not Modified—no body, just a confirmation that the cached version is still valid. This saves bandwidth for large resources where even the validation request would be expensive.
For static sites, ETags are usually generated automatically by the web server based on file metadata. They complement Cache-Control headers rather than replacing them—use both for robust caching behavior.
Service Workers and Offline Reading
Service workers add a programmable cache layer in the browser itself. For reading-focused sites, they enable offline access—readers can save articles for later, even without connectivity. The Cache API gives you fine-grained control over what to cache, when to update, and how to handle network failures.
The simplest service worker caching strategy for reading content is “network-first, cache-fallback.” Try the network for fresh content; if it fails, serve the cached version. This ensures readers always get the latest content when online while maintaining access when offline.
Practical Configuration Patterns
Here is a concrete caching configuration for a static content site deployed on a CDN:
- HTML pages:
Cache-Control: public, max-age=300, stale-while-revalidate=86400 - CSS/JS with hashes:
Cache-Control: public, max-age=31536000, immutable - Images:
Cache-Control: public, max-age=604800(one week) - Fonts:
Cache-Control: public, max-age=31536000, immutable
These values are starting points. Monitor your cache hit ratio and adjust based on your update frequency and reader behavior patterns.
Continue Reading
For the broader performance picture, see Fast Sites for Readers. To understand how HTML structure affects initial rendering and caching decisions, read HTML Basics for Reading. Explore our Web Development hub for more resources on building and optimizing web sites.
