Professor Sloth

Feature Release

Announcing Unified Web Performance: automatic lab testing, real user monitoring, and Google SEO scores.

The fastest website is the website that is already loaded, and that’s exactly what HTTP caching delivers. HTTP caching is a powerful technique that lets web browsers reuse previously loaded resources like pages, images, JavaScript, and CSS without downloading them again. Understanding HTTP caching headers is essential for web performance optimization, but misconfiguration can cause big performance problems.

In this guide, we’ll explore what HTTP caching is, which headers control HTTP caching, and how to cache static resources effectively—without requiring you to read hundreds of pages of the HTTP Caching Spec.

What is HTTP Caching?

HTTP caching is a technique that temporarily stores copies of web resources to reduce server load, bandwidth usage, and latency. When implemented correctly, caching creates a win-win scenario:

  • For users: Faster page loads and reduced data consumption
  • For servers: Decreased load and bandwidth costs
  • For businesses: Improved user experience and conversion rates

The caching process works through a series of HTTP headers that tell browsers and other intermediaries (like CDNs) whether to store a resource, how long to store it, and when to validate it’s still fresh. Let’s explore how these headers work together to optimize your web performance. 🚀

Speed Matters

Get your free speed check to see your website performance for real users.

Free speed test
Request Metrics Speed Check
Free speed check

Check how fast your website is for the real users that visit.

Check website
Request Metrics Speed Check

Which Headers Control HTTP Caching

HTTP caching is managed through a set of specialized response headers. Understanding these headers is crucial for implementing effective caching strategies:

Primary Caching Headers

  1. Cache-Control: The most powerful and flexible caching header
  2. Expires: An older but still supported way to specify cache duration
  3. ETag: Enables efficient validation of cached resources
  4. Last-Modified: Another validation mechanism based on modification time
  5. Vary: Controls how cached responses are matched to requests

Let’s examine each of these headers in detail and see how they work together.

How to Use the Browser Cache

The browser calculates “Cache Freshness” using headers in the HTTP response. Cache freshness is how long a cached asset is valid since it was downloaded. Freshness is calculated depending on which headers are returned.

The Cache-Control Header

The Cache-Control header has a number of directives to control caching behavior, but the most common is max-age. Max-age specifies how many seconds after download the cached resource is valid. Here’s an example:


# Cache this response for 10 minutes (600 seconds).
Cache-Control: max-age=600

Additional important Cache-Control directives include:

  • public: Response can be cached by any cache (browsers, CDNs, etc.)
  • private: Response can only be cached by the user’s browser
  • no-cache: Cache but revalidate before using
  • no-store: Don’t cache at all
  • must-revalidate: Verify resource is still valid when it becomes stale
  • immutable: Resource will never change while cached (prevents revalidation)

The Expires Header

The Expires header contains a date and time at which the cached resource should be marked stale, but only if you didn’t already use the max-age Cache-Control option. Expires is used to determine freshness if the response also contains a Date header for when the response was sent. Freshness is simply subtracting Date from the Expires time.


# This response can be cached for 1 hour (Expires - Date == freshness).
Expires: Tue, 09 Nov 2024 21:09:28 GMT
Date: Tue, 09 Nov 2024 20:09:28 GMT

Pro tip: While Expires is still supported, Cache-Control: max-age is generally preferred as it’s more precise and not dependent on clock synchronization.

The Browser’s Automatic Caching

Even if you don’t use the Cache-Control or Expires header, most web browsers will cache resources automatically and guess how long they will remain fresh. This guessing is referred to as “heuristic freshness”. Usually, the guess is based on the Last-Modified header included automatically by most web servers. But each browser implements this differently, so it’s dangerous to rely on it for your caching.

Some browsers assume a resource is “fresh” for 10% of the time since the resource was last modified.


# Freshness = 2 hours  (20 hours since last modified)
# (Date - Last-Modified) * 10% == freshness
Last-Modified: Tue, 09 Nov 2021 02:00:00 GMT
Date: Tue, 09 Nov 2021 22:00:00 GMT

Warning: Relying on heuristic freshness can lead to unpredictable caching behavior across different browsers and devices. Always set explicit caching directives for predictable performance.

