
Upgrade Astro 6 on Cloudflare Without a Runtime Surprise
Upgrade Astro 6 on Cloudflare safely: replace removed runtime APIs, isolate Node-only prerender work, build each environment, and test in workerd.
Upgrade Astro 6 on Cloudflare with a runtime boundary in mind
Astro 6 changes the Cloudflare adapter's development and prerendering runtime. The adapter guide says that `astro dev` and `astro preview` now run in the Workers runtime, `workerd`, and that prerendered pages use workerd during development and build by default. Astro's Cloudflare adapter guide A project that worked in Astro 5 can therefore fail before deployment because a static route imports a Node-only package, a dependency still uses CommonJS, or old Cloudflare runtime access remains in the code.
The practical fix is to separate two questions. First, decide which code must run in a Worker and make it Worker-compatible. Second, if only prerendered routes need a Node-only build dependency, set `prerenderEnvironment: 'node'` for that specific build path. Do not treat the setting as a way to make server-rendered Worker code run on Node.
Start with the adapter and the old runtime API
Astro 6 requires `@astrojs/cloudflare` v13 or later. Its upgrade instructions also remove `Astro.locals.runtime`. For bindings, import `env` from `cloudflare:workers`; for request metadata use `Astro.request.cf`; for the execution context use `Astro.locals.cfContext`. Read the migration section before changing imports so each replacement matches the value your route actually needs.
// src/pages/api/status.ts
import { env } from 'cloudflare:workers'
export const prerender = false
export function GET() {
return Response.json({ hasApiUrl: Boolean(env.API_URL) })
}The code is illustrative. Generate types from the Wrangler configuration in the project that owns the binding, then let TypeScript tell you whether `API_URL` exists. Keep the `cloudflare:workers` import in server-side code. A browser bundle cannot read Worker secrets.
Find the code that runs while Astro prerenders
Astro documents that `prerenderEnvironment` affects prerendered pages only. On-demand rendered pages remain in workerd. If a static page needs `node:fs` or an npm package that cannot run in workerd, put the compatibility choice in the adapter configuration. The adapter guide gives this option specifically for Node-only prerender dependencies.
// astro.config.mjs
import { defineConfig } from 'astro/config'
import cloudflare from '@astrojs/cloudflare'
export default defineConfig({
adapter: cloudflare({
// Use only when prerendered routes need a Node-only dependency.
prerenderEnvironment: 'node',
}),
})Before adding that option, locate the offending import. A server-only route may need a Worker-compatible replacement instead. A content-processing package used only while generating static pages is the better case for `prerenderEnvironment: 'node'`. This distinction prevents a build fix from hiding a production runtime failure.
Handle CommonJS dependency failures deliberately
The new workerd environment does not support CommonJS syntax such as `require` or `module.exports`. Astro documents pre-compiling a dependency with a Vite plugin and `optimizeDeps.include` when the project controls that dependency choice. Use the documented pattern rather than adding unrelated aliases or polyfills until an error disappears.
// astro.config.mjs
function optimizeServerDependency() {
return {
name: 'optimize-server-dependency',
configEnvironment(environment) {
if (environment !== 'client') {
return { optimizeDeps: { include: ['the-package-that-fails'] } }
}
},
}
}Replace `the-package-that-fails` only after the error identifies a real dependency. The snippet is a shape from Astro's documentation, not a claim that every CommonJS package should be bundled this way. If the package has an ESM release or a Worker-compatible alternative, prefer the smaller change.
Build each Cloudflare environment separately
Astro 6 selects the Cloudflare environment during the build phase. The adapter documentation says a project that uses named Wrangler environments must build separately for each target, using `CLOUDFLARE_ENV` before `astro build`, then deploy with Wrangler. This is different from building once and choosing only `wrangler deploy --env ...`.
# Staging
CLOUDFLARE_ENV=staging astro build
wrangler deploy
# Production
CLOUDFLARE_ENV=production astro build
wrangler deployUse the same target in local verification and CI. Check that the staging and production bindings resolve to the intended resources before deploying. A correct Worker name does not prove a correct database, KV namespace, or secret binding.
Run a Worker-shaped test before deployment
Run `astro build`, then `astro preview` for the Cloudflare target. Astro says this preview uses workerd and closely mirrors the deployed Workers runtime. That makes it the useful place to exercise a dynamic route, a binding read, and a prerendered route together.
Also test the routes that import the package or binding you changed. Check a static page, an on-demand route, and a failure path such as a missing optional value. Do not test with a real secret in source control. Use a disposable non-production binding where the behavior writes data.
Know when to stop changing config
An Astro 6 Cloudflare issue reports a hybrid application where moving from `Astro.locals.runtime.env` to `cloudflare:workers` caused the build path and runtime path to disagree. The report is not proof that every upgrade has the same cause but it is a useful warning: capture the smallest route that fails before changing the adapter, image service, and Vite configuration together.
Stop and isolate the issue when a static route imports a Worker-only module, a server route imports a Node-only module, or development and `astro preview` produce different results. Record the Astro version, adapter version, Node version, Wrangler configuration, route mode, and full error. That is enough to decide whether the next step is a project fix, a dependency change, or an upstream report.
Upgrade checklist
1. Upgrade Astro and `@astrojs/cloudflare` together, then remove every use of `Astro.locals.runtime`.
2. Classify each affected route as prerendered or on-demand before changing its runtime assumptions.
3. Use `prerenderEnvironment: 'node'` only for Node-only work performed by prerendered routes.
4. Investigate CommonJS errors at the named dependency, using the documented optimizer hook only when it fits.
5. Build separately for staging and production when named Cloudflare environments are in use.
6. Run `astro build` and `astro preview`, then exercise one static route, one dynamic route, and the changed binding or dependency.
Astro 6 makes Cloudflare development more like production. The dependable upgrade is not to force every old Node assumption through workerd. It is to make the route boundary explicit, test the build environment that will deploy, and keep the smallest possible compatibility exception.