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:
- A clean build on the wrong branch. The artifact is valid, but it does not contain the intended article.
- A successful build without a deployment.
distchanged locally while the live Worker still serves the previous asset set. - 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.