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

Enter fullscreen mode Exit fullscreen mode

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/*

Enter fullscreen mode Exit fullscreen mode

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

Enter fullscreen mode Exit fullscreen mode

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"

Enter fullscreen mode Exit fullscreen mode

Actions:

  • Edge TTL → Ignore cache-control header, 14 days
  • Browser TTL → Override, 7 days
  • Ignore query string ✅ (critical! /?cat=finance and / 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!

Enter fullscreen mode Exit fullscreen mode

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 ✅

Enter fullscreen mode Exit fullscreen mode

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" />

Enter fullscreen mode Exit fullscreen mode

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

Enter fullscreen mode Exit fullscreen mode

Update the reference:

- <script src="https://browser.sentry-cdn.com/8.30.0/bundle.min.js" />
+ <script src="/sentry-bundle.min.js" />

Enter fullscreen mode Exit fullscreen mode

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:

  1. Create cdn.yourdomain.com in DNS (DNS only / grey cloud), CNAME to a third-party optimized IP pool
  2. All subdomains grey-cloud CNAME to cdn.yourdomain.com
  3. Pages doesn't support routes — use a Worker as reverse proxy
  4. 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:

  1. Explicitly tell CF to cache HTML (it doesn't by default)
  2. Ignore query strings (prevents cache fragmentation)
  3. 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