Which page
https://developers.cloudflare.com/workers/static-assets/headers/
What the page gets right
The combining behaviour is documented, clearly:
An incoming request which matches multiple rules' URL patterns will inherit all rules' headers.
If a header is applied twice in the _headers file, the values are joined with a comma separator.
What's missing is a warning about one consequence of that behaviour.
The gap
The worked example on that page joins X-Robots-Tag, where a comma-separated list is exactly what the header means. Readers reasonably generalise from it.
For a singleton header the same join produces something no browser can act on sensibly. Adding a longer cache lifetime to one file under an existing glob:
/lwe/quadgrams.txt
Cache-Control: public, max-age=604800, stale-while-revalidate=604800
/lwe/*
Cache-Control: public, max-age=0, s-maxage=86400
puts this on the wire (reproduced on wrangler 4.125.0, wrangler dev --local):
Cache-Control: public, max-age=604800, stale-while-revalidate=604800, public, max-age=0, s-maxage=86400
Two max-age values in one field. RFC 9111 leaves that ambiguous, so implementations resolve it however they like, and the result is worse than the single revalidation the narrower rule was meant to save.
Why it's easy to miss
Nothing errors, the asset still serves, and a browser still caches it somehow. curl -sI shows it only if you read the whole line instead of grepping for the directive you just added.
The other thing readers assume, because every other layered-config system in Workers behaves this way, is that the more specific rule wins. Wrangler's routes, CSP maps, and run_worker_first all let the specific entry take precedence. _headers is the exception.
Suggested addition
A short warning after the "joined with a comma separator" line, saying that joining works for list-valued headers like X-Robots-Tag and produces conflicting directives for singleton headers like Cache-Control.
It's worth stating the remedy in the same breath, because there isn't an obvious one: a file under a glob can only get a different Cache-Control by moving out of the glob's path or by setting the header in the Worker. Adding a narrower rule is not one of the options.
Which page
https://developers.cloudflare.com/workers/static-assets/headers/
What the page gets right
The combining behaviour is documented, clearly:
What's missing is a warning about one consequence of that behaviour.
The gap
The worked example on that page joins
X-Robots-Tag, where a comma-separated list is exactly what the header means. Readers reasonably generalise from it.For a singleton header the same join produces something no browser can act on sensibly. Adding a longer cache lifetime to one file under an existing glob:
puts this on the wire (reproduced on wrangler 4.125.0,
wrangler dev --local):Two
max-agevalues in one field. RFC 9111 leaves that ambiguous, so implementations resolve it however they like, and the result is worse than the single revalidation the narrower rule was meant to save.Why it's easy to miss
Nothing errors, the asset still serves, and a browser still caches it somehow.
curl -sIshows it only if you read the whole line instead of grepping for the directive you just added.The other thing readers assume, because every other layered-config system in Workers behaves this way, is that the more specific rule wins. Wrangler's routes, CSP maps, and
run_worker_firstall let the specific entry take precedence._headersis the exception.Suggested addition
A short warning after the "joined with a comma separator" line, saying that joining works for list-valued headers like
X-Robots-Tagand produces conflicting directives for singleton headers likeCache-Control.It's worth stating the remedy in the same breath, because there isn't an obvious one: a file under a glob can only get a different
Cache-Controlby moving out of the glob's path or by setting the header in the Worker. Adding a narrower rule is not one of the options.