squirrelscan
GitHub

Cache-Control on a page: what squirrel reads and expects

A response with no caching headers, no-cache with no validator, or a static asset cached for under a day. squirrel reads the headers per page.

What Cache-Control does

Cache-Control tells a browser and any cache in between how long a response may be reused and what has to happen before it is reused again. max-age is the freshness lifetime in seconds. no-cache allows storage but requires revalidation on every use. no-store forbids storage entirely. immutable promises the body will not change while the response is fresh, which lets a browser skip revalidating it on a reload.

The right value depends on the URL. HTML usually changes, so a short lifetime or no-cache with a validator is right. A hashed asset filename never changes its contents, so a year with immutable is right. A validator, meaning an ETag or Last-Modified, makes revalidation cheap: the server answers 304 with no body when nothing changed.

What squirrel checks

The rule runs on every crawled page and reads the response headers. It emits one or two checks:

  • cache-headers-missing: warns with the message No caching headers found and the expectation Cache-Control header recommended when neither Cache-Control nor Expires is present. Nothing else runs.
  • cache-control: reports at info status with Caching disabled with no-store and stops. With no-cache it passes with Uses revalidation caching with no-cache when an ETag or Last-Modified is present, and warns with no-cache without ETag or Last-Modified when neither is. With a Cache-Control that has no max-age at all it warns with Cache-Control without max-age.
  • cache-control-html: for a response treated as HTML, it reports at info status with HTML cached for Ns (N.Nh) above one hour and passes with HTML cache appropriate (Ns) at or below it.
  • cache-control-static: for anything else, it warns with Short cache for static asset (Ns) under 86,400 seconds and passes with Good cache duration (N days) at or above.
  • cache-immutable: emitted after the max-age branch, whether or not a max-age was found, when the immutable directive is present. It passes with Uses immutable directive for optimal caching.
  • cache-expires: reports at info status with Uses legacy Expires header when there is an Expires header and no Cache-Control.

A response counts as HTML when its content type contains text/html, when the URL ends in .html, or when the whole URL string does not end in a dot followed by two to four lowercase letters. That last test runs against the URL including any query string, so /app.css?v=2 and /logo.PNG are both judged by the HTML thresholds rather than the static ones.

This rule is the one that scores a page with no caching headers. perf/bad-caching also counts the pages with no caching policy across the site, in its bad-caching-freshness check, at info status, which does not move the score, so one missing caching setting costs the score once.

On a local or private-network host, such as localhost or 192.168.1.10, the rule is skipped and left out of the score, because a dev server is not the production edge. See Transport and delivery rules do not apply.

How to fix it

For a hashed asset filename, the value can be as long as you like:

Cache-Control: public, max-age=31536000, immutable

For HTML, keep the browser lifetime short and let a shared cache hold it longer:

Cache-Control: public, max-age=0, s-maxage=600
ETag: "a1b2c3"

The common false positive follows from the HTML test. An extensionless URL, an uppercase extension and any URL carrying a query string are all treated as HTML, so a long cache on a hashed asset with a cache-busting parameter is reported as informational rather than as a good static duration.

Rule IDperf/cache-headers
CategoryPerformance
ScopePer-page
Severitywarning
Weight4/10

Options

OptionTypeDefaultDescription
min_static_max_agenumber86400Minimum max-age for static assets in seconds (1 day)

Configuration example

[rules."perf/cache-headers"]
min_static_max_age = 86400

Enable / disable

Disable this rule

[rules]
disable = ["perf/cache-headers"]

Disable all Performance rules

[rules]
disable = ["perf/*"]

Enable only this rule

[rules]
enable = ["perf/cache-headers"]

enable replaces the default ["*"], so this is the only rule selected. Leave disable out: a rule that matches any disable pattern is skipped even when enable names it. Only an enabled setting under a rule’s own [rule_options] table overrides both lists.

  • perf/bad-caching: the same headers judged across the whole crawl rather than page by page.
  • perf/ttfb: the origin time caching is meant to avoid paying twice.
  • perf/compression: the other response header that decides transfer size.
  • security/cookie-flags: Secure, HttpOnly and SameSite on the Set-Cookie headers of the same response.

Performance findings ship in every audit next to the SEO, security and agent experience rules. See Core Web Vitals for how an agent works through a report.

References

Check your site

Run squirrel audit https://example.com and open the Performance section of the report. Each page shows the Cache-Control value it returned. Signed-out local audits need no account and cost nothing. Signed-in audits spend credits.

Type to search…

↑↓ navigate↵ openesc close