Two Cloudflare Pages behaviours I had to measure to believe
Two Cloudflare Pages Behaviours I Had to Measure to Believe
I run a static browser-games site on Cloudflare Pages: about 4,700 prerendered HTML files, a small React shell, no server. Two things cost me days because the platform did not fail loudly. Both are easy to reproduce, so here are the measurements rather than opinions.
Cache-Control in _headers Is Ignored for Static Assets
The symptom: After a deploy, one page reported the catalogue had 211 games while another route in the same browser at the same moment reported 191. The prerendered HTML was new, but the JavaScript data file the app shell reads was four hours old.
What happened: I added the obvious rule /data.js Cache-Control: public, max-age=0, must-revalidate. After deploying it, fetching the file still returned cache-control: public, max-age=14400, must-revalidate. The interesting part is that _headers itself was clearly being applied - my catch-all rule sets X-Content-Type-Options, and that header was on the very same response. So the file is read, the path matches, and this one header is overridden for static assets. HTML is the exception: Pages already serves it with max-age=0, must-revalidate.
The fix that the platform cannot override: Put a content hash in the URL. My build now hashes data.js, the compiled bundle, and the stylesheet, writing them into index.html as ?v=<hash>. One important ordering trap: the hashing step must run after the bundler, otherwise you hash the previous build's bundle and ship a URL that points at stale content with a fresh-looking version.
_redirects Silently Stops at About 100 Rules
The documentation gives a budget of 2,000 static and 100 dynamic redirects. In production, every rule after roughly the 104th line in my file did nothing. There were no build errors or warnings - the rule simply never fired.
What I discovered: Cutting the file from 15.3 KB to 6.4 KB by removing comments and merging rules kept the boundary at the same rule. This indicates a count cap, not a byte cap, for files mixing static and splat rules.
My current practices:
| Practice | Why |
|---|---|
| Keep the file under 100 rules | Everything past the cap is silently dead |
| Order by importance: load-bearing 301s first, nice-to-haves last | If the cap bites, it bites the rules that matter least |
| Make the last rule a canary | One curl -sI after each deploy tells me whether the whole file is live |
Bonus observation: When I removed about 1,100 pages, a plain request to one of the deleted URLs still returned 200 with the old body and Cache-Control: public, s-maxage=604800, a seven-day shared cache. The same URL with a throwaway query string ?cb=8471) returned 404 in the same second, because the query string changes the cache key and misses the stale entry. So when you delete pages on Pages, verify with a cache-buster. A plain request shows you the corpse and you will think the deploy failed. It drains on its own; a cache purge is the only way to hurry it.
How I Test Now
There is no test suite for a static site like this, so I serve the build output locally the way Cloudflare does (the file if it exists, otherwise 404.html with a real 404) and sweep it with Playwright, with redirects disabled. The tool reports the final 200 and hides broken sitemap entries and wrong canonicals. The site these measurements come from is 360 PlayZone, a free browser-games portal. If you have seen the rule cap land somewhere other than ~100, I would like to compare notes. I measured mine on 360playzone.com in August 2026 and it has not moved since.
Comments
No comments yet. Start the discussion.