Inefficient cache policy medium
Serve static assets with an efficient cache policy
Static files lack long Cache-Control max-age headers, so returning visitors re-download assets that never changed.
What you see
Serve static assets with an efficient cache policy A long cache lifetime can speed up repeat visits to your page. 12 resources found — Cache TTL: None
What’s actually happening
First visit is fine; the second visit is slower than it should be because the browser re-fetches CSS, JS, fonts, and images it already had. Lighthouse lists each asset with its current TTL — often "None" or a few minutes. In the Network panel, reload and watch assets come back 200 from the server instead of 304 or (from disk cache). Repeat-visit load time and bandwidth both suffer, and it adds needless load to your origin.
Common causes
- No Cache-Control header set at all, so the browser falls back to heuristic caching or none
- max-age set to a tiny value (a few minutes) on assets that rarely change
- Filenames without content hashes, so a long cache is unsafe and someone set it short on purpose
- A CDN or proxy in front that isn't honoring or adding cache headers for static paths
- Cache-Control: no-cache or no-store applied site-wide, including to assets that should be cached hard
How to fix it
- Add content hashes to filenames, then cache them foreverBuild tools (Vite, webpack, Rollup) emit files like app.9f3c2a.js. Because the name changes when the content changes, you can safely serve Cache-Control: public, max-age=31536000, immutable. A new deploy gets a new filename, so there's no stale-cache risk.
- Set the headers at the right layerOn Nginx, add a location block for static extensions with expires 1y and Cache-Control. On Apache, use mod_expires/mod_headers in .htaccess. On Netlify/Vercel/Cloudflare Pages, set headers in the config file (_headers, netlify.toml, vercel.json). Confirm the CDN passes them through.
- Keep HTML on a short leashDon't cache your HTML documents long — that's how people get stale pages. Use no-cache or a short max-age on HTML, and rely on the hashed asset URLs inside it to bust caches for everything else.
- Verify with curl before and aftercurl -I https://yoursite.com/assets/app.js and read the Cache-Control line. Do it again after deploying the change. This is the fastest way to confirm the header is actually landing on the response and not being stripped by a proxy.
Stop it recurring
Bake hashed filenames plus a one-year immutable cache header into your build and deploy config so it's the default for every asset.
Related errors