Ctrl + K
Performance22 min read

How Browser Caching Works

A practical guide to browser caching, including fresh and stale responses, Cache-Control directives, ETag, Last-Modified, Expires, cache revalidation, cache invalidation and common caching mistakes.

Published: 2026-10-05

Browser caching allows a browser to reuse resources it has already downloaded instead of requesting them from a server every time a page is opened. This can significantly reduce network traffic, improve loading times and make websites feel much faster on repeat visits.

Caching is not simply a matter of storing a file and using it forever. The browser follows HTTP caching rules to determine whether a stored response is still fresh, whether it can be reused, whether it needs to be revalidated with the server, or whether a completely new response must be downloaded.

Understanding these rules is useful when working with JavaScript bundles, CSS files, images, fonts, API responses and other HTTP resources. It also helps explain why a deployed change may not immediately appear in a browser, why some requests return a 304 response, and why cache-control headers are an important part of web performance.

What Is Browser Caching?

Browser caching is the process of storing HTTP responses locally so that the browser can reuse them later. The stored response can contain the response body, HTTP headers and metadata required to determine whether the cached representation can still be used.

For example, suppose a browser downloads an image from /images/logo.png. If the server tells the browser that the response can be cached for a period of time, the browser may store that response. When the same URL is requested again while the cached response is fresh, the browser can use the stored response without downloading the image again.

The same basic mechanism can apply to HTML documents, stylesheets, JavaScript files, fonts, JSON responses and many other HTTP resources.

Why Does Browser Caching Matter?

Without caching, a browser would need to repeatedly transfer resources that often have not changed. A typical web page can request dozens or hundreds of resources, so avoiding unnecessary network requests can have a noticeable effect on performance.

  • Fewer network requests can reduce page loading time.
  • Previously downloaded resources can be reused immediately when they are still fresh.
  • Less data needs to be transferred between the browser and server.
  • Servers and CDNs can handle fewer unnecessary requests.
  • Repeat visits can become substantially faster.
  • Cached resources can sometimes remain available when network connectivity is temporarily poor.

Caching is especially valuable for resources that change infrequently, such as versioned JavaScript bundles, CSS files, fonts and images.

The Basic Browser Cache Lifecycle

When a browser receives an HTTP response, it can decide whether the response is cacheable and how long it can remain fresh. Later, when the same resource is requested, the browser checks its stored response and applies the relevant caching rules.

  • The browser requests a resource.
  • The server returns the resource together with HTTP headers.
  • The browser decides whether the response can be stored.
  • The response is stored in the HTTP cache when appropriate.
  • A later request asks for the same resource.
  • The browser checks whether the cached response is still fresh.
  • If it is fresh, the browser can reuse it without contacting the server.
  • If it is stale but can be revalidated, the browser can send a conditional request.
  • If the resource has changed, the server can return the new representation.

Fresh vs Stale Cached Responses

One of the most important concepts in HTTP caching is the difference between a fresh and a stale response.

A fresh response is one that the browser is allowed to reuse without contacting the origin server, according to the applicable cache rules. A stale response has exceeded its freshness lifetime and generally needs to be revalidated before it can be reused.

Stale does not necessarily mean unusable. A stale cached response can often be checked with the server using validators such as ETag or Last-Modified. If the server confirms that the resource has not changed, the browser can continue using its existing cached body.

StateTypical browser behavior
FreshReuse the cached response without a network request when the cache rules allow it.
Stale and revalidatableSend a conditional request to check whether the cached representation is still valid.
Stale and not reusableFetch a new response according to the applicable caching rules.

The Cache-Control Header

Cache-Control is the primary HTTP response header used to communicate caching instructions. It supports directives that control freshness, reuse, validation and whether a response may be stored.

HTTP/1.1 200 OK
Cache-Control: max-age=3600

Content-Type: text/css

body {
  font-family: sans-serif;
}

In this example, max-age=3600 tells a cache that the response can be considered fresh for 3,600 seconds, or one hour, relative to the response's applicable age.

Common Cache-Control Directives

