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 assets — newerkey-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:
- Delete the CNAME in the
newerkey.comzone’s DNS records (the one pointing tohashnode.network). - Re-add the custom domain on the Worker. Cloudflare provisions the DNS record and the SSL certificate itself this time.
- 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.