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
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.
1. Cache-Control in _headers is ignored for static assets
The symptom: after a deploy, one page said the catalogue had 211 games while another route, in the same browser at the same moment, said 191. The prerendered HTML was new. The JavaScript data file the app shell reads was four hours old.
So I added the obvious rule:
/data.js
Cache-Control: public, max-age=0, must-revalidate
Deployed it, fetched the file, and the response still said:
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 is the old one: put a content hash in the URL. My build now hashes data.js, the compiled bundle and the stylesheet and writes them into index.html as ?v=<hash>. One ordering trap: the hashing step has to 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.
2. _redirects silently stops at about 100 rules
The docs give a budget of 2,000 static and 100 dynamic redirects. In production, every rule after roughly the 104th line in my file did nothing. No build error, no warning, the rule just never fired.
I first suspected a size limit, so I cut the file from 15.3 KB to 6.4 KB by deleting comments and merging rules. The boundary stayed at the same rule. That makes it a count cap, not a byte cap, at least for a file that mixes static and splat rules the way mine does.
What I do now:
| 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 |
The canary is the cheapest monitoring I have. If the last rule answers 301, every rule above it is inside the cap.
Bonus: a deleted page keeps answering 200 for up to a week
After 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. Following redirects 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.
Originally published by Dev.to WebDev. Aggregated on AIWithGhost for educational purposes — full credit and traffic to the original publisher.