sfw/fix
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

  1. 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.
  2. 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.
  3. 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.
  4. 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