Handling Expired Resources

What happens when a resource “expires”? This is referred to as a “stale resource”, and the browser must re-validate the resource from the server. In some cases, the browser can validate the resource without downloading it again. Otherwise, the browser re-downloads the entire resource and caches the new version.

There are a couple ways this validation can happen, depending on which HTTP Validation Headers are sent with your resources.

Validating With the ETag Header

The ETag header allows the browser to tell the server what version it currently has. The header contains a string which uniquely identifies the content, usually a checksum of the file.

When a resource with an ETag expires, the browser will send a validation request with a If-None-Match header containing the ETag value it already has. If the resource is unchanged, the server replies with an empty 304 (Not Modified) HTTP response. Otherwise, the server sends the resource like normal when the content has changed.


# In original resource response headers:
ETag: "123abc987654"

# Browser sends in the validation request headers:
If-None-Match: "123abc987654"

Validating With the Last-Modified Header

When an ETag is unavailable, web servers may send a Last-Modified header, with the last modified date of the source file. Similar to ETags, the browser can send that date in a validation request with the If-Modified-Since header to tell the server which version it has.

The server returns an empty 304 (Not Modified) response if the content has not changed since the date specified.


# In original resource response headers:
Last-Modified: Tue, 09 Nov 2021 20:00:00 GMT

# Browser sends in the validation request headers:
If-Modified-Since: Tue, 09 Nov 2021 20:00:00 GMT

No Validation

If the original resource had neither ETag nor Last-Modified headers, then the browser simply requests the entire resource and uses the result.

How to Cache Static Resources Effectively

Static resources like images, CSS, JavaScript, and fonts are perfect candidates for aggressive caching. Here’s how to implement optimal caching for different types of static assets:

Images and Media Files

Images often make up the largest portion of a webpage’s size. Caching them properly can dramatically improve load times:


# Cache images for a year
Cache-Control: public, max-age=31536000, immutable

The immutable directive tells browsers that this content will never change at this URL, which prevents unnecessary revalidation requests.

CSS and JavaScript Files

For CSS and JavaScript, use versioned filenames and aggressive caching:


# Cache for a year, CDN-friendly
Cache-Control: public, max-age=31536000, immutable

Fonts

Web fonts should be cached aggressively as they rarely change:


# Cache fonts for a year
Cache-Control: public, max-age=31536000, immutable

HTML Documents

For HTML documents, a more conservative approach is typically best:


# Cache HTML briefly or validate frequently
Cache-Control: public, max-age=300, must-revalidate

Busting the Browser’s Cache

When something changes, such as a new image, refreshed session, or an updated release of your code, you’ll want to invalidate (or bust!) the browser cache so that your users get the new stuff. If you’ve aggressively set caching headers, this can be challenging, but there are a couple ways to solve it.

1. Changing the URL to the Resource

The most common cache busting strategy is just to change the name of your resources when they change. This could be something like including a hash, version, or date in the filename when you build your site.

scripts.e7686eaf.min.js

Implementation tip: Most modern build tools (Webpack, Rollup, Parcel) can automatically add content hashes to filenames during the build process.

2. Adding a Query Parameter

If you can’t change the name of your resources, you can add a querystring parameter with a changeable key, like a version or date. When you change your site, or a resource, updating the querystring to a new value will invalidate all browser caches.

/my/images.png?v=2024119

If you have a look at the source of our page here, you’ll see what we use this strategy, adding a date representation of the build time to all our scripts and styles.

3. Using the Vary Header

The Vary header can be returned in resource responses and tells the browser when a resource should be cached as a unique variation of the resource. It does this by specifying one or more headers to use as a unique key.

The browser will never be able to use its cached responses if the header values change on every request. Vary is often omitted entirely, and should be used carefully when needed.


# Good: A common value that should not impact caching
# Caches gzip vs non-gzip responses separately
Vary: Accept-Encoding

# Bad: Probably not what you want.
# Any change to X-App-Version will invalidate your cache!
Vary: X-App-Version

Common HTTP Caching Mistakes to Avoid

Even experienced developers can make mistakes with HTTP caching. Here are some pitfalls to watch out for:

1. Over-Caching Dynamic Content

Setting long cache times on content that changes frequently can lead to stale content being shown to users.

2. Under-Caching Static Content

Not caching static resources aggressively enough means browsers download the same files repeatedly, wasting bandwidth and slowing down the experience.

3. Forgetting Cache-Busting Mechanisms

Without proper cache-busting, users may never see your updated resources after deployment.

4. Misunderstanding no-cache vs no-store

Remember: no-cache means “revalidate before using” while no-store means “don’t cache at all.”

5. Ignoring the Vary Header

Without proper Vary headers, cached responses might be incorrectly served for different request contexts.

Testing Your HTTP Cache Configuration

Verifying that your caching strategy works as intended is crucial. Here are some tools and techniques to help:

1. Browser Developer Tools

Chrome, Firefox, and other modern browsers include Developer Tools that show how resources are being cached:

  1. Open DevTools (F12 or Ctrl+Shift+I)
  2. Navigate to the Network tab
  3. Look for the “Size” column - you’ll see “(from disk cache)” or “(from memory cache)” for cached resources

2. Online HTTP Header Checkers

Check how your caching is configured right now! We made a neat tool that checks your HTTP cache settings.

3. Command Line Testing

You can use curl to inspect headers directly:


curl -I https://example.com/style.css

HTTP Caching Recipes

Different resources are cached differently. Here’s how to accomplish a few common caching scenarios.

1. Never Cache Anything!

Some resources are dynamic or time sensitive and should never be cached. This will force the browser to re-download resources each and every time the user loads the page. Force the browser to always makes a request:

Cache-Control: no-store

2. Cache, But Always Revalidate

Some resources are cacheable, but change often enough that they should be re-validated before use. We can accomplish this with the confusingly named no-cache directive. The browser will request an updated version of the resource, but will accept a 304 (Not Modified) response to save download bandwidth.


Cache-Control: no-cache

# no-cache is equivalent to:
Cache-Control: max-age=0, must-revalidate

3. Cache For A Day

Some resources change, but do so slowly. Setting a “just right” max-age on these allows them to be cached but updated in a timely manner when changed. Don’t depend on max-age alone if it’s critical that the browser immediately uses a new version, use a Cache-Buster!

Cache-Control: max-age=86400

4. Cache “Forever”

You probably don’t want to do this unless you are using a cache-busting strategy. There isn’t actually a “forever” cache directive, but you can get close enough by specifying a very large max-age.


# Cache this resource for a year
Cache-Control: max-age=31536000

Frequently Asked Questions About HTTP Caching

How long should I cache my resources?

The optimal cache duration depends on how frequently your content changes. Static assets like images, CSS, and JavaScript that are versioned can be cached for a year or more. Dynamic content might be cached for minutes or hours.

Does HTTPS affect HTTP caching?

HTTPS doesn’t prevent caching, but some directives like public and private become more important when using encrypted connections.

How can I force an update when content changes?

The most reliable approach is to change the URL of the resource, either by changing the filename or adding a version parameter to the URL.

What’s the difference between browser cache and CDN cache?

Browser caching occurs on the user’s device, while CDN caching happens on distributed servers. Both are controlled by HTTP headers, but they may interpret those headers differently.

Can I cache API responses?

Yes, but carefully. Short cache times or validation-based caching is usually best for API responses unless they’re truly static.


Conclusion: Mastering HTTP Caching for Optimal Performance

Proper HTTP caching is one of the most powerful tools in your web performance toolkit. By understanding HTTP caching headers and implementing appropriate caching strategies for different resource types, you can dramatically improve your website’s performance metrics:

  • Reduced page load times
  • Lower bandwidth usage
  • Decreased server load
  • Improved Core Web Vitals scores
  • Better user experience and conversion rates

Remember, the right caching strategy depends on your specific content needs - there’s no one-size-fits-all solution. The key is finding the right balance between freshness and performance.

Ready to see how your caching strategy affects your real-world performance? Request Metrics provides detailed monitoring of your website’s performance, including cache hit rates and load times across different user segments. Start your free trial today and discover how effective caching can transform your website experience.