DirectivePurpose
max-ageSpecifies a freshness lifetime in seconds for a response.
no-cacheAllows storage but requires validation before reuse.
no-storeInstructs caches not to store the response.
publicIndicates that the response may be stored by shared caches even in situations where it might otherwise be restricted.
privateIndicates that the response is intended for a private cache, such as a browser, rather than a shared cache.
must-revalidateRequires a cache to revalidate a stale response before reuse.
immutableIndicates that a representation is not expected to change during its freshness lifetime.
s-maxageSpecifies a freshness lifetime for shared caches and can override max-age there.

max-age and Browser Cache Lifetime

The max-age directive is one of the most common ways to define how long a response can remain fresh.

Cache-Control: max-age=86400

Here, the freshness lifetime is 86,400 seconds, equivalent to one day. If the cached response is still fresh when the browser needs the resource, the browser can generally reuse it without sending a request to the server.

💡 Use longer cache lifetimes for resources whose URLs change whenever their contents change. This makes aggressive caching much safer because a new filename or URL can represent a new version.

no-cache Does Not Mean Do Not Cache

One of the most common HTTP caching misunderstandings is treating no-cache as a synonym for no-store.

Cache-Control: no-cache does not mean that the browser cannot store the response. It means the stored response must be revalidated before reuse.

Cache-Control: no-cache

By contrast, no-store tells caches not to store the response.

Cache-Control: no-store
⚠️ Do not automatically replace no-cache with no-store. They have different purposes. no-cache is useful when a response may be stored but must be checked before reuse, while no-store is intended for responses that should not be stored by caches.

How ETag Enables Revalidation

ETag is an HTTP response header that provides a validator for a representation. A server can assign an identifier to a particular version of a resource.

HTTP/1.1 200 OK
ETag: "a1b2c3d4"
Cache-Control: no-cache

Content-Type: text/css

When the browser later needs to validate this cached response, it can send the ETag value using the If-None-Match request header.

GET /styles.css HTTP/1.1
Host: example.com
If-None-Match: "a1b2c3d4"

If the resource has not changed, the server can respond with 304 Not Modified. The browser can then continue using the cached response body instead of downloading the complete resource again.

HTTP/1.1 304 Not Modified
ETag: "a1b2c3d4"

This can save bandwidth even when a network request is still required for validation.

Last-Modified and If-Modified-Since

Last-Modified is another mechanism for validating a cached response. The server can indicate when a resource was last modified.

HTTP/1.1 200 OK
Last-Modified: Tue, 29 Sep 2026 06:00:00 GMT

On a later validation request, the browser can send the value through If-Modified-Since.

GET /app.js HTTP/1.1
If-Modified-Since: Tue, 29 Sep 2026 06:00:00 GMT

If the server determines that the representation has not changed since that time, it can return 304 Not Modified.

ETag and Last-Modified can both be used as validators. ETag is generally more precise because it identifies a representation rather than relying only on modification time.

What Is a 304 Not Modified Response?

A 304 Not Modified response tells a client that its cached representation is still valid and can be reused. The server therefore does not need to send the full response body again.

A 304 response is associated with conditional requests. It is not the same thing as the browser serving a resource entirely from its cache without contacting the server.

SituationNetwork requestResponse body downloaded
Fresh cache hitUsually no request to the origin is required.No
Revalidation with unchanged resourceYesNo, 304 allows the cached body to be reused.
Resource changedYesYes, the updated representation is returned.

Expires and HTTP Caching

Expires is an older HTTP caching mechanism that provides an absolute expiration date and time.

Expires: Tue, 29 Sep 2026 07:00:00 GMT

Modern applications generally prefer Cache-Control because it provides more flexible directives. Expires can still appear in responses for compatibility and legacy configurations.

Cache-Control vs ETag vs Last-Modified

These mechanisms solve related but different problems. Cache-Control primarily describes caching behavior and freshness, while ETag and Last-Modified provide validators that can be used during revalidation.

MechanismMain purpose
Cache-ControlDefines how a response may be cached and how long it can remain fresh.
ETagIdentifies a representation for conditional validation.
Last-ModifiedProvides a modification date that can be used for conditional validation.
ExpiresProvides an absolute expiration time for a cached response.

