Fetching resources over the network is both slow and expensive:
- Large responses require many round trips between the browser and the server.
- Your page won't load until all its critical resources have downloaded completely.
- If a user on your site has a limited mobile data plan, every unnecessary network request is a waste of their money.
How can you avoid unnecessary network requests? The browser's HTTP Cache is your first line of defense. It's not necessarily the most powerful or flexible approach, and you have limited control over the lifetime of cached responses, but it's effective, it's supported in all browsers, and it doesn't require much work.
This guide shows you the basics of an effective HTTP caching implementation.
Browser compatibility
The HTTP Cache is the general name for a collection of web platform APIs supported in all browsers:
Cache-Control
ETag
Last-Modified
How the HTTP Cache works
All HTTP requests the browser makes are routed first to the browser cache to check whether there's a valid cached response that can be used to fulfill the request. If there's a match, the response is read from the cache, which eliminates both the network latency and the transfer's data costs.
The HTTP Cache's behavior is controlled by a combination of request headers and response headers. In an ideal scenario, you have control over both the code for your web app, which determines the request headers, and your web server's configuration, which determines the response headers.
Refer to MDN's HTTP Caching article for a more in-depth conceptual overview.
Request headers: stick with the defaults (usually)
There are a number of important headers that should be included in your web
app's outgoing requests, but the browser almost always takes care of setting
them on your behalf when it makes requests. Request headers that affect checking
for freshness, like If-None-Match
and
If-Modified-Since
appear based on the browser's
understanding of the current values in the HTTP Cache.
This is good news: it means you can continue including tags like
<img src="my-image.png">
in your HTML, and the browser automatically takes
care of HTTP caching for you without extra effort.
Response headers: configure your web server
The part of the HTTP caching setup that matters the most is the headers that your web server adds to each outgoing response. The following headers all factor into effective caching behavior:
Cache-Control
- The server can return a
Cache-Control
directive to specify how, and for how long, the browser and other intermediate caches should cache the individual response. ETag
.- When the browser finds an expired cached response, it can send a small token (usually a hash of the file's contents) to the server to check if the file has changed. If the server returns the same token, then the file is the same, and there's no need to re-download it.
Last-Modified
- This header serves the same purpose as
ETag
, but uses a time-based strategy to determine if a resource has changed, as opposed to the content-based strategy ofETag
.
Some web servers have built-in support for setting those headers by default. Others leave the headers out entirely unless you explicitly configure them. The specific details of how to configure headers vary greatly depending on which web server you use, and you should consult your server's documentation to get the most accurate details.
To save you some searching, here are instructions for configuring a few popular web servers:
Leaving out the Cache-Control
response header doesn't disable HTTP caching!
Instead, browsers effectively
guess
what type of caching behavior makes the most sense for a given type of content.
Chances are you want more control than that offers, so you'll need to take the
time to configure your response headers.
Which response header values should you use?
There are two important scenarios that you should cover when configuring your web server's response headers.
Long-lived caching for versioned URLs
Suppose your server instructs browsers to cache a CSS file
for 1 year (Cache-Control: max-age=31536000
) but your designer
just made an emergency update that you need to implement immediately. How do
you notify browsers to update the "stale" cached copy of the file?
You can't, at least not without changing the URL of the resource.
After the browser caches the response, the cached version is used until it's no longer
fresh, as determined by max-age
or expires
, or until
it's evicted from the cache for some other reason, like the user clearing
their browser cache. As a result, different users might end up loading different
versions of the file when the page is constructed: users who just fetched the
resource use the new version, but users who cached an earlier (but still
valid) copy use an older version.
To get both client-side caching and quick updates, you can change the URL
of the resource and force the user to download the new response whenever its
content changes. Typically, you do this by embedding a fingerprint of the file, or a version
number, in its filename: for example, style.x234dff.css
.
When responding to requests for URLs that contain
"fingerprint" or
versioning information, and whose contents are never meant to change, add
Cache-Control: max-age=31536000
to your responses.
Setting this value tells the browser that when it needs to load the same URL anytime over the next year (31,536,000 seconds, the maximum supported value), it can immediately use the value in the HTTP Cache, without having to make a network request to your web server at all. That's great—you've immediately gained the reliability and speed that comes from avoiding the network!
Build tools like webpack can automate the process of assigning hash fingerprints to your asset URLs.
Server revalidation for unversioned URLs
Unfortunately, not all of the URLs you load are versioned. Maybe you can't
include a build step before you deploy your web app, so you can't add hashes to
your asset URLs. And every web application needs HTML files, which almost never
include versioning information, because no one will bother to use your web app
if they need to remember that the URL to visit is
https://example.com/index.34def12.html
. So what can you do for those URLs?
HTTP caching alone isn't powerful enough to avoid the network completely. (Don't worry—you'll soon learn about service workers, which provide additional support.) But there are a few steps you can take to make sure that network requests are as quick and efficient as possible.
The following Cache-Control
values can help you fine-tune where and how
unversioned URLs are cached:
no-cache
tells the browser that it must revalidate with the server every time before using a cached version of the URL.no-store
tells the browser and other intermediate caches (like CDNs) to never store any version of the file.private
: Browsers can cache the file but intermediate caches cannot.public
: Any cache can store the response.
See Appendix: Cache-Control
flowchart to visualize the process
of deciding which Cache-Control
value(s) to use. Cache-Control
can also
accept a comma-separated list of directives. See
Appendix: Cache-Control
examples.
Setting either ETag
or Last-Modified
can also help.
As mentioned in Response headers, ETag
and Last-Modified
both serve the same purpose: determining whether the browser needs to
re-download a cached file that has expired. We recommend using ETag
because
it's more accurate.
Assume that 120 seconds have passed since the initial fetch and the
browser has initiated a new request for the same resource. First, the
browser checks the HTTP Cache and finds the previous response.
Unfortunately, the browser can't use the previous response because it has
expired. At this point, the browser can dispatch a new request and fetch
the new full response. However, that's inefficient because if the resource
hasn't changed, then there's no reason to redownload the information
that's already in the cache.
This is the problem that ETag
validation tokens are designed
to solve. The server generates and returns an arbitrary token, which is
typically a hash or some other fingerprint of the contents of the file.
The browser doesn't need to know how the fingerprint is generated. It
only needs to send it to the server on the next request. If the
fingerprint is still the same, then the resource hasn't changed and the
browser can skip the download.
Setting ETag
or Last-Modified
, makes the revalidation request much more
efficient by letting it trigger the If-Modified-Since
or
If-None-Match
request headers mentioned in
Request headers.
When a properly configured web server sees those incoming request headers, it
can confirm whether the version of the resource that the browser already has in
its HTTP Cache matches the latest version on the web server. If there's a match,
then the server can respond with a 304 Not Modified
HTTP response,
which is the equivalent of "Hey, keep using what you've already got!" There's
very little data to transfer when sending this type of response, so it's usually
much faster than having to actually send back a copy of the actual resource
being requested.
Summary
The HTTP Cache is an effective way to improve load performance because it reduces unnecessary network requests. It's supported in all browsers and doesn't take too much work to set up.
The following Cache-Control
configurations are a good start:
Cache-Control: no-cache
for resources that should be revalidated with the server before every use.Cache-Control: no-store
for resources that should never be cached.Cache-Control: max-age=31536000
for versioned resources.
The ETag
or Last-Modified
header can help you revalidate expired cache
resources more efficiently.
Learn more
If you're looking to go beyond the basics of using the Cache-Control
header,
check out Jake Archibald's Caching best practices & max-age
gotchas guide.
See Love your cache for guidance on how to optimize your cache usage for return visitors.
Appendix: More tips
If you have more time, here are further ways to optimize your usage of the HTTP Cache:
- Use consistent URLs. If you serve the same content on different URLs, the browser fetches and stores that content multiple times.
- Minimize churn. If part of a resource (such as a CSS file) updates frequently, while the rest of the file doesn't (as with library code), consider splitting the frequently updating code into a separate file and using a short-duration caching strategy for the frequently updating code, and a long caching duration strategy for the code that doesn't change often.
- If some degree of staleness is acceptable in your
Cache-Control
policy, consider the newstale-while-revalidate
directive .
Appendix: Cache-Control
flowchart
Appendix: Cache-Control
examples
Cache-Control value |
Explanation |
---|---|
max-age=86400 |
The response can be cached by browsers and intermediary caches for up to one day (60 seconds x 60 minutes x 24 hours). |
private, max-age=600 |
The response can be cached by the browser, but not intermediary caches, for up to ten minutes (60 seconds x 10 minutes). |
public, max-age=31536000 |
The response can be stored by any cache for one year. |
no-store |
The response can't be cached and must be fetched in full on every request. |