Fix Inefficient Cache Policy (Cache-Control)
An "inefficient cache policy" notice means your static assets (images, CSS, JS, fonts) are served with a short cache lifetime or no Cache-Control header, so returning visitors re-download files that never changed. The fix is to send a long max-age (up to a year) for versioned assets and a shorter, revalidating policy for files that change at the same URL. This speeds up repeat and multi-page visits and cuts server bandwidth without touching your content.
What this means
This notice comes from the Lighthouse "Serve static assets with an efficient cache policy" audit (audit ID uses-long-cache-ttl). It inspects static resources such as fonts, images, media, scripts, and stylesheets returned with a cacheable status (200, 203, or 206) and reads their Cache-Control response header. If an asset has a short max-age, no max-age, or no caching directive, the audit flags it and estimates how much data a repeat visitor would re-download unnecessarily.
The browser cache is separate from any server-side or CDN cache. It lives on the visitor's device. When a file carries a strong Cache-Control header, the browser stores it locally and serves it instantly on the next page view instead of hitting the network. Without that header, or with a weak one, the browser re-fetches the file or sends a conditional request on every navigation, adding round trips that slow repeat and multi-page visits.
This is a notice, not an error, because it does not break anything or hurt first-time visitors. It is a repeat-visit efficiency win. Assets with a content hash in the filename (like app.4f9a2c.js) are the clearest candidates for aggressive caching: a new deploy produces a new filename, so the old cached copy is simply never requested again.
Why it matters
Caching mainly helps returning visitors and people browsing several pages in one session. Once static files are cached, the second page view skips re-downloading your CSS, JS, fonts, and images, which shortens load time and reduces the work behind Core Web Vitals like LCP. Google's field data (CrUX) reflects real returning users, so a solid cache policy quietly improves the metrics that feed page experience signals, even though this specific audit is diagnostic and not a ranking factor on its own.
There is also a cost angle. Every uncached asset is bandwidth you pay for and an origin request your server handles. Long TTLs on immutable assets cut origin traffic sharply, which matters most on shared hosting, metered CDNs, and high-traffic sites.
For AI answer engines the effect is indirect but real. Crawlers from Google, Bing, and AI systems (GPTBot, PerplexityBot, ClaudeBot) fetch pages under time and resource budgets. Faster asset delivery and fewer redundant fetches mean cleaner, quicker crawls, so your content is more likely to render and index reliably rather than time out partway. Efficient caching won't make an AI cite you, but a slow, request-heavy site can get crawled less thoroughly.
How to fix it
- 1
Split assets into immutable vs. mutable
Sort your static files into two buckets. Fingerprinted assets, whose filenames include a content hash like
main.8a3f.css, can safely use a one-year cache because any change produces a new filename. Files that keep the same URL when edited, such as a logo at/logo.pngor an un-hashed stylesheet, need a shorter TTL or a revalidation strategy so users pick up updates. Never apply a long immutable cache to HTML documents; those must stay fresh. - 2
Set a long immutable cache on fingerprinted files
For hashed static files, send
Cache-Control: public, max-age=31536000, immutable. That is one year in seconds, andimmutabletells the browser not to revalidate even on a reload. This one header clears the audit for the vast majority of flagged JS, CSS, and font files. Confirm it is actually present by checking the Network tab or runningcurl -I https://yoursite.com/assets/app.8a3f.js. - 3
Use revalidation for files that change at the same URL
For assets you edit in place without renaming, use a shorter max-age, for example
Cache-Control: public, max-age=3600. If you need the browser to confirm freshness on every request while still avoiding a full re-download, useno-cacheinstead (note:no-cachemeans revalidate before use, not "don't cache"). Pair either approach with a strongETagorLast-Modifiedheader so the server can answer with a cheap304 Not Modified. - 4
Set the header at the layer that serves the asset
Where you set headers depends on your stack. On Apache, use
.htaccesswithmod_expiresandmod_headers; on Nginx, use alocationblock withexpiresandadd_header Cache-Control; on a CDN like Cloudflare, use a Cache Rule with an edge and browser TTL. If a CDN sits in front of your origin, its headers usually win, so set the rule there. Managed platforms like Vercel, Netlify, and Shopify already fingerprint and cache built assets automatically; see the platform notes below. - 5
Re-run the audit and check repeat views
After deploying, re-run Lighthouse or the audit. Then load a page, navigate to a second page, and watch the Network tab: cached assets should show as served "from disk cache" or "from memory cache" with no round trip. If files are still flagged, the header is likely being stripped or overridden by an upstream proxy or CDN, or it is set on the HTML document instead of the static file. Check the exact response headers on the specific URLs the audit listed.
Example
# Fingerprinted/immutable assets - cache hard for a year
location ~* \.(css|js|woff2|png|jpg|jpeg|webp|avif|svg|ico)$ {
expires 1y;
add_header Cache-Control "public, max-age=31536000, immutable";
}
# HTML documents - always revalidate so content stays fresh
location ~* \.html$ {
add_header Cache-Control "no-cache";
}Nginx: long immutable cache for versioned assets, revalidation for HTML
Platform-specific steps
Enable mod_expires and mod_headers, then set headers by file type. Example: <IfModule mod_expires.c> ExpiresActive On ExpiresByType image/webp "access plus 1 year" ExpiresByType text/css "access plus 1 year" ExpiresByType application/javascript "access plus 1 year" </IfModule>. To add immutable: <FilesMatch "\.(css|js|woff2|webp|png|jpg|svg)$"> Header set Cache-Control "public, max-age=31536000, immutable" </FilesMatch>. Keep HTML out of these rules.
Add a location block for static assets in your server config: location ~* \.(css|js|woff2|png|jpg|jpeg|webp|svg|ico)$ { expires 1y; add_header Cache-Control "public, max-age=31536000, immutable"; }. For files that change at the same URL, drop immutable and lower the TTL. Run nginx -s reload after editing. If a CDN fronts Nginx, confirm it forwards or respects these headers.
WordPress does not set asset cache headers itself; your web server or a plugin does. On Apache or Nginx hosts, add the server rules above. Or use a caching plugin (WP Rocket, W3 Total Cache, LiteSpeed Cache) and enable its browser-cache / "add expires headers" option. If your host runs a server-level cache (Kinsta, WP Engine, SiteGround), asset headers are often already set, so check the actual response before adding duplicate rules.
Cloudflare respects your origin's Cache-Control by default. To override or strengthen it, create a Cache Rule (Rules > Cache Rules) matching your static-asset paths or extensions and set Edge TTL and Browser TTL (for example, Browser TTL of 1 year). "Cache Everything" plus a long Browser TTL will set the header even when the origin does not. Do not apply long browser TTLs to HTML unless the content is truly static.
Built and hashed assets (the _next/static folder on Next.js, hashed build output on Netlify) already ship with Cache-Control: public, max-age=31536000, immutable automatically, so those need no action. For files you serve from /public or a custom route, set headers yourself: use the headers() function in next.config.js, or a _headers file or netlify.toml on Netlify. Verify the specific flagged URLs, since only un-hashed public assets usually need attention.
These hosted platforms manage asset caching and CDN delivery for you, and you cannot edit raw response headers. Theme assets are fingerprinted and served with long cache lifetimes automatically. When the audit flags files here, they are usually third-party scripts or app or embed resources loaded from external domains you do not control. The fix is to remove or defer the offending third-party embed rather than set headers.
Frequently asked
For versioned files with a content hash in the name, use Cache-Control: public, max-age=31536000, immutable (one year). For files that change at the same URL, use a shorter max-age such as public, max-age=3600, backed by an ETag or Last-Modified so the server can return a 304. Keep HTML pages short-lived or no-cache so content updates appear immediately.
Not if your assets are fingerprinted. When a build tool renames app.js to app.9f2c.js on each change, the HTML references the new filename, the browser requests the new file, and the old cached copy is never asked for again. The rule is simple: long TTLs only on files whose URL changes when their content changes. For un-versioned files, use a shorter TTL or revalidation.
Not directly. This audit is diagnostic, not a ranking factor by itself. But caching improves load time for returning and multi-page visitors, which feeds real-user Core Web Vitals data in CrUX, and page experience is something Google considers. Fixing it is a low-risk performance win, not a ranking lever on its own.
A CDN or plugin cache stores rendered HTML or assets on a server so your origin does less work. The browser cache, controlled by Cache-Control, stores files on the individual visitor's device so their next page view needs no network request at all. This audit is specifically about the browser-cache headers on your static assets, which a server-side cache does not set unless configured to.
Usually one of three things: a CDN or reverse proxy in front of your origin is overriding or stripping the header (set the rule at the CDN), the header is on the HTML document rather than the actual JS, CSS, or image URLs, or the max-age is too short. Check the exact response headers on the specific URLs the audit listed with the Network tab or curl -I.
Does your site have this issue?
Run a free, AI-powered audit and we’ll flag this and 150+ other checks in about a minute. No signup.