Build once, ship twice
open.awareride.com runs on two CDNs. GitHub Pages is the free default; Cloudflare Pages is the one I actually point the domain at for better caching and edge behavior. I did not want to maintain two build pipelines, so the deploy workflow builds exactly once and fans out.
It is also manual - on: workflow_dispatch, nothing automatic on push. A broken main should
never silently take the site down; I run the deploy from the Actions tab when I am ready.
The shape
on:
workflow_dispatch:
jobs:
build:
# npm ci -> npm run build -> upload two artifacts
# - pages artifact for GitHub Pages
# - dist-cf artifact for Cloudflare
deploy-gh-pages:
needs: build
uses: actions/deploy-pages@v4
deploy-cf-pages:
needs: build
# wrangler pages deploy
One build job produces dist/ and uploads it twice. Two downstream jobs depend on build and
deploy in parallel - GitHub Pages via the official deploy-pages action, Cloudflare via
wrangler pages deploy.
The gotchas (in order)
None of it worked the first time. Each failure was a small, fixable thing:
-
npm cineeds a lock file. The workflow aborted with “Dependencies lock file is not found”.package-lock.jsonhad to be committed -npm cirefuses to run without it. -
Wrangler needs the token as an env var, not just a secret. Storing
CLOUDFLARE_API_TOKENin the repo was not enough; wrangler in a non-interactive runner only sees it when it is passed to the step’senv:. I put bothCLOUDFLARE_API_TOKENandCLOUDFLARE_ACCOUNT_IDbehind acf-deployenvironment and expose them to the deploy step. -
The Pages project does not exist. The first deploy failed with
The Pages project "awareride" does not exist. Cloudflare’s dashboard no longer even shows a “Pages” creation tab - only “Create a worker” - so I create the project from wrangler instead, and the workflow now does it defensively:npx wrangler pages project create awareride --production-branch=main 2>/dev/null || true npx wrangler pages deploy dist --project-name=awareride --branch=main -
The preview URL was unreachable. After
wrangler pages deployfinally succeeded and uploaded the files,https://<hash>.awareride.pages.devstill would not load in a browser until the custom domain and DNS were wired up toopen.awareride.com. The deploy succeeding and the site being reachable are two different things.
The CNAME
For GitHub Pages, a public/CNAME containing open.awareride.com tells Pages which custom domain
to serve. Cloudflare Pages gets the same domain configured on its side. Both targets answer for one
hostname; whichever you hit, you get the same built dist/.
The whole thing is one workflow, one build, two deploys, and a manual button. That is exactly the amount of automation I wanted - no more, no less.
Part of the series on building open.awareride.com. Previous: Rebuilding open.awareride.com on Astro. Next: two locales with per-page fallback.