Mastodon
All articles
Web Performance

CDN Caching Mistakes That Make Fast Sites Feel Slow

Is your CDN actually caching your content? Learn why cache MISS responses happen, how query strings and Vary headers fragment cache keys, and how to verify edge performance.

Share:

The application has already been tuned. The origin responds quickly from the primary region. Bundles are trimmed, images are compressed, and the CDN toggle is enabled in production. Yet users far from the origin still see slow first responses, high TTFB, and page transitions that feel heavier than the architecture diagram promised.

The investigation starts in the network waterfall. The CDN is present, but the responses that should be hot at the edge keep coming back as MISS, BYPASS, DYNAMIC, or some provider-specific equivalent instead of HIT. The site is behind a CDN, but the CDN is mostly forwarding traffic to origin.

TL;DR

  • A CDN does not make a site fast by existing; it must actually cache and reuse the right responses.
  • Query strings, cookies, Vary, cache keys, missing shared-cache directives, and override rules can destroy cache hit ratio.
  • s-maxage and platform-specific CDN cache controls can let shared caches keep a response longer without forcing the same browser cache behavior.
  • Live headers such as Age, Cache-Status, CF-Cache-Status, or X-Vercel-Cache can confirm whether the edge is warm, but availability and exact labels depend on the platform.

Before changing code, inspect the live headers. The CodeAva HTTP Headers Checker fetches response headers for a public URL and shows cache-related fields such as Cache-Control, ETag, Last-Modified, Expires, Age, and Vary when they are present. If the edge is not returning cache hits, your CDN may only be forwarding traffic to origin faster.

A CDN is not a performance guarantee

A CDN can reduce latency by serving cached responses close to users. That benefit only appears when the edge can store a response and reuse it for later requests. If every request is unique, private, uncacheable, or split into too many variants, the edge cannot help much.

Cacheability is an outcome of several signals working together: response headers, request method, response status, cookies, cache key configuration, query-string handling, Vary, and CDN rules. Edge performance is not a checkbox. It is a production configuration that needs the same verification discipline as database indexes or load balancer health checks.

Mistake 1: measuring CDN presence instead of cache hit ratio

Many teams stop at “the CDN is enabled.” That is the wrong measurement. The useful question is whether critical assets and cacheable documents are producing hits often enough to matter.

Inspect representative responses, not just the homepage. Check HTML, JavaScript, CSS, image, API, redirect, and error responses. A CDN can cache one category well and bypass another entirely.

  • HIT usually means the edge reused a stored response.
  • MISS often means no usable object was available at that edge for that cache key.
  • BYPASS or DYNAMIC often means platform rules, cookies, request properties, or response directives prevented normal caching.
  • STALE and REVALIDATED can be good or bad depending on policy, freshness, and whether stale serving was intentional.

Do not treat these labels as universal. Cloudflare, Vercel, Fastly, CloudFront, Akamai, Netlify, and reverse proxies all expose different headers and vocabulary. Use the values as evidence, then map them back to the provider documentation and your cache rules.

Mistake 2: query strings fragmenting the cache key

A cache key is the identity the CDN uses to decide whether a stored response can satisfy a request. The URL path is usually part of it. Query strings often are too, either by default or by configuration.

That is correct when the query string changes the content. It is waste when the query string only carries tracking or campaign data.

/style.css
/style.css?utm_source=linkedin
/image.jpg?campaign=spring

If those requests return identical bytes but create separate cache entries, the edge has to warm multiple variants for the same resource. The cache hit ratio drops even though nothing about the file changed.

What belongs in the cache key

Cache-key policy must be path-specific and content-aware:

  • Ignore irrelevant marketing parameters for static assets when you are certain they do not change the response.
  • Include only query parameters that actually select a different representation, size, language, format, or data set.
  • Treat ecommerce, search, filter, sort, and pagination URLs with care. Their query strings often represent different content and should not be collapsed globally.

The dangerous fix is “ignore all query strings everywhere.” The safer fix is “normalize only the parameters that do not affect this resource.”

Mistake 3: missing shared-cache directives

Browser caching and CDN caching are related, but they are not the same layer. A browser cache is private to one user and one device. A CDN cache is shared across users who reach the same edge and cache key. The risk model is different, so the policy often needs to be different.

max-age defines freshness for caches generally. s-maxage is specifically for shared caches and overrides max-age there. Browsers ignore s-maxage. That lets you keep browser freshness short while letting the CDN absorb origin traffic for longer.

Cache-Control: public, max-age=60, s-maxage=3600, stale-while-revalidate=300

In that example, the browser freshness lifetime is 60 seconds, while a shared cache can keep serving the response for up to an hour before revalidation. The stale-while-revalidate directive can allow a stale response to be served while the cache refreshes it in the background, if the cache supports that behavior.

