Performance
Cache and CDN: the real hit rate, measured
Of 60 requests, 40 came from the edge cache (66.7%): static assets hit 40 of 40, HTML 0 of 20. TTFB goes from 26 to 276 ms.
Key points
- Of 60 measured requests, 40 came from the edge cache: 66.7% overall, 100% on static assets.
- HTML answers DYNAMIC with Cache-Control: no-cache on all 20 requests.
- Median TTFB is 26-34 ms for assets and 256-276 ms for HTML: the difference is the origin.
- The query string is part of the cache key: a new URL misses, the second request hits.
Of 60 requests to the site, 40 were served from the edge cache, 66.7%: all 40 static assets hit, all 20 HTML requests came back DYNAMIC. Median TTFB goes from 26-34 ms for assets to 256-276 ms for HTML.
What the edge answers
| Resource | Requests | HIT | Median TTFB | Cache-Control |
|---|---|---|---|---|
| Home HTML | 10 | 0 | 276 ms | no-cache |
| Article HTML | 10 | 0 | 256 ms | no-cache |
| CSS | 10 | 10 | 26 ms | max-age=2592000, swr=604800 |
| Font | 10 | 10 | 34 ms | max-age=31536000, immutable |
| Image | 10 | 10 | 30 ms | max-age=2592000 |
| Icon | 10 | 10 | 29 ms | max-age=2592000 |
The gap between 30 and 276 ms is the cost of the origin: HTML is generated on every request, assets come from the edge. That is not a CDN defect, it is a decision: HTML changes often and has no stable cache key.
Measuring it from a terminal
for u in / /blog/core-web-vitals-ttfb /css/components-ui.css \
/fonts/inter-400-latin.woff2; do
printf '%s ' "$u"
curl -sI "https://ui.eneawork.it$u" |
grep -i -E '^(cf-cache-status|age|cache-control):' | tr '\n' ' '
echo
done
The command prints cache status, object age and cache policy for each URL. Age says how many seconds the edge has held that copy: 83,580 s for the CSS, almost a day.
The query string is part of the key
With a new URL (?probe=...) the first answer is MISS and the second is HIT: the parameter is part of the key. A campaign identifier that differs per user empties the cache on every visit, and so does an ordering parameter in internal links.
What to change
- Assets with a hash in the name: a one-year cache and
immutable, like the font answering withmax-age=31536000. Source: web.dev, CC BY 4.0. - HTML: when you cannot cache it, cut the origin TTFB. In the local test the home page answers in 205 ms. Source: web.dev, CC BY 4.0.
- Query strings: normalise the parameters that do not change the content, otherwise every variant is a new object. Source: MDN, CC BY-SA 2.5.
stale-while-revalidate=604800serves the old copy at once and refreshes it afterwards: the site CSS already uses it.
The limitation to name
The measurement comes from one client and one point of presence (Milan, from the CF-RAY), with ten requests per URL. Real hit rate depends on traffic, geography and TTLs: a site with many HTML pages and few assets shows a much lower figure, because the 66.7% here mixes two different populations.
Where to continue
The EneUI catalogue indexes 111,985 components, for instance card-stack, and the grid of 1,556 blocks: every record is an HTML page that never reaches the edge cache. Read how to measure TTFB, caching with a service worker and how to set a performance budget.
Frequently asked questions
What is cache hit rate?
It is the share of requests served from cache instead of the origin. In our sample it is 66.7% overall and 100% on static assets alone.
Why is HTML not cached?
Because it answers with Cache-Control: no-cache and cf-cache-status DYNAMIC. The content changes often and the edge has no stable key to reuse it.
How do I read cf-cache-status?
HIT means served from the edge, MISS means fetched from the origin and stored, DYNAMIC means not cacheable under the current rules, EXPIRED means the stored copy ran out.
Is the TTFB of a HIT always low?
It depends on the distance to the point of presence. Here 26-34 ms from the edge against 256-276 ms from the origin, measured from the same client.
Does a query string break the cache?
Yes, when it is part of the key. A URL with a new parameter is a new object: the first request misses, the second hits.