Deploying Astro 6 on Cloudflare Workers
Sri Annamalai
Shipping this site was supposed to be a weekend job. Astro 6 made me earn it. Here are the three potholes I hit, documented so the next person doesn’t.
1. Workers, not Pages
Astro 6’s Cloudflare adapter auto-provisions an ASSETS binding. Cloudflare Pages reserves that exact binding name. The two collide, and there is no workaround — the Vite plugin that ships with Astro 6 only supports Workers (see withastro/astro#16107).
Practical fix: use wrangler deploy, not wrangler pages deploy. Your wrangler.toml needs an [assets] section, never pages_build_output_dir.
2. Astro.locals.runtime.env is gone
Astro 6 removed the familiar Astro.locals.runtime.env access. Running any old code that touches it throws a runtime error pointing at the replacement:
import { env } from 'cloudflare:workers';
// in a .astro page or API route
const secret = env.RESEND_API_KEY;
Type it via an ambient interface Env in src/env.d.ts. No more App.Locals augmentation for env vars. During astro dev, .dev.vars is read as long as you have platformProxy: { enabled: true } in the adapter config.
3. Hybrid is dead — long live per-page prerender
output: 'hybrid' was removed. The new pattern:
// astro.config.mjs
export default defineConfig({ output: 'server' });
…and every static page explicitly opts in:
---
export const prerender = true;
---
No flag → server-rendered. Simple, but easy to miss on a migration.
Also worth knowing
compatibility_flags = ["nodejs_compat"]inwrangler.toml— required for Svelte SSR on Cloudflare.- Svelte 5 runes mode:
onMountcrashes withlifecycle_outside_component. Use$effectinstead. - Scoped
<style>blocks won’t match classes added viaclassList.add()— either use:global()or drive visibility with$statereactivity.
Nothing catastrophic. But the kind of papercuts you want to find on your own site, not the client’s.