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.
Deployment shape and historical scope
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?
Verification recorded on 2026-08-10
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.
These historical commands used the previous domain. Use the updated domain checks in the next section for current releases:
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.
Update on 2026-09-10: verifying this revision
The public domain is now hch-log.com; the Worker is still named tech-blog. src/worker.ts redirects the old domain and www host, while static assets serve the articles. A 301 from the old domain proves a redirect, not a new article release.
Run these checks from the repository root. pnpm seo:check already builds the site, so another build immediately before it is unnecessary. The final assertions inspect both actual article files rather than a related-article link on an arbitrary page.
git status --short --branch
pnpm test
pnpm lint
pnpm seo:check
node --input-type=module <<'JS'
import assert from 'node:assert/strict';
import { readFileSync } from 'node:fs';
const slug = 'field-notes/static-astro-release-evidence';
for (const locale of ['zh-TW', 'en']) {
const html = readFileSync(`dist/${locale}/article/${slug}/index.html`, 'utf8');
assert(html.includes(`https://hch-log.com/${locale}/article/${slug}/`));
assert(html.includes('2026-09-10'));
assert(html.includes('hch-log.com'));
}
console.log('Both local article pages contain the current revision marker.');
JS
2026-09-10 identifies this revision. For a later release, replace the assertion with content unique to that revision. These commands check local files and upload nothing.
After a production deployment, inspect GET responses for both article locales: HTTP 200, a canonical on hch-log.com, the revised content, and working CSS/JS assets referenced by the response. A successful home page or HEAD request is not enough. The live sitemap must also contain the article and its correct modification date.
| Observation | Next step |
|---|---|
| Revision missing from dist | Check branch, MDX path, and build before purging a CDN |
| dist is correct, live article is old | Check deployment target, version, and HTTP caches |
| HTML is new, assets return 404 | Check asset delivery and old-asset retention; reloading does not fix that contract |
Verification for this article update covers the local build and preview. It does not establish a new production release. Search indexing and AdSense review remain separate outcomes.
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.