Deploying Astro to Cloudflare Workers Static Assets
Cloudflare's hosting for static sites has converged on Workers: a Worker project can serve a directory of static assets from the edge for free, run code only for the routes that need it, and use the full set of Worker features — versions and gradual rollouts, cron triggers, queues, Durable Objects — alongside. For a static Astro site, that means a deploy target with Pages-like simplicity and a clear path to adding server-rendered routes or APIs later without changing platforms.
This guide deploys a 410-page static Astro site to Workers static assets: the wrangler configuration, matching Astro's URL settings, headers and redirects, 404 handling, preview versions and gradual rollouts, and adding a single server route with the Cloudflare adapter. It is part of Cloudflare Pages Edge Caching Setup, and the migration from an existing Pages project is covered in Migrating from Cloudflare Pages to Workers Static Assets.
Prerequisites
- Astro 4.16 or 5.x with
output: 'static'(the default). - Wrangler 3.90+ and a Cloudflare account; a custom domain on Cloudflare DNS.
- CI credentials: an API token with Workers edit permission, stored as a secret.
Step 1: Align Astro's URL Settings
Decide on URL shape first, because Astro and the asset server must agree. For directory-style URLs with trailing slashes:
// astro.config.mjs
export default defineConfig({
site: 'https://docs.example.com',
trailingSlash: 'always',
build: { format: 'directory' } // about/index.html
});
This matches Workers' default html_handling = "auto-trailing-slash", which serves about/index.html at /about/ and redirects /about to /about/ with a 307. If Astro used build.format: 'file' (about.html), the same setting serves it at /about and redirects /about/ to /about. Consistency with canonical URLs and internal links matters more than which one you pick; see Canonical URLs and Duplicate Content on Static Sites.
Step 2: The Wrangler Configuration
A fully static site needs no Worker script at all:
# wrangler.toml
name = "docs-site"
compatibility_date = "2026-09-01"
[assets]
directory = "./dist"
html_handling = "auto-trailing-slash"
not_found_handling = "404-page"
[[routes]]
pattern = "docs.example.com"
custom_domain = true
not_found_handling = "404-page" serves the nearest 404.html walking up from the requested path, with a 404 status — so /de/missing/ gets dist/de/404.html if one exists, which suits multilingual sites. custom_domain = true creates the DNS record and certificate automatically.
Deploy with npx wrangler deploy. Requests for static files are served from Cloudflare's edge without invoking code and are not billed as Worker requests.
Step 3: Headers and Redirects
Workers static assets read _headers and _redirects files from the assets directory, in the same format as Pages. Put them in Astro's public/ folder so they are copied to dist/:
# public/_headers
/_astro/*
Cache-Control: public, max-age=31536000, immutable
/*
X-Content-Type-Options: nosniff
Referrer-Policy: strict-origin-when-cross-origin
Strict-Transport-Security: max-age=31536000; includeSubDomains
# public/_redirects
/docs/installation.html /guide/install/ 301
/blog/:year/:slug/ /articles/:slug/ 301
HTML gets no explicit Cache-Control here; Workers static assets serve HTML with revalidation by default and handle cache invalidation on deploy themselves, so no purge step is needed. Redirect limits and splats behave as on Pages; generating _redirects from a data file keeps it testable, as in Testing Redirects and Headers Before Deploy.
Step 4: Preview Versions and Gradual Rollouts
Workers separates uploading a version from deploying it. In CI, pull requests upload a version and get a preview URL; merges to main deploy:
# pull request job
- run: npm ci && npm run build
- run: npx wrangler versions upload --tag "pr-${{ github.event.number }}" --message "${{ github.event.pull_request.title }}"
env: { CLOUDFLARE_API_TOKEN: '${{ secrets.CF_WORKERS_TOKEN }}' }
# main job
- run: npx wrangler deploy
versions upload prints a preview URL for that version (https://<version-prefix>-docs-site.<account>.workers.dev), which the workflow comments on the pull request. For riskier changes, wrangler versions deploy can split traffic — 10% to the new version and 90% to the current one — and then move to 100% after checks pass, the approach described in Canary Releases for Static Sites. Rolling back is wrangler rollback to the previous version, which took under 10 seconds to propagate in testing.
Step 5: Adding One Server Route Later
Months after launch, the site needed a feedback endpoint that writes to a D1 database. With the @astrojs/cloudflare adapter, Astro can keep every page prerendered and add a single on-demand route:
// astro.config.mjs
import cloudflare from '@astrojs/cloudflare';
export default defineConfig({ output: 'static', adapter: cloudflare() });
// src/pages/api/feedback.ts
export const prerender = false;
export async function POST({ request, locals }) {
const { page, helpful } = await request.json();
await locals.runtime.env.DB.prepare('INSERT INTO feedback (page, helpful) VALUES (?, ?)').bind(page, helpful ? 1 : 0).run();
return new Response(null, { status: 204 });
}
The adapter generates a Worker script and configures run_worker_first so only /api/* invokes it; every page remains a static asset. The wrangler config gains a main entry and a [[d1_databases]] binding. Monthly Worker invocations after the change: about 38,000 — the feedback submissions — while the site served 2.4 million static page views.
Observability and Limits
Static asset requests do not run code, so they do not appear in Worker logs; traffic, cache and status analytics for them are in the zone's analytics and the Workers dashboard's asset metrics. Enable [observability] in the wrangler config once a Worker script exists, so server routes get logs and traces without extra setup. Two limits are worth knowing before a large site moves: individual asset files are limited to 25 MiB, which rules out large video files (host those elsewhere), and the number of files per version is capped in the tens of thousands on the free plan — comfortably above this site's 1,900 files, but relevant for sites that generate an image variant per width and format for thousands of photos. Checking the file count in CI against the plan's limit turned a potential deploy failure into an early warning; the build-output counting follows the same approach as Deploying Only Changed Files.
Keep the wrangler configuration in the repository next to the Astro config, and review changes to both together; most deployment surprises on this site came from one being changed without the other.
Measured Impact
Compared with the site's previous deployment on Cloudflare Pages:
| Measure | Cloudflare Pages | Workers static assets |
|---|---|---|
| Deploy time (upload 1,900 files, 740 changed) | 38 s | 29 s |
| TTFB p75 for HTML, field | 112 ms | 104 ms |
| Rollback time | redeploy of previous build, ~40 s | wrangler rollback, < 10 s |
| Gradual rollout | not available | percentage split per version |
| Worker invocations for static pages | 0 | 0 |
| Monthly cost | 0 | 0 (static) + feedback route within free tier |
Pitfalls & Rollback
- Mismatched trailing-slash settings. Astro's
trailingSlashandbuild.formatmust matchhtml_handling, or every internal link redirects. - Using
single-page-application404 handling for a static site. Missing pages return the home page with a 200, which search engines treat as soft 404s. - Placing
_headersoutsidepublic/. It must end up in the assets directory. - Forgetting
prerender = falseon server routes. Otherwise the route is built as a static file and POST requests fail. - Rollback:
wrangler rollbackrestores the previous version; moving back to Pages means pointing the custom domain at the Pages project again.
Conclusion
Workers static assets serve a static Astro site as simply as Pages does — one config file, _headers and _redirects in public/, no code for static requests — and add versioned previews, gradual rollouts, fast rollbacks and a path to server routes without changing platforms. Align Astro's URL settings with the asset server's, keep 404s honest, and add on-demand routes only where they are needed.
FAQ
Why deploy to Workers static assets instead of Cloudflare Pages?
Workers with static assets is Cloudflare's current platform for both static and dynamic sites, with the same free static asset serving as Pages plus full Worker features such as cron triggers, Durable Objects, more bindings and gradual deployments. New projects increasingly start there, and Pages projects can migrate.
Does a fully static Astro site need a Worker script?
No. With only an assets directory configured in wrangler, Cloudflare serves the files without running any code, and requests are not billed as Worker invocations. A script is only needed for server routes, middleware or custom logic.
How are trailing slashes and 404s handled?
The assets configuration has html_handling, which defaults to auto-trailing-slash and serves about/index.html at /about/, and not_found_handling, which can serve the nearest 404.html or act as a single-page app fallback. Set them to match Astro's trailingSlash and build.format settings.
How do preview deployments work?
wrangler versions upload creates a new version with a preview URL without routing production traffic to it. Promoting it with wrangler versions deploy, optionally as a gradual percentage rollout, makes it live. Git-connected Workers builds can create previews per branch automatically.
Related
- Parent: Cloudflare Pages Edge Caching Setup — caching on Cloudflare.
- Migrating from Cloudflare Pages to Workers Static Assets — moving an existing project.
- Building Astro Sites with GitHub Actions — the build side of the pipeline.
- Server Islands in Astro — on-demand fragments on static pages.
- Blue-Green Deploys for Static Sites — another way to switch versions safely.
Filed underHow-tosAstroCloudflare