newerkey notes
// Cloud · 1 July 2026 · 4 min read

Cloudflare Pages, Workers, and Moving a Live Domain Off Hashnode

I meant to create a Cloudflare Pages project and ended up with a Worker instead. Here's the architecture mismatch that caused, and how I moved notes.newerkey.com off Hashnode with no downtime.

newerkey cloudflare wrangler dns custom-domain hashnode-migration

Two things happened while getting this site live: I built the wrong kind of Cloudflare project by accident, and I moved a domain that was actively serving traffic on Hashnode without a gap in between. Neither went the way I expected.

Trying to create a Pages project

Cloudflare’s dashboard now puts Workers and Pages behind a single Create application button. The options it offers:

  • Connect GitHub
  • Connect GitLab
  • Start with Hello World
  • Select a template
  • Upload your static files

I didn’t want to connect GitHub directly to Cloudflare — GitHub Actions already owns the build and deploy, and I didn’t want two systems racing to deploy the same repo. So I picked Upload your static files, expecting Direct Upload mode on a Pages project, uploaded a placeholder HTML file to initialize it, and named it newerkey-notes.

What I got was a Worker with static assetsnewerkey-notes.newerkey.workers.dev — not a Pages project. Cloudflare has quietly merged the two products under the hood, and the “upload your static files” path now defaults to a Worker.

This mattered immediately: my GitHub Actions workflow was written around cloudflare/pages-action@v1, which deploys to Pages projects specifically. Pointed at a Worker, it had nothing valid to target.

Wrangler instead of the Pages action

Rather than delete the Worker and start over chasing an actual Pages project, I kept what Cloudflare had created and changed the deploy method to match it: wrangler-action instead of pages-action, with a wrangler.toml describing the Worker directly.

name = "newerkey-notes"
compatibility_date = "2025-06-29"

[assets]
directory = "./dist"

[env.staging]
name = "newerkey-notes-staging"

The [assets] block is what makes this a static-asset Worker rather than a code Worker — it serves the built dist/ folder directly, no Worker script needed. The [env.staging] block gives the same wrangler deploy command a second target (--env staging) that resolves to a completely separate Worker, so staging and production never share a deployment.

This turned out to be the more current path anyway — Wrangler is where Cloudflare is investing, and treating the whole thing as “a Worker with a wrangler.toml” is simpler than reasoning about Pages and Workers as two separate products with different tooling.

Moving the domain off Hashnode

notes.newerkey.com was live on Hashnode before this migration, via a DNS record:

notes.newerkey.com   CNAME   hashnode.network   (DNS only)

To point the domain at the new Worker instead, I went to the Worker’s Settings → Domains & Routes → Add Domain and entered notes.newerkey.com. Cloudflare refused it outright:

Hostname 'notes.newerkey.com' already has externally managed DNS records
(A, CNAME, etc). Delete them first or try a different hostname.

Cloudflare won’t let a custom domain claim a hostname that already has a manually-managed DNS record pointing somewhere else — which is the correct behavior, since silently overriding it could break the live site with no warning. The fix was in the right order:

  1. Delete the CNAME in the newerkey.com zone’s DNS records (the one pointing to hashnode.network).
  2. Re-add the custom domain on the Worker. Cloudflare provisions the DNS record and the SSL certificate itself this time.
  3. Remove the custom domain from Hashnode’s own dashboard (Dashboard → Domain → Custom Domain), so Hashnode stops treating that hostname as its own and won’t contest it later.

Steps 1 and 2 happen inside the same Cloudflare account, so there’s no propagation gap between “old site down” and “new site up” — the CNAME deletion and the Worker’s domain claim happen back to back, and Cloudflare’s edge picks up the new target almost immediately. Step 3 is cleanup, not a dependency — Hashnode no longer controls the DNS, so it doesn’t matter when I get to it, only that I do.

The result

$ curl -sI https://notes.newerkey.com
HTTP/2 200
server: cloudflare
cf-cache-status: HIT

Served by Cloudflare, from the Worker, with the build that GitHub Actions produced. No maintenance page, no downtime window.

The takeaway

The actual lesson wasn’t about DNS mechanics — it was that the Cloudflare dashboard’s product boundaries (Pages vs. Workers) are less stable than the underlying primitives (wrangler.toml, DNS records, custom domains). Reasoning from the primitives up got me to a working setup faster than trying to match whatever the dashboard called things that week.

newerkey cloudflare wrangler dns custom-domain hashnode-migration

// newerkey notes

Engineering notes on Linux, infrastructure, automation, and platform systems — written as I learn and build.

about these notes