Skip to content

ADR-0007: Deploy as a Worker with static assets instead of a Pages project

Listen to this page5:02
Read the transcript

1. The ground moved under a settled decision

Host: So we’ve got ADR-0007, and the setup here is a little unusual because this isn’t a story about a bad decision getting fixed. ADR-0005 picked Cloudflare Pages via git integration, and by every account that was the right call at the time. So what actually forced a new ADR?

Guest: The platform moved out from under it. ADR-0005 was written against a specific flow — Workers and Pages, Create, Pages, Connect to Git — with build settings tuned for that: build command, output directory, root directory. But if you go create a new git-connected project on Cloudflare today, you don’t land in that flow anymore, you land in Workers Builds. That setup form doesn’t even ask for an output directory, it asks for a deploy command, and critically, it deploys a Worker, not a Pages project. Same goal, totally different artifact underneath.

2. Three silent breakages

Host: Okay, so say you just point the same repo at Workers Builds and don’t touch anything else. Walk me through what actually breaks.

Guest: Three things, and none of them throw an obvious error at you. First, wrangler deploy just refuses outright, because the config still has pages_build_output_dir in it, which marks the project as Pages, and the Workers deploy command isn’t valid against that. Second, even if you strip that out, bare wrangler deploy at the root of a pnpm workspace flat out declines to run — it says application detection has been run at the root of a workspace instead of targeting a specific project, because it won’t guess which package you mean, and that’s a monorepo-specific failure you won’t find in a single-package tutorial.

Host: And the third one — that’s the one that doesn’t even fail, right? It just quietly does the wrong thing.

Guest: Exactly, and it’s the worst of the three. CF_PAGES_URL simply doesn’t exist on Workers Builds, it’s a Pages-only variable, but the site config was reading it to derive the canonical site URL. So on Workers it silently falls through to the hard-coded fallback, the build goes green, the site renders fine to a human — and every one of the fifty pages plus every entry in sitemap-0.xml gets stamped with the wrong canonical URL.

3. Weighing the way out: legacy flow, split repo, or static-assets Worker

Host: So you’re staring at this with three failure modes on the table. What were the actual options for getting out of it?

Guest: Three paths. First, hunt down the legacy Pages project creation flow and just stay put — it still exists for now, but Cloudflare is visibly steering people away from it, and it doesn’t actually solve the monorepo detection problem or the canonical URL problem, it just avoids confronting them. Second, pull the site out of the pnpm workspace into its own repository so wrangler’s auto-detection has a single package to find, no ambiguity.

Host: That second one sounds tempting on the surface — trade a messy detection problem for a clean repo boundary. What’s the catch?

Guest: The catch is that it undoes the exact thing ADR-0002 set up on purpose. The monorepo exists so a change to a shared component doesn’t need a version bump and publish step, and so the labs and packages sit right next to the site they document. Splitting the site out for a one-line deploy flag would be solving a Workers problem by breaking a cohesion decision we made deliberately, which left the third option — deploy as a Worker with static assets, no server code, no main entry point, just the built directory served from the edge, functionally what Pages was already doing.

4. The decision and what it costs going forward

Host: So walk me through what actually landed. It’s an assets block pointing at the dist folder, no main entry point, and a deploy command that names the config file explicitly — why does that last part matter so much?

Guest: Because without naming wrangler.toml, the CLI tries to auto-detect a workspace and gets confused inside a monorepo, and it only breaks here, which is exactly the kind of failure someone simplifies away without noticing. That’s why the explanation lives in the config file itself, right next to the flag, instead of in an ADR nobody rereads. Same logic applies to SITE_URL — we set it explicitly now instead of trusting CF_PAGES_URL to be there, because a silent absence is fine but a silently wrong value isn’t.

Host: And the tradeoffs ride along with it — preview URLs look different now, but you’ve bought yourself an easier path if real server logic ever shows up, and CI still can’t confirm any of this actually worked, only that it built. That last part feels like the honest ending to this whole story: the config can be right and you still have to go look. Which is exactly why that post-deploy checklist exists.

Not covered

The planner wanted these and found nothing in the source to support them:

  • A deeper narrative about the original GitHub Pages era and the base-path link prefixing work (only tangentially referenced here, not the focus of ADR-0007)

