Topic: Cloudflare

A Green Build Is Not a Live Release

A field note from this Astro blog: what local checks prove, what they do not, and how static assets reach a Cloudflare Worker.

It is tempting to see a green pnpm build and call a technical article published. The feeling is understandable: the page rendered, MDX parsed, and dist exists.

It is also incomplete.

This note records the release boundary used by this site. It is not a benchmark and it does not claim a production deployment happened merely because a local command succeeded. That distinction is small enough to sound pedantic until a stale deployment, wrong branch, or broken asset path makes a locally perfect page invisible to readers.

The deployment shape I am verifying

This repository has a narrow release path:

{
  "deploy": "pnpm build && wrangler deploy"
}

Its Wrangler configuration points static assets at the build output:

{
  "assets": {
    "directory": "./dist"
  }
}

Cloudflare uploads the configured assets directory as part of a Worker deployment and serves those assets to readers. That makes dist a necessary artifact, but it is still local until Wrangler successfully deploys it. A correct static-site workflow therefore has two separate questions: did the build produce the right files? and is the live domain serving those files?

What I verified

For the 2026-08-10 content update, I used three checks before considering the source ready:

pnpm lint
pnpm build
pnpm seo:check

Each one answers a different question:

Check Evidence it gives Evidence it does not give
pnpm lint The edited Astro, TypeScript, and MDX-adjacent source passes the configured lint rules. The page is built or deployed.
pnpm build Astro can render the routes and write dist. The production Worker received that dist.
pnpm seo:check The built sitemap, canonical URLs, hreflang links, JSON-LD, and route assertions match this repository’s rules. Search engines have crawled or ranked the change.

That separation caught a mistake in my own release vocabulary: “locally valid” and “live” are not synonyms. The correct handoff after a local build is ready to deploy, not deployed.

Inspect the built evidence before reaching for a dashboard

The first inspection needs no browser automation:

test -f dist/sitemap.xml
test -f dist/robots.txt
rg 'static-astro-release-evidence' dist/en dist/zh-TW

The first two commands confirm the generated crawler entry points exist. The last one makes sure both language versions of the new field note entered the static output. This is a useful, cheap check because a typo in an MDX filename or locale suffix can otherwise look like a content problem when it is really a missing route.

After an authorized deployment, live checks are a separate step:

curl -fsSI https://tech-blog.hungchihsueh.com/en/
curl -fsS https://tech-blog.hungchihsueh.com/sitemap.xml | xmllint --noout -

The first confirms that the published route responds. The second validates the live sitemap XML. Neither command proves that an individual reader saw a new version through every cache, but both are much stronger evidence than a local dist directory.

Trade-offs and failure modes

The static setup deliberately keeps runtime small: MDX becomes HTML during build, and the Worker serves assets. That removes database calls and server rendering from normal article reads. The trade-off is that content changes require a build and a deployment; there is no hidden CMS write that can make production current by itself.

Three failure modes are worth naming because they lead to confident but wrong status updates:

  1. A clean build on the wrong branch. The artifact is valid, but it does not contain the intended article.
  2. A successful build without a deployment. dist changed locally while the live Worker still serves the previous asset set.
  3. A deployment that skipped a required generated file. A route can respond while robots.txt, sitemap output, or a locale page is missing.

The cheap countermeasure is also boring: inspect branch and worktree before publishing, run the build checks, deploy only when authorized, then test the exact live URLs. I prefer this sequence to a more elaborate release abstraction because the site already has the commands required to prove each boundary.

What I deliberately did not add

I did not add a CMS, a release dashboard, or a Container to solve this. The site already has a static build output and a Worker assets directory. More infrastructure would create another state to verify without making the proof boundary clearer.

If the site later needs scheduled dynamic content or personalized pages, that is a new requirement and deserves a new design. Until then, a small checklist plus direct URLs is easier to trust and easier to debug.

Sources