When platform-specific CDN cache headers fit

Some platforms support a separate CDN-facing header so the public browser policy can stay short while the edge policy stays longer. When the platform supports it, a split policy can look like this:

CDN-Cache-Control: public, max-age=3600, stale-while-revalidate=300
Cache-Control: public, max-age=60

Do not assume CDN-Cache-Control is universal. Do not add s-maxage to every static file by habit. Fingerprinted JS, CSS, fonts, and images can often use a simple long browser policy because the URL changes when the content changes. Shared-cache directives are most valuable when the browser and CDN need different freshness behavior.

Mistake 4: Vary: User-Agent and cache variant explosion

Vary tells caches which request headers affected the response. It is necessary for real content negotiation. If a server returns different content based on Accept-Encoding, Accept-Language, or another request header, the cache must know that those headers matter.

The problem is over-broad variation. User-Agent has an enormous number of possible values. A response with Vary: User-Agent can fragment into many cached variants for the same URL, which lowers reuse and makes the edge feel cold.

Vary: User-Agent

Avoid Vary: User-Agent unless the response truly differs by user agent and there is no cleaner strategy. Prefer responsive design, media queries, explicit device hints, or narrower variation rules where possible. If the response does vary, keep Vary consistent across normal 200 responses and 304 Not Modified revalidation responses so caches do not validate the wrong variant.

Vary is not bad, but unnecessary Vary is expensive

The header protects correctness when request headers change the response. The danger is adding broad values such as User-Agent without proving the response needs that many variants.

Mistake 5: cookies turning public content into uncacheable content

Cookies make shared caching harder because they often indicate user state. Depending on provider defaults and rules, requests with cookies may bypass the cache, split the cache key, or be treated more cautiously than anonymous requests.

Analytics, consent, A/B testing, personalization, and session cookies are common sources of accidental bypass. If every request for a public page carries a cookie that the CDN considers relevant, the cache hit ratio can collapse even when the content is mostly the same for everyone.

  • Keep static asset domains cookie-light or cookie-free where possible.
  • Separate personalized routes from public routes instead of mixing both behind one ambiguous cache policy.
  • Cache public shells only when the response truly does not vary by user.
  • Never cache personalized responses as public shared responses.

Mistake 6: caching HTML like static assets, or static assets like HTML

One cache policy for every response usually creates one of two failures: stale HTML or under-cached static assets.

Fingerprinted JS, CSS, fonts, and images can usually be cached aggressively because a content hash in the filename creates a new URL whenever the bytes change.

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

HTML is different. It often points to the latest asset graph, contains route data, reflects deployment state, and may include personalization or CSRF-sensitive content. HTML often needs a shorter TTL, revalidation, or an intentional stale strategy:

Cache-Control: public, max-age=60, s-maxage=600, stale-while-revalidate=60

That is not a universal HTML policy. It is an example of separating browser freshness from shared-cache reuse. Authenticated HTML, admin pages, checkout flows, and user-specific dashboards may need private or no-store instead.

Mistake 7: purging everything instead of invalidating carefully

Purge-all workflows are operationally simple and performance-hostile. They make every edge cold at once. The next traffic wave has to rebuild the cache, and origin may receive a sudden burst of requests that would otherwise have been absorbed at the edge.

Safer invalidation reduces blast radius:

  • Use versioned asset filenames so deployments do not require purging old JS, CSS, fonts, or images.
  • Purge changed paths instead of the entire site when only a small set of pages changed.
  • Use cache tags, surrogate keys, or content tags on platforms that support them.
  • Keep TTLs and stale policies aligned with how quickly the content actually needs to change.

Cache tags are not a universal CDN feature. Treat them as a platform capability to use where available, not a standard you can assume everywhere.

Mistake 8: not checking the Age and cache-status headers

Live response headers are the fastest way to see what the edge is actually doing. They are not the whole measurement system, but they are the first thing to inspect when a CDN-backed page feels slow.

Age can indicate how long a response has been stored in a cache. A standard Cache-Status header may appear on some systems. Vendor headers such as CF-Cache-Status, X-Vercel-Cache, X-Cache, or X-Served-By can reveal provider-specific cache behavior when that provider sends them.

HTTP/2 200
Cache-Control: public, max-age=60, s-maxage=3600, stale-while-revalidate=300
Age: 1842
Cache-Status: "ExampleCDN"; hit; ttl=1758
HTTP/2 200
Cache-Control: public, max-age=0, s-maxage=600
CF-Cache-Status: MISS
X-Vercel-Cache: MISS

