Deploying a static site to Cloudflare Workers
Originally published on indiecore.net . I moved this site off a hosted blogging platform onto Cloudflare, with GitHub Actions doing the building and Cloudflare doing the serving. It costs nothing, deploys in about two and a half minutes, and refuses to publish anything that fails its checks. Getting there took longer than it should have. Here is the setup, and the five things that tripped me up — none of which are obvious from the documentation. The shape of it git push └─ GitHub Actions ├─ build generate the site ├─ verify dead links, missing images, bad metadata, broken redirects ├─ Lighthouse fail if performance/accessibility/SEO drop below budget └─ deploy upload to Cloudflare The important part is that deploy depends on the checks . A broken build never reaches the internet. Pull requests get a preview URL; merges to main go live. The triggers and permissions that make that safe: ci-cd.yml — triggers and permissions name : CI/CD # Build and verify every change; deploy previews for PRs and production from main. # Deploy jobs depend on the quality gates, so nothing ships unverified. on : push : branches : [ main ] # The SEO watch ledger is machine-written, is never part of dist/, and is # committed daily. Deploying the site again for it would be pure noise — and # would re-trigger the SEO ping through workflow_run every single day. paths-ignore : - ' _source/seo-watch.json' pull_request : branches : [ main ] workflow_dispatch : # Least privilege by default; individual jobs elevate only what they need. permissions : contents : read # Supersede in-flight runs for a branch, but never interrupt a production deploy. concurrency : group : ci-cd-${{ github.ref }} cancel-in-progress : ${{ github.ref != 'refs/heads/main' }} env : # wrangler ships as a pinned devDependency; never phone home from CI WRANGLER_SEND_METRICS : " false" permissions: contents: read at the top means every job starts with the minimum, and only the one that comments on pull requests gets more. The c