p4ni.

Tutorial

How to Deploy an Astro Site to Cloudflare Workers (2026 Guide)

For years, “deploy Astro to Cloudflare” meant Cloudflare Pages. That advice is now stale: Cloudflare has shifted its focus to Workers with static assets, recommends Workers for new projects, and is no longer bringing new features to Pages. The good news is that deploying a static Astro site to Workers is arguably simpler than Pages ever was — one config file, one command, and your site is on Cloudflare’s edge.

This is the exact setup I use for this blog and for my commercial Astro themes. It covers:

  1. Why Workers (and what “static assets” means)
  2. The minimal wrangler.jsonc for a static Astro site
  3. Deploying with wrangler deploy
  4. Attaching a custom domain
  5. Gotchas: 404 pages, trailing slashes, and when you actually need the Cloudflare adapter

Why Workers instead of Pages?

A quick decision table before we start:

You are building… Use
A new static or SSR Astro site Workers (this guide)
An existing site already on Pages Pages still works; migrate when convenient
A site that needs D1, Durable Objects, cron, or queues Workers (Pages never got full parity)

“Static assets” is the Workers feature that made Pages redundant: a Worker can now ship a directory of files (your dist/) that Cloudflare serves directly from its CDN. For a fully static site you don’t write or pay for any Worker code at all — requests served from static assets are free and unmetered, even on the free plan.

Step 0: An Astro site

Any static Astro project works. If you’re starting fresh:

pnpm create astro@latest my-site -- --template minimal
cd my-site

Astro is static by default — no adapter needed. pnpm build outputs plain HTML/CSS/JS to dist/. That’s our deployable artifact.

Step 1: Install Wrangler

Wrangler is Cloudflare’s CLI. Keep it as a dev dependency so your deploys are reproducible:

pnpm add -D wrangler

Step 2: Write wrangler.jsonc

Create wrangler.jsonc in your project root. This is the entire configuration for a static site:

{
  "$schema": "node_modules/wrangler/config-schema.json",
  "name": "my-site",
  "compatibility_date": "2026-07-27",
  "assets": {
    "directory": "./dist",
    "not_found_handling": "404-page"
  }
}

Three things worth understanding:

  • name becomes your Worker’s name and its free preview URL: my-site.<your-subdomain>.workers.dev.
  • assets.directory points at Astro’s build output. There is no main field — with no Worker script, Cloudflare serves the directory directly.
  • not_found_handling: "404-page" serves your dist/404.html (Astro builds one from src/pages/404.astro) with a proper 404 status. If you were deploying an SPA instead, you’d use "single-page-application" here.

Step 3: Deploy

Authenticate once, then deploy:

pnpm exec wrangler login
pnpm build
pnpm exec wrangler deploy

Wrangler uploads dist/, and a few seconds later your site is live at https://my-site.<your-subdomain>.workers.dev. Add a script to package.json so a deploy is always a fresh build:

{
  "scripts": {
    "deploy": "astro build && wrangler deploy"
  }
}

From now on, shipping is pnpm deploy.

Step 4: Custom domain

If your domain’s DNS is already on Cloudflare (i.e. the zone lives in the same account), you don’t need to touch the dashboard. Declare the domain in wrangler.jsonc:

{
  // …everything from before…
  "routes": [
    {
      "pattern": "blog.example.com",
      "custom_domain": true
    }
  ]
}

On the next wrangler deploy, Cloudflare creates the DNS record and provisions the TLS certificate automatically. The site you’re reading was attached to astro.p4ni.com exactly this way — one line of config, one deploy.

Don’t forget to tell Astro its production URL so canonical URLs, sitemaps, and RSS links are absolute:

// astro.config.mjs
export default defineConfig({
  site: 'https://blog.example.com',
});

Gotchas worth knowing

Trailing slashes. By default, static assets use “auto” HTML handling: /about/ serves about/index.html and /about 308-redirects to /about/. That matches Astro’s default directory-style output, so things just work — but keep your internal links consistent (I set trailingSlash: 'always' in astro.config.mjs so dev and prod behave identically).

_headers and _redirects. The Pages-style _headers and _redirects files work with Workers static assets too. Drop them in public/ and Astro copies them into dist/. Handy for cache headers on fonts or redirecting old URLs after a migration.

Preview before deploying. wrangler dev serves the built site locally under the same asset-serving rules as production — useful for checking redirect/404 behavior that astro preview approximates slightly differently.

When you do need the adapter. Everything above assumes a fully prerendered site. The moment you need SSR — personalization, form handling, an API route hitting D1 — add @astrojs/cloudflare. Your static pages still ship as free static assets; only server-rendered routes invoke the Worker. That hybrid setup is how my directory theme Almanac works: static browsing pages, with D1-backed search, submissions, and an admin panel on the Worker side.

Recap

pnpm add -D wrangler                 # 1. CLI
# 2. wrangler.jsonc with assets.directory = ./dist
pnpm exec wrangler login             # 3. authenticate once
pnpm deploy                          # 4. astro build && wrangler deploy

Static Astro on Workers gives you free, unmetered hosting on Cloudflare’s edge with a config file short enough to memorize — and a clean upgrade path to D1 and SSR when your site outgrows “just static.”