Static files are re-downloaded because they are barely cached
What this check looks for
‹count› static file(s) this page loads — scripts, stylesheets, fonts, images — are served with a short or missing cache lifetime, so returning visitors fetch them again. An estimated ‹savings bytes› per repeat visit.
Why it matters
A returning visitor should download almost nothing. Short cache lifetimes on files that never change turn every repeat visit into a first visit, for no benefit at all.
When the check passes, your report says: “Static files are cached long enough to survive a repeat visit”.
What it costs your score
When this check fails it removes 6 points from your Performance score, before the status, confidence and repeat multipliers are applied. Performance carries a weight of 8 in the overall score.
It shares the performance.assets family ceiling of 25 points: however many findings that family produces, together they cannot remove more than that from Performance. One underlying problem showing up in several places is still one problem.
- Severity
- low
- Default confidence
- high
- Status when triggered
- warn
- Deduction
- 6 points
- Family cap
- performance.assets · 25
- Category
- Performance
- Module
- Perf lab
- Fix owned by
- user
- In the ruleset since
- 2026.09
How to fix it
Give fingerprinted static assets a one-year immutable cache lifetime.
Returning visitors currently re-download an estimated ‹savings bytes› of files that have not changed.
Put a content hash in the file name of every build artefact —
app.4f3a9c2e.jsrather thanapp.js. Most bundlers do this with one setting.Serve anything whose name contains a hash with
Cache-Control: public, max-age=31536000, immutable.Leave anything **not** fingerprinted on a short lifetime. A long
max-ageon a stable URL means you cannot ship a fix to visitors who already have it.Apply the same policy at the CDN as at the origin — a CDN that overrides the origin's header is the usual reason this finding persists after the server is fixed.
Do not set a long lifetime on the HTML document; it is what tells the browser about the new asset URLs.
How to confirm it worked
Re-run this scan and confirm the short-cache count has dropped.
curl -sSI https://‹host›/path/to/app.<hash>.js | grep -i cache-control — expect
public, max-age=31536000, immutable
A named slot like ‹domain› — and the braces left in the configuration below — is filled in with your own values when this rule appears on a report.
Remediation by platform
location ~* "\.[0-9a-f]{8,}\.(?:css|js|woff2|png|jpg|avif|webp|svg)$" {
add_header Cache-Control "public, max-age=31536000, immutable" always;
}<IfModule mod_headers.c>
<FilesMatch "\.[0-9a-f]{8,}\.(css|js|woff2|png|jpg|avif|webp|svg)$">
Header always set Cache-Control "public, max-age=31536000, immutable"
</FilesMatch>
</IfModule>@immutable path_regexp \.[0-9a-f]{8,}\.(css|js|woff2|png|jpg|avif|webp|svg)$
header @immutable Cache-Control "public, max-age=31536000, immutable"The pattern deliberately matches only names containing a hash. Assets without one keep whatever the default is, which is what stops an un-fingerprinted file from becoming uncacheable-fixable for a year.
Place this in the vhost or in the directory config for the asset root, not in a
.htaccessat the site root, or it will also match paths you did not intend.Caddy matchers are evaluated in order; put this before any broader
headerdirective that would otherwise win.
Technical detail
Static subresources with an inefficient cache policy: ‹count›, estimated saving **‹savings bytes›** per repeat visit.
‹detail›
This came from **one page load we performed**, not from your visitors. A lab measurement is reproducible and tells you exactly which bytes and which resources are responsible — which is why the numbers below are byte counts and resource lists rather than a score. What real visitors experience is in the field-data section, which is the evidence this report scores Core Web Vitals on.
**This is about the files the page loads, not about the page itself.** The main document's Cache-Control is checked separately by http.headers.no-cache-control and http.headers.cache-directives-conflicting, which fetch that response directly. The document *should* have a short lifetime — it changes. These subresources should not, and that is the difference the two findings are drawing.
The rule that makes a long lifetime safe is **content-addressed URLs**: if the file name contains a hash of its contents, a changed file is a different URL, so nothing can be stale. With that in place, Cache-Control: public, max-age=31536000, immutable is correct and immutable additionally tells the browser not to revalidate on a reload (RFC 8246). Without it, a long lifetime is dangerous — you cannot deploy a fix — so fix the file naming first and the header second.
Standards and references
Test this on your domain
Run the check that produces this finding, on its own, against any domain.
Other perf lab checks
- The page ships more JavaScript than it uses
- Images are served in older, heavier formats
- Images are much larger than the space they are shown in
- Lab measurement of one page load
- Stylesheets and scripts stop the page from rendering
- This report never publishes a headline performance score
- Scripts and stylesheets are sent without compression
- The page was not measured in a browser