Do not expect all CDNs to expose all of those headers. Do not assume a missing header proves there is no cache. But if Age is always absent or zero, and the cache-status header keeps reporting a non-hit state on a response that should be reusable, investigate cacheability, cache key fragmentation, cookies, and CDN override rules.

The CDN caching audit checklist

Use this sequence when the site is behind a CDN but still feels cold from real user locations:

  1. Pick representative URLs: HTML, CSS, JS, image, API, redirect, and error responses.
  2. Inspect live response headers from outside local development using browser DevTools, curl -I, or the HTTP Headers Checker.
  3. Check Cache-Control, s-maxage, Age, and any cache-status headers that the response actually includes.
  4. Confirm static assets are fingerprinted before applying long TTLs or immutable.
  5. Confirm query strings do not fragment cache unnecessarily on paths where parameters do not change the content.
  6. Confirm Vary is not over-broad, especially for User-Agent.
  7. Confirm cookies are not forcing avoidable bypasses on public pages and static assets.
  8. Confirm CDN override rules match origin intent. Provider defaults can override, strip, or reinterpret origin cache headers.
  9. Re-test after purge or deployment. A cold edge after deploy can hide whether the steady-state cache policy is healthy.
  10. Track cache hit ratio over time in the CDN dashboard or logs. Header inspection explains individual responses; hit ratio shows whether the pattern holds in production.

Comparison table: common CDN caching mistakes

MistakeSymptomWhy it slows the siteSafer fix
Query-string fragmentationSimilar URLs keep returning MISS.Tracking parameters create separate cache entries for identical content.Normalize irrelevant parameters per path, not globally.
Missing shared-cache policyBrowser caching works, but edge reuse is poor.CDN freshness is too short, absent, or overridden.Use s-maxage or supported CDN-specific controls where appropriate.
Vary: User-AgentMany variants for one URL.User-Agent splits the cache into too many low-reuse objects.Avoid broad variation; use responsive design or narrower explicit variation.
Unnecessary cookiesPublic pages or assets bypass cache.CDN treats requests as personalized or unsafe to share.Keep asset hosts cookie-free and separate public from personalized routes.
One cache policy for everythingStale HTML or under-cached static files.HTML and fingerprinted assets have different freshness needs.Cache hashed assets aggressively; give HTML a shorter, verified policy.
Purge-all workflowsOrigin load spikes after deploy.Every edge goes cold at the same time.Use versioned assets, targeted purge, or cache tags where supported.
Unverified edge headersConfig looks correct, live traffic is still slow.CDN rules or intermediaries changed what users receive.Inspect live headers and correlate with CDN hit-ratio metrics.

Tool integration: verify the edge response, not the config file

Config files describe intent. Live headers reveal what survived the full path through framework, reverse proxy, CDN, WAF, redirect rules, and platform defaults.

The CodeAva HTTP Headers Checker makes a server-side request to a public URL, follows redirects, and lists the response headers it receives. It groups known cache and freshness headers such as Cache-Control, ETag, Last-Modified, Expires, Age, and Vary. Provider-specific headers such as CF-Cache-Status or X-Vercel-Cache are visible when the response includes them.

Use the tool for individual response inspection, then use your CDN dashboard or logs for aggregate cache hit ratio. The tool does not simulate cache keys, test multiple geographic regions, or calculate hit ratio; it helps you see the live header evidence for the URL you are debugging.

The CDN checkbox fallacy

A CDN with a low cache hit ratio is often just a faster route to your origin, not a real edge cache. Inspect live headers and cache behavior before assuming the CDN is helping.

Conclusion and next steps

A CDN improves performance only when the edge can reuse the right responses. Most slow-CDN problems are not caused by raw origin speed. They come from cache-key fragmentation, missing shared-cache rules, over-broad Vary, cookies, one-size-fits-all cache policy, or invalidation habits that keep the edge cold.

The operational workflow is simple: inspect the live response, identify whether the edge is reusing it, fix the cacheability or cache-key problem, and verify again after deployment. Treat edge caching as a production architecture layer, not a hosting checkbox.

Start by checking the live response in the CodeAva HTTP Headers Checker. For the header policy layer, read How to Use HTTP Caching Headers to Improve Real-World Performance. For production response hardening, see Security Headers Every Production Website Should Send. If slow edge responses are affecting Core Web Vitals, use the LCP Debugging Checklist for Modern Frontends to trace the impact through TTFB, discovery, download, and render delay.

#CDN caching#edge performance#Cache-Control#cache keys#Vary#query strings#cache hit ratio#HTTP headers#s-maxage#CF-Cache-Status#X-Vercel-Cache#Age header

Frequently asked questions

More from Sophia DuToit

Found this useful?

Share:

Want to audit your own project?

These articles are written by the same engineers who built CodeAva\u2019s audit engine.