Caching Static Assets

Static assets are among the easiest resources to cache aggressively. JavaScript bundles, CSS files, images and fonts often remain unchanged for long periods after deployment.

A common strategy is to include a content hash in the filename. For example, instead of serving app.js for every deployment, a build system might produce a filename such as app.8f31c2.js.

Cache-Control: public, max-age=31536000, immutable

With content-hashed filenames, a long freshness lifetime is much safer because changing the source file results in a different URL. The browser does not need to guess whether app.8f31c2.js has changed; the next version can simply use a new filename.

💡 Content hashing and long-lived caching work particularly well together. The URL becomes the version identifier, while the cache can keep each immutable asset for a long time.

Why Cache Busting Is Needed

Imagine that a website always serves /app.js and gives it a cache lifetime of one year. If the JavaScript changes tomorrow, a browser that already has the old response may continue using it until the cached response becomes stale.

Cache busting solves this problem by changing the URL when the content changes.

/app.8f31c2.js
/app.3a91de.js
/app.c52e10.js

Build tools commonly generate these hashed filenames automatically. Query-string versioning such as /app.js?v=42 can also create distinct URLs, although filename hashing is a common approach for production asset pipelines.

Caching HTML Documents

HTML requires more careful caching because it can change frequently and often contains links to the current versions of CSS and JavaScript assets.

If an HTML document is cached too aggressively, a visitor may receive an outdated page even after a deployment. For many dynamic pages, short freshness periods or revalidation are more appropriate than a year-long cache lifetime.

Cache-Control: no-cache

A site can use other strategies depending on how its HTML is generated and delivered. Static sites with carefully managed deployment versions can use stronger caching in some situations, while frequently changing personalized HTML generally requires more conservative policies.

Caching API Responses

API responses can also be cached, but the correct policy depends heavily on whether the response is public, personalized or sensitive.

A public response that is identical for many users may be suitable for shared caching. A response containing account-specific information generally requires private caching rules or no-store behavior depending on the application's requirements.

Cache-Control: private, max-age=60

This tells a cache that the response is intended for a private cache and can remain fresh for 60 seconds. It is only an example; the correct policy should be based on the data and application's behavior.

⚠️ Never assume that an API response is safe to cache simply because it is returned through HTTPS. Authentication and encryption protect the connection, while caching controls determine where and how a response may be stored.

Private vs Shared Caches

A browser's HTTP cache is a private cache because it belongs to a particular user agent. Shared caches can sit between clients and an origin server, such as a CDN or proxy cache.

Cache typeExampleTypical purpose
Private cacheBrowser HTTP cacheStore responses for one user's browser.
Shared cacheCDN or proxy cacheReuse responses across multiple clients when allowed.

The distinction matters because a response containing user-specific information generally should not be reused by a shared cache for another user.

The Meaning of public and private

Cache-Control: public indicates that a response may be stored by shared caches when other caching requirements permit it. Cache-Control: private indicates that the response is intended for a private cache and should not be stored by a shared cache.

Cache-Control: public, max-age=3600
Cache-Control: private, max-age=300

The choice depends on whether different users can safely receive the same representation. A response containing personalized account information is fundamentally different from a public CSS file.

no-store for Sensitive Responses

Some responses should not be stored by caches. no-store is the directive used when the application wants caches not to store the response.

Cache-Control: no-store

This can be appropriate for certain highly sensitive responses, depending on the application's security requirements and threat model. Examples may include some authentication-related responses or pages containing particularly sensitive information.

What Happens When You Hard Refresh?

A browser's refresh controls can affect how cached resources are handled, but the exact behavior depends on the browser and the particular reload operation.

A normal reload does not mean that every resource must always be downloaded from scratch. The browser can still use HTTP caching mechanisms and may perform conditional requests. Developer tools also provide options for disabling cache while they are open, which is especially useful when debugging deployment or caching issues.

💡 When debugging a stale asset, check the browser's Network panel and inspect the actual response headers rather than assuming that a refresh completely bypassed caching.

