Cloudflare Pages Cache Hit Rate at 7%? I Brought It to 90% with 3 Rules
After deploying utlkit.com to Cloudflare Pages, domestic (China) access was painfully slow.
LCP P75 hit 20 seconds. Cache hit rate was only 7.62%.
3 Cache Rules + 1 localization fix later: 90%+ hit rate, first paint from 20s down to 3s.
The Problem
After deploying utlkit.com to Cloudflare Pages, users in China reported extremely slow load times. Opening Chrome DevTools revealed LCP P75 at 20 seconds, P90 at 26 seconds.
I checked Cloudflare's Cache Analytics:
Cache hit rate: 7.62%.
That means 92% of requests were origin fetches — no cache hit at all. For Cloudflare Pages, each "origin fetch" means reading from object storage, pushing TTFB from milliseconds to several seconds. Combined with China's network conditions to overseas servers, the experience was terrible.
Root Cause Analysis
Why was the cache hit rate so low?
Cloudflare's default behavior is to not cache HTML pages (or use a very short Edge TTL), because dynamic sites need fresh content. But for a purely static site, this default is completely wrong.
Three specific reasons:
1. Cloudflare doesn't cache HTML by default
Cloudflare's design philosophy is "your origin knows best". If the origin doesn't send explicit Cache-Control headers, CF uses a 2-hour Edge TTL for HTML — and often bypasses cache entirely.
2. _headers only controls browser caching, not Cloudflare edge caching
I had _headers configured in my project:
# out/_headers
/*
Cache-Control: public, max-age=86400, stale-while-revalidate=604800
This tells the browser to cache the page for 1 day. But does Cloudflare's edge node respect it? Not necessarily. _headers sets response headers for the browser — it has limited direct impact on CF edge caching behavior.
3. Cookies cause cache bypass
Cloudflare's default behavior: if a request carries cookies, bypass the cache. After users interact with your site (click buttons, fill forms), the browser sends cookies on subsequent requests. For a tools site, after using one tool, navigating to another — cookies come along, and cache is bypassed every time.
The Solution
Approach Comparison
| Approach | Effect | Cost | Complexity | Best For |
|---|---|---|---|---|
| Cache Rules | ★★★★ | Free | Low | All sites |
| IP Optimization + Worker Proxy | ★★★★★ | Free | High | Heavy CF users |
| China CDN → CF origin | ★★★★★ | $8+/mo | Medium | ICP-filed domains |
_headers only |
★★ | Free | Very low | Supplement only |
Conclusion: Cache Rules is the best bang-for-buck. Start here.
Step 1: Create Cache Rules
Go to Cloudflare Dashboard → Caching → Cache Rules → Create Rule.
The free plan allows 3 rules — exactly what we need.
Rule 1: Long Cache for Static Assets (highest priority)
Conditions:
HTTP Host equals "utlkit.com"
AND
HTTP Request URI matches one of:
/_next/static/*
/fonts/*
Actions:
- Edge TTL → Ignore cache-control header, 30 days
- Browser TTL → Override, 30 days
- Ignore query string ✅
All files under
/_next/static/in Next.js include content hashes in their filenames. If content changes, the filename changes — so it's safe to cache aggressively. Same for fonts.
Rule 2: Short Cache for Sitemap
Conditions:
HTTP Host equals "utlkit.com"
AND
HTTP Request URI matches one of:
/sitemap.xml
/robots.txt
Actions:
- Edge TTL → Ignore cache-control, 1 day
- Browser TTL → Override, 1 day
Rule 3: HTML Page Cache (catch-all, most important)
Conditions:
HTTP Host equals "utlkit.com"
Actions:
- Edge TTL → Ignore cache-control header, 14 days
- Browser TTL → Override, 7 days
- Ignore query string ✅ (critical!
/?cat=financeand/share the same cache)
How long should Edge TTL be? Too short and caching is useless; too long and users see stale content after deploys. Cloudflare Pages auto-purges cache on every deploy, so Edge TTL can be long. Browser TTL should be shorter (7 days) to avoid users being stuck on old versions forever.
Rule order: Static assets → Sitemap → HTML (catch-all last)
Step 2: Purge Everything
After creating rules, go to Caching → Configuration → Purge Everything to clear old cache.
Step 3: Verify
# First request
$ curl -sI https://utlkit.com/ | grep cf-cache-status
cf-cache-status: MISS ← Normal, first request
# Second request
$ curl -sI https://utlkit.com/ | grep cf-cache-status
cf-cache-status: HIT ← Working!
Verify key resources:
# Tool page
curl -sI https://utlkit.com/tools/bmi-calculator/ | grep cf-cache-status
# MISS → HIT ✅
# JS chunk
curl -sI https://utlkit.com/_next/static/chunks/main-app-xxx.js | grep cf-cache-status
# MISS → HIT ✅
# CSS
curl -sI https://utlkit.com/_next/static/css/xxx.css | grep cf-cache-status
# MISS → HIT ✅
Additional Fix: Localizing Sentry CDN
During debugging, I also found this in layout.tsx:
<script src="https://browser.sentry-cdn.com/8.30.0/bundle.min.js" />
browser.sentry-cdn.com frequently times out from China (sometimes completely unreachable). This 70KB script sits in <head> and blocks page rendering.
Simple fix: download it to public/:
curl -o public/sentry-bundle.min.js \
https://browser.sentry-cdn.com/8.30.0/bundle.min.js
Update the reference:
- <script src="https://browser.sentry-cdn.com/8.30.0/bundle.min.js" />
+ <script src="/sentry-bundle.min.js" />
Now the script is served through Cloudflare's own CDN — instant load in China.
⚠️ Note: Remember to manually update this file when Sentry releases new versions. Set a reminder to check quarterly.
Results
| Metric | Before | After |
|---|---|---|
| Cache hit rate | 7.62% | 90%+ |
| Homepage TTFB (cache hit) | 2-7s | 200-400ms |
| LCP P75 | 20s | 2-3s |
| First paint in China | Often 10s+ | Stable 3-5s |
Lessons Learned
Lesson 1: Cloudflare's new UI doesn't match old tutorials
Most online guides are based on the old Cache Rules interface. The parameter names and layout are completely different in the 2024+ UI. Settings like "Cache Level" and "Edge TTL" are now in sub-panels.
Fix: Edge TTL is now a dropdown — select "Ignore cache-control header and use this TTL" to unlock the time input. Maximum selectable: 30 days.
Lesson 2: Not ignoring query strings splits your cache
The homepage gets frequent visits as /?cat=finance, /?cat=developer, etc. Without ignoring query strings, CF treats each unique query as a different cache key. 104 tools × 8 categories = 800+ cache variants, killing your hit rate.
Fix: Enable "Ignore query string" so all queries share the same cached version.
Lesson 3: Browser TTL too long means users see stale content after deploys
I initially set Browser TTL to 14 days. After deploying a new version, a colleague complained "why is it still the old version?". Browser cache isn't affected by CF Purge — it only expires when TTL runs out.
Fix: Drop Browser TTL to 7 days, keep Edge TTL at 14 days. CF auto-purges Edge cache on deploy, so new visitors see the new version immediately. Returning users gradually update over 7 days.
Advanced: IP Optimization for China
If your site is still slow in China even with cache hits (because Cloudflare's default Anycast IPs route through congested international backbones), consider IP Optimization + Worker Proxy:
- Create
cdn.yourdomain.comin DNS (DNS only / grey cloud), CNAME to a third-party optimized IP pool - All subdomains grey-cloud CNAME to
cdn.yourdomain.com - Pages doesn't support routes — use a Worker as reverse proxy
- Worker uses route mode (supports IP optimization), forwards to Pages
This can reduce China latency from 300-500ms to 50-100ms, but maintenance is significantly more complex. For a tools site, Cache Rules optimization is usually enough.
Summary
Cloudflare Pages free plan + 3 Cache Rules = good enough global CDN.
Three key takeaways:
- Explicitly tell CF to cache HTML (it doesn't by default)
- Ignore query strings (prevents cache fragmentation)
- Separate Edge TTL from Browser TTL (Edge long, Browser short)
Free, simple, and highly effective.
*Based on the real-world optimization of utlkit.com. Site: utlkit.com
Top comments (0)