What Is Browser Caching and How to Set It Up
Browser caching lets visitors reuse files instead of re-downloading them. What it is, the HTTP headers that control it, cache-busting, and how to verify it.
The fastest file is the one the browser already has. Every asset a returning visitor can reuse from their local cache is a file that does not have to cross the network again — saving time, bandwidth and energy. Browser caching is the mechanism that makes this possible, and it is one of the highest-leverage, lowest-effort performance wins available, yet it is frequently misconfigured or skipped entirely. In short: browser caching lets visitors' browsers store and reuse your files instead of re-downloading them, it is controlled by HTTP headers (chiefly Cache-Control), you cache static versioned assets aggressively and dynamic content cautiously, and you use fingerprinted filenames so you can cache for a year yet still ship updates instantly. This guide explains how it works, the headers that control it, and how to verify it is actually happening.
It is one of the big levers in how to make your website load faster, explained in full here.
What browser caching is
When a browser loads a page for the first time, it downloads everything: the HTML, the stylesheets, the scripts, the fonts and the images. Browser caching lets the browser keep local copies of those files so that, on subsequent page loads and return visits, it can reuse them instead of fetching them again over the network. The browser stores them on disk (or in memory), and as long as a cached file is still considered "fresh," the browser serves it locally — instantly, with no network request at all.
The impact is concentrated on repeat visits and multi-page sessions. A first-time visitor with an empty cache downloads everything, but as they navigate to a second and third page, shared assets like the site's CSS, JavaScript and logo are already cached, so each subsequent page loads far faster. And when they return tomorrow, much of the site may load almost entirely from cache. This is why the "repeat view" in performance tools is usually dramatically faster than the "first view" — caching is doing the work.
How the browser decides what to cache
The browser does not guess. The server tells it, via HTTP response headers sent with each file, how the file may be cached and for how long. Two related questions are answered by these headers:
- Freshness: how long can the browser use the cached copy without checking back? (Controlled mainly by
Cache-Control: max-ageand the olderExpires.) - Validation: when the cached copy is stale, how can the browser cheaply check whether it is still valid rather than re-downloading it? (Controlled by
ETagandLast-Modified.)
Get these right and a returning visitor reuses everything that has not changed, revalidates cheaply what might have, and downloads afresh only what is genuinely new.
The caching headers, explained
This is the core toolkit. Each header plays a distinct role.
| Header | Purpose |
|---|---|
Cache-Control | The primary modern header. Directives like max-age, no-cache, no-store, public/private and immutable control how and how long a response may be cached |
ETag | A validation token (a fingerprint of the file). The browser sends it back to ask "has this changed?"; the server replies 304 Not Modified if not, saving the download |
Last-Modified | A timestamp for validation. The browser asks "modified since this time?"; the server replies 304 if not |
Expires | An older absolute expiry date. Superseded by Cache-Control: max-age, which takes precedence when both are present; kept for legacy compatibility |
Cache-Control directives worth knowing
Cache-Control is where most of the work happens, and a few directives do most of the lifting:
max-age=<seconds>— how long the response stays fresh.max-age=31536000is one year, the standard for static assets.no-cache— the browser may store the file but must revalidate with the server before using it. Good for HTML you want fresh but cheaply checked.no-store— do not cache at all. Reserve for sensitive or always-dynamic responses.publicvsprivate—publicallows shared caches (like a CDN) to store it;privaterestricts it to the individual browser (use for personalised responses).immutable— tells the browser the file will never change during its lifetime, so it should not even bother revalidating it on reload. Pairs perfectly with fingerprinted filenames.
A typical strong header for a versioned asset is Cache-Control: public, max-age=31536000, immutable — cache it for a year, share it, and never revalidate it.
Static vs dynamic: cache them very differently
The single most important decision is what to cache aggressively and what to cache cautiously, because the answer differs sharply by content type.
Static, versioned assets — CSS, JavaScript, fonts, images whose filenames include a content hash — should be cached as aggressively as possible, typically with max-age=31536000 (one year) and immutable. They are safe to cache forever precisely because, as we will see, any change produces a new filename, so a long cache never traps users on a stale file.
HTML documents are the opposite. The HTML is the entry point that references all those versioned assets, so it must stay fresh — otherwise a returning visitor would load an old HTML file pointing at last week's asset filenames. HTML is usually served with no-cache (store but always revalidate) or a very short max-age, so users reliably get the current page.
Dynamic and personalised responses — a logged-in dashboard, a cart, an API response tailored to the user — should generally be private and either short-lived or no-store, so one user never sees another's content from a shared cache.
The principle: the more a response is the same for everyone and the less it changes, the longer you cache it; the more personal or fast-changing it is, the less you cache it.
Cache-busting: how to cache forever and still ship updates
Here is the question that trips people up: if you cache a stylesheet for a year, how do visitors ever get an update? The elegant answer is cache-busting through fingerprinted filenames.
Modern build tools generate a filename that includes a hash of the file's contents — for example main.a1b2c3d4.js or styles.9f8e7d.css. The HTML references that exact filename. When you change the file, its contents change, so its hash changes, so it gets a brand-new filename — main.e5f6a7b8.js — that the browser has never seen and therefore must download fresh. Files that did not change keep their existing hashed names and stay served from cache.
This is why you can safely set max-age=31536000, immutable on assets: you are not promising that main.js never changes; you are promising that this specific hashed filename never changes, which is true. Updates ship instantly because they arrive under new names, while unchanged files remain cached. The only file that must not be aggressively cached is the HTML that points at the current filenames — which is exactly why HTML gets no-cache. Most frameworks and bundlers do this fingerprinting automatically; the main thing is to confirm your asset headers and your HTML headers are configured to match this pattern.
Browser cache vs CDN cache
It is worth distinguishing two layers that both "cache," because they interact. The browser cache lives on the individual visitor's device and serves that one visitor on repeat visits. A CDN cache lives on edge servers and serves cached copies to many visitors from a location near them, so even a first-time visitor benefits. The two are complementary: a request can be served from the browser cache (fastest, no network), or from the CDN edge (fast, short network hop), or fall through to your origin (slowest). The same Cache-Control headers generally govern both, though public is what permits the shared CDN layer to cache at all, and CDNs add their own controls. If you run a CDN, your caching strategy spans both layers — see what is a CDN, and do you need one for how the edge layer fits in.
How to set it up
The mechanics depend on your stack, but the shape is the same everywhere:
- Decide your policy per content type: long
max-age+immutablefor hashed static assets;no-cacheor shortmax-agefor HTML;private/no-storefor personalised responses. - Set the headers at the layer that serves each file — your web server config (Nginx, Apache), your application framework, or your CDN's caching rules. CDNs and static hosts often let you set these by file path or extension in a dashboard.
- Enable fingerprinted filenames in your build so cache-busting works; confirm your bundler outputs content hashes.
- Add
ETagorLast-Modifiedfor revalidation on resources that are cached but may change, so stale checks are cheap304s rather than full downloads. - Verify (below) that the headers you intended are actually being sent and that repeat loads are served from cache.
How to verify caching in DevTools
Never assume your headers are correct — check them. The verification is quick:
- Open Chrome DevTools → Network panel and load the page. Click any asset and read its response headers under the Headers tab — confirm
Cache-Control,ETagandLast-Modifiedmatch what you intended. - Reload the page and look at the Size column in the Network panel. Cached files show
(from disk cache)or(from memory cache)instead of a byte size, proving they were served locally rather than re-downloaded. - Look for
304 Not Modifiedresponses on revalidated files — that means the browser asked "has this changed?" and the server answered cheaply without resending the body. - From the command line,
curl -I https://yoursite.com/asset.jsprints the response headers directly, which is handy for confirming a server's configuration without a browser.
A common gotcha: DevTools has a "Disable cache" checkbox that is active while DevTools is open, which suppresses caching so you can test fresh loads. Make sure it is unchecked when you are trying to verify that caching works. A broader audit tool such as StackOptic will also report on caching headers as part of a wider performance check, flagging assets that lack sensible cache lifetimes.
Common mistakes
- Not caching static assets at all, so returning visitors re-download unchanged CSS and JavaScript every time.
- Caching HTML too aggressively, so users get stale pages pointing at old asset versions.
- Long cache lifetimes without fingerprinted filenames, which traps users on outdated files until the cache expires.
- Using
no-storeeverywhere out of caution, throwing away the entire benefit of caching. - Forgetting to verify, and assuming headers are set when the server or CDN is actually overriding them.
Why it matters
Caching is unusually cost-effective. It is mostly a one-time configuration task, it requires no change to your content or design, and it makes a tangible difference to the experience of every returning visitor and every multi-page session — which, for most sites, is a large share of traffic. It also reduces load on your origin server and cuts bandwidth, which can lower hosting costs and, by reducing repeated data transfer, modestly lowers the energy your site consumes. Like much of performance work, the same change that speeds the site up also makes it leaner and cheaper to run. Configure it once, verify it, and it quietly pays off on every visit thereafter.
Go deeper
- The big picture, in priority order: how to make your website load faster.
- The shared edge layer: what is a CDN, and do you need one.
- The metrics caching helps: Core Web Vitals explained.
- Reducing what must be cached at all: how to reduce JavaScript and speed up your site.
Want to see whether your caching headers are set sensibly, alongside performance, SEO and security? Analyse any URL with StackOptic — free, no sign-up.
Frequently asked questions
What is browser caching?
Browser caching is the practice of storing copies of a website's files locally in the visitor's browser, so they can be reused instead of downloaded again. The first time someone visits, the browser downloads assets like CSS, JavaScript, images and fonts; if those files are cached, subsequent pages and return visits load them from local storage rather than the network. This makes repeat visits dramatically faster and reduces bandwidth for both the visitor and the server.
How do I set up browser caching?
You configure HTTP response headers on your server or CDN. The primary one is Cache-Control: set a long max-age (for example one year) on static, versioned assets, and add immutable so browsers do not revalidate them. For HTML and dynamic content, use short lifetimes or no-cache so users get fresh content. Add ETag or Last-Modified headers to allow efficient revalidation. Most servers, CDNs and frameworks let you set these per file type.
What is the Cache-Control header?
Cache-Control is the main HTTP header that tells browsers and intermediaries how to cache a response. Common directives include max-age, which sets how many seconds the file is considered fresh; no-cache, which means the browser must revalidate before using the cached copy; no-store, which forbids caching entirely; private or public, which control whether shared caches may store it; and immutable, which tells the browser the file will never change, so it should not even revalidate it during its lifetime.
What is cache-busting?
Cache-busting is how you cache assets for a long time yet still deploy updates instantly. You include a content fingerprint in each filename — for example main.a1b2c3.js — generated from the file's contents. When the file changes, its hash changes, so it gets a brand-new filename the browser has never seen and must download fresh. Unchanged files keep their names and stay cached. This lets you set very long cache lifetimes without ever serving users a stale file.
How do I check if my caching is working?
Open the Network panel in Chrome DevTools and load your site twice. On the second load, cached files show '(from disk cache)' or '(from memory cache)' in the Size column instead of a transfer size, confirming they were not re-downloaded. Click any request to inspect its response headers and verify Cache-Control, ETag and Last-Modified are set as intended. You can also run a command-line check with curl -I to see the headers a server returns.
Analyse any website with StackOptic
Get the full technology stack, performance, security and SEO report in seconds — free.
Analyse a websiteRelated articles
How to Test Your Website Speed (Tools and Metrics)
Which tools to use, lab versus field data, and the metrics that matter. Test your site speed with PageSpeed Insights, Lighthouse, WebPageTest and DevTools.
How to Optimize CSS for Faster Pages
CSS is render-blocking, so bloated stylesheets delay your page. How to minify, cut unused CSS, inline critical CSS and defer the rest to speed up FCP and LCP.
What Are Gzip and Brotli Compression (and How to Enable Them)?
Gzip and Brotli shrink HTML, CSS and JavaScript before they are sent. What each is, how Brotli compares to Gzip, how to verify it, and how to enable it.