How to Inspect Browser Cache Behavior

Browser developer tools make it possible to inspect caching-related request and response information. Open the Network panel, reload the page and select a resource such as a CSS file, JavaScript bundle or image.

  • Check the request URL and status code.
  • Inspect the response headers.
  • Look for Cache-Control, ETag, Last-Modified and Expires.
  • Check whether the request was served from memory or disk cache when the browser exposes that information.
  • Compare behavior after a normal reload and after cache-related developer-tool settings are changed.
  • Check whether a 304 response was returned during revalidation.

The exact labels shown by developer tools vary between browsers and browser versions, so the most useful information is usually the request status and HTTP headers.

Browser Cache Is Not the Same as CDN Cache

A browser cache and a CDN cache can both store HTTP responses, but they operate at different locations and have different purposes.

The browser cache is local to a particular client. A CDN cache is a shared infrastructure layer that can store responses closer to users and reuse them for many requests.

A request can therefore interact with several caching layers. A browser may have a cached response, while a CDN may independently have another cached copy. HTTP cache headers help communicate how responses should be handled across these layers.

Browser Cache vs Service Worker Cache

The browser's normal HTTP cache should also be distinguished from caches controlled by service workers.

The HTTP cache follows HTTP caching semantics and response headers. A service worker can intercept requests and implement application-specific caching logic through the Cache API and the Fetch API.

A progressive web application might therefore have both HTTP caching behavior and service-worker-controlled caching. These mechanisms should not be treated as interchangeable.

Memory Cache and Disk Cache

Developer tools sometimes distinguish between resources served from memory cache and disk cache. These are implementation details of the browser rather than separate HTTP protocols.

Memory cache can be very fast but is generally temporary. Disk cache persists data on local storage and can survive longer. The browser decides how resources are stored and managed internally.

Developers should therefore focus primarily on HTTP caching semantics such as freshness, validation and cache-control directives instead of depending on whether a particular resource happens to be stored in memory or on disk.

Cache Invalidation and Deployments

Cache invalidation is the process of ensuring that outdated cached representations are no longer used when newer content should be served.

One common deployment strategy is to make static assets immutable by giving them content-based filenames. HTML then references the new asset URL after deployment.

Another approach is to use shorter cache lifetimes or revalidation for resources whose URLs remain unchanged.

ResourceCommon caching approach
Hashed JS/CSSLong freshness lifetime because content changes produce a new URL.
ImagesLonger caching when filenames are versioned or content changes infrequently.
FontsLong-lived caching when assets are versioned.
HTMLShorter freshness or revalidation when deployments change page content.
Personalized API responsePrivate caching or no-store depending on sensitivity and application behavior.

Common Browser Caching Mistakes

  • Using no-store everywhere and losing the performance benefits of caching.
  • Assuming no-cache means that a response cannot be stored.
  • Giving an unversioned JavaScript or CSS URL a very long cache lifetime.
  • Caching personalized responses in a shared cache.
  • Changing server content without changing the URL while relying on a long freshness lifetime.
  • Ignoring ETag and Last-Modified when revalidation would reduce bandwidth.
  • Using cache rules without considering CDN or proxy behavior.
  • Assuming every refresh completely bypasses the HTTP cache.
  • Treating browser cache behavior and service-worker caching as the same mechanism.
  • Debugging stale content without inspecting the actual response headers.

A Practical Caching Strategy

A useful caching strategy starts by classifying resources according to how frequently they change, whether they are personalized and whether their URLs are versioned.

  • Version static assets when possible.
  • Use long cache lifetimes for content-hashed assets.
  • Use revalidation for resources that can change while keeping the same URL.
  • Use private caching for user-specific responses when caching is appropriate.
  • Use no-store when a response should not be stored.
  • Avoid long-lived caching for unversioned resources that change frequently.
  • Inspect real response headers when diagnosing cache behavior.
  • Test deployments with both cached and uncached clients.

Example Cache-Control Policies

The following examples illustrate common patterns. They are not universal rules; the correct policy depends on the resource and application.

