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.
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.
| State | Typical browser behavior |
|---|---|
| Fresh | Reuse the cached response without a network request when the cache rules allow it. |
| Stale and revalidatable | Send a conditional request to check whether the cached representation is still valid. |
| Stale and not reusable | Fetch 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
| Directive | Purpose |
|---|---|
| max-age | Specifies a freshness lifetime in seconds for a response. |
| no-cache | Allows storage but requires validation before reuse. |
| no-store | Instructs caches not to store the response. |
| public | Indicates that the response may be stored by shared caches even in situations where it might otherwise be restricted. |
| private | Indicates that the response is intended for a private cache, such as a browser, rather than a shared cache. |
| must-revalidate | Requires a cache to revalidate a stale response before reuse. |
| immutable | Indicates that a representation is not expected to change during its freshness lifetime. |
| s-maxage | Specifies 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=86400Here, 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.
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-cacheBy contrast, no-store tells caches not to store the response.
Cache-Control: no-storeHow 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/cssWhen 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 GMTOn 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 GMTIf 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.
| Situation | Network request | Response body downloaded |
|---|---|---|
| Fresh cache hit | Usually no request to the origin is required. | No |
| Revalidation with unchanged resource | Yes | No, 304 allows the cached body to be reused. |
| Resource changed | Yes | Yes, 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 GMTModern 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.
| Mechanism | Main purpose |
|---|---|
| Cache-Control | Defines how a response may be cached and how long it can remain fresh. |
| ETag | Identifies a representation for conditional validation. |
| Last-Modified | Provides a modification date that can be used for conditional validation. |
| Expires | Provides 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, immutableWith 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.
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.jsBuild 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-cacheA 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=60This 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.
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 type | Example | Typical purpose |
|---|---|---|
| Private cache | Browser HTTP cache | Store responses for one user's browser. |
| Shared cache | CDN or proxy cache | Reuse 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=3600Cache-Control: private, max-age=300The 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-storeThis 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.
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.
| Resource | Common caching approach |
|---|---|
| Hashed JS/CSS | Long freshness lifetime because content changes produce a new URL. |
| Images | Longer caching when filenames are versioned or content changes infrequently. |
| Fonts | Long-lived caching when assets are versioned. |
| HTML | Shorter freshness or revalidation when deployments change page content. |
| Personalized API response | Private 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-storeHow 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.