Workers hosting with an optional script
Context and Problem Statement
A web app deploys to a Cloudflare Worker that serves static files. A static site could
deploy only to GitHub Pages, under /<repo>/. Some projects want the site on Cloudflare
too, on a custom domain, and some want a little server code (an API route) next to the
files of either. The template needs one way to do that for both, without a framework
adapter per site generator.
Considered Options
- Where the site goes: a
site_hostchoice (pagesorcloudflare), or always Workers - The custom domain: a
routesentry withcustom_domainin the wrangler config, orwrangler deploy --domainin the production job - Server code: a Worker script with
main, theASSETSbinding andrun_worker_first; a framework adapter such as Astro's Cloudflare one; or the Cloudflare Vite plugin - Its types and tests:
@cloudflare/workers-typesand a Vitest test with a stubASSETS;wrangler typeswith a generatedworker-configuration.d.ts; or@cloudflare/vitest-pool-workers
Decision Outcome
Chosen:
site_host, defaultpages, so every existing site renders as before. Withcloudflare, production deployswrangler.site.jsonc, the previews' own config, under its own name,<slug>-site.--domainfromsite_url, in the production job only. Previews deploy the same config under other names, and aroutesentry there would move the domain to each preview. wrangler 4.154.0 adds--domainto the config's routes as a custom domain.- One
workerquestion for both:main,assets.binding: "ASSETS"andrun_worker_first: ["/api/*"]. It works with any build that writes files, Vite or Astro or a plain copy, and requests for files still never run the script. - A web app's script is TypeScript with
@cloudflare/workers-typesin its ownworker/tsconfig.json, because those types clash with the DOM types of the app. Vitest runs its test in Node with a stubASSETS: no workerd and no extra test pool. A site's script is plain JavaScript tested withnode --test, so a site needs no TypeScript stack.
A script with only the ASSETS binding holds no secret, so
0001 still holds. A script
that gains KV, D1 or a secret triggers its revisit.
Consequences
- Good: one config per Worker serves production and previews; a domain is one answer.
- Good: the script's tests run without Cloudflare's runtime, in the checks every project already runs.
- Bad: the tests do not run workerd, so a runtime-only difference (such as a compatibility flag) shows up in the preview, not in the tests.
- Bad: the custom domain is not in the wrangler config, so a local
wrangler deploydoes not attach it. - Bad: a token that deploys a custom domain also needs Zone > Workers Routes: Edit.
Confirmation
scripts/check-render.sh asserts that only production passes --domain, that main,
the binding and run_worker_first appear only with worker, that a site on a Worker
renders no Pages job, and it runs a site script's tests through just site. CI's
TypeScript job type-checks and tests a web app's script.