Generated from this page by Claude Sonnet 5 on , spoken by Kokoro-82M running locally. Two synthetic voices, not a recorded conversation. Every claim is drawn from this page — where it differs from the text above, the text is correct.

ADR-0007acceptedAugust 10, 2026

Accepted, superseding ADR-0005. Implemented by replacing pages_build_output_dir in wrangler.toml with an [assets] block, and by preferring SITE_URL over CF_PAGES_URL in apps/handbook/astro.config.mjs.

ADR-0005 chose Cloudflare Pages via git integration, and its build settings — build command pnpm build, output directory apps/handbook/dist, root directory / — were written against the Workers & Pages → Create → Pages → Connect to Git flow.

That flow is no longer where Cloudflare puts new git-connected projects. Creating one now lands in Workers Builds, whose setup form asks for a deploy command (npx wrangler deploy) rather than an output directory, and which deploys a Worker rather than a Pages project. The decision in ADR-0005 was not wrong when it was made; the platform moved underneath it.

Three things break when the same repository is pointed at Workers Builds unchanged, and none of them announce themselves:

  • wrangler deploy rejects the Pages configuration. pages_build_output_dir marks the project as Pages, and the Workers deploy command is not valid against it.
  • Bare wrangler deploy refuses to run at a pnpm workspace root, with “The Cloudflare application detection logic has been run in the root of a workspace instead of targeting a specific project.” Wrangler tries to infer which workspace package to deploy and declines to guess. This is specific to monorepos and does not appear in any single-package guide.
  • CF_PAGES_URL does not exist on Workers Builds. It is a Pages variable. The site config read it to derive site, so on Workers it would fall through to the hard-coded fallback and stamp a wrong canonical URL onto all 50 pages and every entry of sitemap-0.xml — while the build stays green and the site looks correct to a human.
  • Stay on Pages by finding the legacy creation flow. Possible for now, but it means building on the path Cloudflare is steering projects away from, and the flow may not survive. It also leaves the monorepo and canonical-URL problems unexamined rather than solved.
  • Move the site out of the workspace into its own repository so wrangler’s auto-detection has a single package to find. This trades a one-line deploy flag for splitting the labs, packages, and content away from the site they document — the monorepo exists for exactly that cohesion (ADR-0002).
  • Deploy as a Worker with static assets, keeping the monorepo. No server code and no main entry point, so Cloudflare serves the built directory from its edge — functionally what Pages did. Costs an explicit -c flag on the deploy command and an explicitly configured site URL.

Deploy as a static-assets Worker through Workers Builds.

wrangler.toml declares an [assets] block pointing at apps/handbook/dist, with not_found_handling = "404-page" so Astro’s own 404 is served instead of Cloudflare’s. There is no main, so no Worker code runs on a request.

The deploy command must name the config file — npx wrangler deploy -c wrangler.toml — which skips workspace auto-detection entirely. Paths inside the config resolve relative to the config file, so the repository root stays the correct working directory and pnpm build still resolves the workspace.

site now reads SITE_URL first, set explicitly in the Cloudflare build settings, falling back to CF_PAGES_URL so a Pages deployment continues to work unchanged.

Cloudflare project settings, for the record:

Setting Value
Build command pnpm run build
Deploy command npx wrangler deploy -c wrangler.toml
Path /
Environment variable SITE_URL = the deployment’s own URL
  • The deploy command carries a flag that looks redundant and is not. -c wrangler.toml is load bearing, and removing it fails only inside a workspace — which is to say, only here. The reason is recorded in wrangler.toml itself, next to the thing it protects, because a comment in an ADR would not be read by whoever simplifies that command.
  • The site URL is now configuration rather than something the platform supplies. That is more to set up once and one less thing that can be silently absent — the previous arrangement failed by producing plausible wrong output, which is the worse failure.
  • Preview deployments change shape: non-production branches use wrangler versions upload, which produces a version preview URL rather than the per-branch *.pages.dev subdomain ADR-0005 described.
  • Assets are served by the Workers runtime, so adding real server behavior later — redirects, auth-gated pages, an API route — becomes a matter of adding a main entry point rather than migrating platforms. Pages would have required moving to Functions.
  • CI still cannot verify any of this. .github/workflows/site-ci.yml proves the build and the links; whether Cloudflare is configured correctly is only observable on the deployed URL, which is why docs/DEVELOPMENT.md carries a post-deploy checklist.