A slow website is not always caused by an underpowered server. Often, browsers and networks are simply re-downloading the same files repeatedly: logos, CSS, JavaScript, images, or unchanged API responses. This is where HTTP caching comes into play.
HTTP caching is a temporary storage mechanism that allows browsers, intermediary servers, or CDNs to reuse responses that have been previously received. When used correctly, caching can reduce load times, save bandwidth, and decrease server load. However, overly aggressive caching can cause users to see outdated data, while caching at the wrong endpoints can pose privacy risks.
Caching is more than just "storing files"
Imagine you frequently borrow the same book from the library. Instead of going to the library every time, you keep a copy on your desk. As long as the book hasn’t changed, you can read it directly. HTTP caching works on a similar principle.
When a browser requests a resource, such as /assets/app.css, the server can inform how long that resource can be reused. On subsequent visits, the browser does not need to re-download as long as the copy is still considered valid.
Caching can occur in several places:
- Browser cache, which is storage on the user's device.
- CDN or reverse proxy, which stores responses closer to visitors.
- Application cache, such as query results or frequently used data on the server side.
All three can speed up a website, but each has different expiration rules and risks.
Understanding the Cache-Control header
The main caching settings for HTTP are usually sent via the Cache-Control header. This header instructs browsers and intermediary caches on how a response can be stored.
A simple example:
Cache-Control: public, max-age=3600This means the response can be stored by public caches and is considered valid for 3,600 seconds or one hour. After that, the cache needs to check back with the server.
Some important directives include:
public: the response can be stored by public caches like CDNs.private: the response can only be stored by the user's browser, not shared caches.no-store: the response must not be stored. Suitable for highly sensitive data.no-cache: the response can be stored but must be validated with the server before reuse.max-age: the maximum duration, in seconds, before the response is considered expired.
The difference between no-cache and no-store can often be confusing. no-cache does not mean "do not store"; the browser can still store a copy but must request validation before using it. Meanwhile, no-store requests that the response not be stored at all.
Distinguishing static files from dynamic data
A common mistake in caching configuration is treating all responses with the same rules. Static files and personal data should have different policies.
Static files are suitable for long caching
Files like images, fonts, CSS, and JavaScript are generally safe to have a long cache duration, especially if their filenames use versions or hashes.
<script src="/assets/app.8f31c2.js"></script>If the content of the JavaScript changes, the filename also changes. The browser will treat it as a new file and download the latest version. This pattern is called cache busting.
A common header for versioned assets looks like this:
Cache-Control: public, max-age=31536000, immutableWith this rule, the browser can store the file for up to one year. The immutable directive signals that the file will not change as long as its URL remains the same.
User data requires more caution
Responses such as user profiles, order histories, dashboard pages, and API results that depend on login sessions should not be carelessly stored by public caches.
Cache-Control: private, no-cacheFor highly sensitive information, use:
Cache-Control: no-storePractically, checkout pages, authentication tokens, account details, and health data require special attention. Do not rely solely on seemingly safe URLs. Also check cookies, authentication headers, and the content of the responses.
Validation with ETag and Last-Modified
Caching does not always require downloading the entire file when its validity expires. The server can provide version markers using ETag or modification times via Last-Modified.
ETag: "article-42-v3"
Last-Modified: Tue, 22 Sep 2026 09:30:00 GMTWhen checking again, the browser sends these values through headers like If-None-Match. If the content has not changed, the server can respond with a 304 Not Modified status without resending the file's content.
The result is not only faster but also more bandwidth-efficient. This is particularly useful for large files that rarely change.
Common caching issues
Users still see old versions
This usually happens because the URL name remains the same while the file content changes. The solution is to add a version to the filename or query string, for example, app.css?v=4. For cleaner projects, use automatic hashes from the build process.
User data gets mixed up
This can occur if pages that should be private end up in public caches. Review the use of public, session cookies, and CDN configurations. User-based data generally needs to use private or no-store.
Changes are not immediately visible on the CDN
The CDN may still store an old copy even if the origin server has been updated. You need a purge or cache invalidation mechanism. However, do not make purging a solution for every deployment. Cache busting through filenames is usually more predictable.
What you can do now
- List all types of responses on the website: static assets, public pages, APIs, and personal data.
- Determine which can be safely stored long-term and which must always be validated.
- Add versions or hashes to CSS and JavaScript files.
- Use
privateorno-storefor responses related to accounts and sessions. - Check response headers using browser DevTools or commands like
curl -I https://example.com/assets/app.css. - Test after deployment: open pages as different users and ensure no content is mixed up.
A good cache is a predictable cache
The goal of caching is not just to make performance numbers look good. A good cache helps a website feel fast without sacrificing data accuracy and user privacy.
Start with a simple separation: static assets can be stored long if their URLs are versioned, while dynamic data must be validated or not stored at all. After that, measure the results and document the policies. With this approach, caching shifts from a source of mysterious problems to a controllable part of the website architecture.
– Rio Yotto @rioyotto
