Notes

Exact _redirects rules beat splats

At the Cloudflare edge a catch-all splat can beat a narrower splat written above it, so where an exact rule and a splat overlap, put the exact rule above it and never rely on the order of two splats.

Workers Static Assets reads _redirects from the asset root. The docs say that when several rules share a source path the top-most is applied. That held locally and not at the edge. Local workerd applied /blog/tags/* above /blog/* in file order, but the deployed edge did not: /blog/tags/cloudflare/ fell through to the catch-all and landed at /articles/tags/cloudflare/, which does not exist. Nothing errors and the deploy stays green.

An exact path is a static redirect and a splat is a dynamic one, and the docs say static redirects should come before dynamic ones. So where an exact rule and a splat overlap, give the URL its own exact rule and put it above the splat. The build writes a pair for every topic it builds, one with the trailing slash and one without. For builds the built file reads:

/blog/ /articles/ 301
/blog/tags/ /topics/ 301
/blog/tags/builds/ /topics/builds/ 301
/blog/tags/builds /topics/builds/ 301
/blog/tags/* /topics/:splat 301
/blog/* /articles/:splat 301

The rules are generated from the topic pages the build produces rather than kept by hand, so a new topic gets its pair without anyone remembering to add it. The tag splat and the catch-all stay below as the fallback, in the order local workerd honours.

Two checks guard it. A build test asserts that each exact topic rule sits above the /blog/tags/* splat, and a smoke check after each deploy requests a live tag URL and fails unless it returns a 301 to the topic page.

Two other things the docs state that are easy to get wrong:

  • Only one splat is allowed per source pattern.
  • Redirects run before _headers, so a path matching both gets the redirect.

Source: Cloudflare Workers Static Assets redirects.