# Versioned static asset
Cache-Control: public, max-age=31536000, immutable
# Response that may be stored but should be validated
Cache-Control: no-cache
# Private response with short freshness
Cache-Control: private, max-age=60
# Response that should not be stored
Cache-Control: no-store

How Cache Headers Affect Performance

The biggest performance benefit comes from avoiding unnecessary work. A fresh browser cache hit can eliminate the network request entirely. Revalidation still requires a network round trip, but a successful 304 response can avoid downloading the resource body again.

Longer freshness lifetimes can therefore improve repeat-visit performance, but they also increase the period during which a cached representation can be reused without checking the server. Versioned URLs help solve this trade-off for static assets.

Caching should therefore be designed together with the application's deployment and asset-versioning strategy rather than treated as a single header that can be added everywhere.

How to Debug a Stale Resource

When a browser appears to show an old CSS file, JavaScript bundle or image, start by checking whether the browser is actually using a cached response and then inspect the resource's HTTP headers.

  • Open the browser's Network developer tools.
  • Find the resource that appears outdated.
  • Check the request URL and whether the URL changed after deployment.
  • Inspect Cache-Control and other caching headers.
  • Check ETag and Last-Modified when present.
  • Determine whether the response was fresh, revalidated or downloaded again.
  • Check whether a CDN or reverse proxy is also caching the response.
  • Verify that your build process generates new URLs for changed static assets.

If the resource URL is unchanged and the server instructs the browser to keep it fresh for a long time, seeing an older version may be expected behavior rather than a browser bug.

Frequently Asked Questions

Does browser caching improve website speed?

Yes. A fresh cached response can be reused without downloading the resource again, reducing network traffic and improving repeat-visit performance.

What is the difference between no-cache and no-store?

no-cache allows a response to be stored but requires validation before reuse. no-store tells caches not to store the response.

What does a 304 Not Modified response mean?

It means the server determined that the cached representation is still valid. The browser can reuse its existing response body instead of downloading the full representation again.

What is ETag used for?

ETag provides a validator for a particular representation. Browsers can send it in If-None-Match requests so the server can determine whether the cached representation is still current.

How long should browser cache data be stored?

There is no universal lifetime. Versioned static assets can often use long freshness periods, while frequently changing or personalized resources generally need shorter lifetimes or revalidation.

Why does my browser still show an old JavaScript or CSS file?

The resource may still be fresh in the browser cache, a CDN may be serving an older response, or the URL may not have changed after deployment. Inspect the resource URL and HTTP cache headers to determine which layer is responsible.

Is browser cache the same as a service worker cache?

No. The browser HTTP cache follows HTTP caching rules, while a service worker can intercept requests and implement application-specific caching through the Cache API.

Helpful Caching Tools

When working with HTTP caching, a Cache-Control Generator can help build and inspect Cache-Control directives, while an HTTP Header Generator can be useful for constructing complete header sets. An HTTP Header Viewer helps inspect real request and response headers, which is especially useful when debugging cache behavior.

A MIME Type Detector can help verify the Content-Type associated with a cached resource, while an HTTP Response Formatter can make raw HTTP responses easier to inspect when troubleshooting headers, status codes and response bodies.

Conclusion

Browser caching is a core HTTP mechanism that lets browsers reuse previously downloaded responses instead of transferring the same resources repeatedly. The key concepts are freshness, revalidation, cache lifetime and cache invalidation.

Cache-Control defines how responses should be cached and how long they can remain fresh. ETag and Last-Modified can validate cached representations, while 304 Not Modified allows a server to confirm that a cached response can still be reused without sending its complete body again.

For modern web applications, a strong caching strategy usually combines appropriate cache lifetimes with versioned static assets. Long-lived caching works particularly well for content-hashed files, while HTML, personalized responses and frequently changing resources generally require more careful policies.

Once you understand the difference between a fresh cache hit, revalidation and a new download, browser caching becomes much easier to reason about and debug.

Found an issue?

Found an error, outdated information, or something missing from this article? Let me know through the Contact page.

Your feedback helps improve our articles and keep them accurate and useful.