Your deploy target should be the most boring part of the entire build. One config file, one command, assets served from the edge nearest every visitor — and here is the exact wiring, copy-pasteable.
This is the deploy half of the AI-native stack from AI-Native Website in 4 Weeks: Astro 6 builds a static dist/, and Cloudflare Workers Static Assets serves it from the edge with a thin Worker in front for the handful of edge-side concerns a pure static host cannot cover. Static Assets shipped in 2024 and replaced Cloudflare Pages for new builds — one runtime, one deploy, one config file, no seam between "static host" and "edge compute." Everything below assumes your Astro build already produces a clean dist/; this is the Cloudflare side.
The wrangler.toml
About thirty lines. The canonical pattern:
name = "nbm-site-v2"
main = "src/index.ts"
compatibility_date = "2026-05-15"
compatibility_flags = ["nodejs_compat"]
account_id = "your-cloudflare-account-id"
workers_dev = false
[assets]
directory = "./dist"
binding = "ASSETS"
not_found_handling = "404-page"
html_handling = "auto-trailing-slash"
[[routes]]
pattern = "nobrainermedia.com/*"
custom_domain = true
zone_name = "nobrainermedia.com"
[[routes]]
pattern = "www.nobrainermedia.com/*"
custom_domain = true
zone_name = "nobrainermedia.com"
[observability]
enabled = true
head_sampling_rate = 1.0
[vars]
ENVIRONMENT = "production"
Six choices earn naming:
- The
[assets]block is the Static Assets binding.directorypoints at the Astro output;binding = "ASSETS"is what the Worker references (env.ASSETS.fetch(request));not_found_handling = "404-page"servesdist/404.htmlon misses;html_handling = "auto-trailing-slash"resolves both/aboutand/about/to the same canonical content. compatibility_datepins the runtime to a tested Cloudflare release — do not auto-track latest.maindeclares the Worker entry point. Pure static sites can omit it, but most production sites want at least one edge-side concern, so the entry point is standard.[[routes]]binds the Worker to your custom domains — two here, because the site canonicalizeswww→ non-wwwat the edge.workers_dev = falsedisables the default*.workers.devURL so it never leaks into search results.[observability]turns on the per-request log surface for Logpush.
The Worker entry point
About thirty lines of TypeScript handling what the pure Static Assets surface cannot:
type Env = {
ASSETS: Fetcher
ENVIRONMENT: string
}
export default {
async fetch(request: Request, env: Env): Promise<Response> {
const url = new URL(request.url)
// Canonicalize www → non-www
if (url.hostname === 'www.nobrainermedia.com') {
url.hostname = 'nobrainermedia.com'
return Response.redirect(url.toString(), 301)
}
// Delegate everything else to the Static Assets binding
return env.ASSETS.fetch(request)
},
}
Three patterns: www canonicalization at the Worker boundary (a 301 in code scales without the per-domain page-rule quota); the binding handles every 200 (the Worker is a thin shim — insert A/B, geo-routing, or consent-aware analytics before the final env.ASSETS.fetch); and environment-typed bindings (the Env type catches binding-name typos at compile time, not deploy time).
The _headers and _redirects files
Two convention files in public/ carry into dist/ and configure the runtime. dist/_headers sets cache-control and security headers per pattern:
/*
Strict-Transport-Security: max-age=31536000; includeSubDomains; preload
X-Content-Type-Options: nosniff
Referrer-Policy: strict-origin-when-cross-origin
Permissions-Policy: geolocation=(), microphone=(), camera=()
/_astro/*
Cache-Control: public, max-age=31536000, immutable
/*.html
Cache-Control: public, max-age=300, must-revalidate
Fingerprinted assets (/_astro/…) get a one-year immutable cache — the filename hash invalidates it on change; HTML gets a 5-minute must-revalidate cache, balancing freshness against edge efficiency; security headers (HSTS, nosniff, Referrer-Policy, Permissions-Policy) apply globally. dist/_redirects carries the redirect map from the Legacy CMS Migration Playbook, emitted at build time from the URL inventory, never hand-edited:
/old-services/ /services/ 301
/blog/category/seo/ /tag/seo/ 301
/wp-admin/* /404 410
Want this stack deployed correctly the first time? Talk to the team that runs it. →
The deploy — one shot from CI
Install, build, deploy:
# .github/workflows/deploy.yml
name: Deploy Production
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v3
- uses: actions/setup-node@v4
with:
node-version: '24'
cache: 'pnpm'
- run: pnpm install --frozen-lockfile
- run: pnpm build
- uses: cloudflare/wrangler-action@v3
with:
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
command: deploy
pnpm build produces dist/; wrangler deploy uploads it and updates the Worker. End-to-end is 30–90 seconds depending on bundle size. Scope the CLOUDFLARE_API_TOKEN to deploy-only (Workers Scripts: Edit, Workers Routes: Edit, Account: Read) — never a full account token. Rollback is one command: wrangler rollback flips the active pointer to the retained previous deploy.
Post-deploy verification
Four checks before you call it done:
- Canonical serving.
curl -I https://nobrainermedia.com/returns200with the expected headers; thewwwform returns301to canonical. - 304 on repeat. Capture the
etag, re-request withif-none-match— a304 Not Modifiedconfirms the edge honors conditional requests. - Edge propagation. The Cloudflare request map should show every region on the new version within five minutes; slower means a Cloudflare-side incident.
- 404 handling. A missing path returns
404with yourdist/404.htmlbody — a default Cloudflare error page meansnot_found_handlingis misconfigured.
The four failure modes
- Misconfigured [assets] binding.
directorymust point at./dist, not./public. Verify Astro'soutDirmatches. - Stale compatibility_date. Pinning more than ~6 months back risks runtime regressions on date-gated changes. Refresh after each tested Cloudflare release.
- Custom-domain DNS lag. The first deploy against a new domain takes 30–120 seconds to propagate; let
custom_domain = trueprovision DNS, wait it out, verify withdig. - Redirect loops. A
/about/ → /aboutrule plusauto-trailing-slashcan loop infinitely. Pick one canonical form and reserve_redirectsfor legacy URLs, not canonical-form enforcement.
Closing
Workers Static Assets is the deploy target that lets the AI-native stack live up to its claims — sub-1.5-second LCP from the nearest edge, zero monthly cost at most service-business volumes, one-command deploy from CI. The recipe is short because the technology is small. The boring stack that prints does not need elaborate deploy plumbing; it needs the plumbing configured once, correctly, and never deviated from. Variations should be justified at code review.
Ready to ship the Astro + Workers deploy in week 4 of the cluster build? Book a 30-minute call →
Get the kit, not just the theory.
We'll send the build checklist behind this post — and the next pillar when it ships. One email, no drip sequence. Unsubscribe in one click.