·3 min read

Hosting Notes: Cloudflare Pages, Workers, and the Day They Fought Each Other

What I learned deploying a Next.js static site to Cloudflare Pages — and why a forgotten Worker silently ate a whole week of deploys.

Short version of this post: if you deploy a Pages site and its custom domain stubbornly refuses to update, a Worker is probably intercepting the hostname. The DNS record can point wherever it wants; Workers win.

The setup

dashboard.e4z.xyz is a Next.js 16 static export deployed to a Cloudflare Pages project called e4z-xyz. Routing looks like this on paper:

Browser → Cloudflare DNS (proxied) → Pages project → ./out

DNS is a proxied CNAME from dashboard.e4z.xyz to e4z-xyz.pages.dev. Simple.

The symptom

I deployed. The build passed. e4z-xyz.pages.dev served the new content. dashboard.e4z.xyz served content from two weeks ago. Cache purges did nothing. Hard refreshes did nothing. A full purge_everything did nothing.

The tell

Headers don't lie:

$ curl -sI https://dashboard.e4z.xyz/ | grep -iE 'x-opennext|x-powered'
x-powered-by: Next.js
x-opennext: 1

$ curl -sI https://e4z-xyz.pages.dev/ | grep -iE 'x-opennext|x-powered'
# (nothing)

x-opennext: 1 is emitted by OpenNext's Worker runtime. Pages doesn't emit that header. Something Worker-shaped was in the request path on the custom domain but not on the project domain.

The cause

Two layers of shadowing:

  1. An old Worker named e4z-dashboard still had dashboard.e4z.xyz bound as a custom domain.
  2. A zone-level wildcard Worker route *.e4z.xyz/* pointed at a script called e4z-wildcard-test — a leftover experiment from three weeks prior.

The wildcard was the real killer. It swallowed every subdomain in the zone. Cloudflare's routing precedence puts Worker custom domains and routes above Pages custom domains, so DNS was essentially irrelevant.

The fix

In order:

  1. Remove the Worker's custom domain (e4z-dashboard → Settings → Triggers).
  2. Delete the wildcard route and its script.
  3. Remove and re-add dashboard.e4z.xyz as a custom domain on the Pages project. The binding gets into a weird state after being shadowed for a while; a clean re-add reprovisions the SSL cert and rebuilds the routing.
  4. Purge cache.

Page served. Ten minutes of head-scratching, then it just worked.

What I'd do differently

  • Name test Workers like test Workers. e4z-wildcard-test is the right name, but routing scope is what matters. A wildcard route is a production foot-gun even if the script is called -test.
  • Prefer specific routes over wildcards. order.e4z.xyz/* is fine. *.e4z.xyz/* is a trap.
  • x-opennext / x-powered-by are your friends. If a Pages site's custom domain is serving something unexpected, check the headers before anything else — they'll tell you immediately whether you're actually hitting Pages.

Next one up: moving an old Next.js site from Vercel to Cloudflare Pages, and where the Prisma adapter falls over.