
How to deploy an Astro site to Cloudflare Pages?
You can still deploy a fully static Astro site to Cloudflare Pages: connect your repository in the Cloudflare dashboard, use npm run build as the build command and dist as the output directory. But for anything new, and for any site with on-demand pages, the answer has changed. The Astro docs note that Cloudflare recommends Cloudflare Workers for new projects, and the @astrojs/cloudflare adapter dropped Pages support entirely in v13, the version that came with Astro 6. The current way to deploy Astro to Cloudflare is to run npx astro add cloudflare, build with astro build, and publish with npx wrangler deploy, or connect your repository with Workers Builds so every push deploys automatically.
If you followed an older tutorial, this is confusing: many guides still say "Cloudflare Pages", and Cloudflare's own Pages guide for Astro hasn't caught up at the time of writing. In this article you'll learn what changed and why, how to deploy a static site and a server-rendered site to Workers, how to set up automatic deploys from Git, how to use environment variables, secrets and bindings, how to handle custom 404 pages and Node.js APIs, and how to move an existing Pages project across.
Pages vs Workers: What Changed
Cloudflare used to have two separate products for websites. Pages hosted static files with Git-based deploys and ran server code through Pages Functions. Workers ran code at the edge. Workers can now serve static assets directly, which gives one product that does both. Cloudflare's migration guide puts it this way: "Unlike Pages, Workers has a distinctly broader set of features available to it", and Cloudflare publishes a guide for moving Pages projects to Workers.
Astro followed. Here's how the current setup differs from older Astro and Pages guides:
| Older Astro + Pages setup | Astro 6/7 + Workers | |
|---|---|---|
| Adapter version | @astrojs/cloudflare v12 and earlier | v13 and later (14.4.2 at the time of writing) |
| Deploy target | Cloudflare Pages | Cloudflare Workers with static assets |
| Deploy command | wrangler pages deploy dist | wrangler deploy |
| Dev server runtime | Node.js | Cloudflare's workerd |
| Environment and bindings | Astro.locals.runtime.env | import { env } from 'cloudflare:workers' |
| Execution context | Astro.locals.runtime.ctx | Astro.locals.cfContext |
Wrangler main | dist/_worker.js/index.js | @astrojs/cloudflare/entrypoints/server |
The adapter's upgrade notes say it directly: "The Astro Cloudflare adapter no longer supports deployment on Cloudflare Pages. For the best experience and feature support, you should migrate to Cloudflare Workers."
So if your Astro site renders anything on demand, uses actions, sessions or server islands, or uses Cloudflare bindings such as KV or D1, deploy it to Workers. If it's purely static, either product works, but Workers is where Cloudflare is putting new features.
Option 1: Deploy a Static Astro Site to Workers
A fully prerendered site doesn't need the adapter. Install Wrangler, Cloudflare's CLI:
npm install wrangler@latest --save-dev
Create a Wrangler config at the project root that points at your build output. There's no main field because there's no Worker code, only assets:
// wrangler.jsonc
{
"name": "my-astro-site",
"compatibility_date": "2026-10-19",
"assets": {
"directory": "./dist",
"not_found_handling": "404-page"
}
}
Set compatibility_date to the day you first deploy. not_found_handling: "404-page" tells Cloudflare to serve your 404.html for unknown URLs. Workers doesn't infer this the way Pages did, so without it you won't see your custom 404 page.
Then build and deploy:
npx astro build
npx wrangler deploy
The first time, Wrangler asks you to log in. A dry run shows what will be uploaded without deploying anything:
npx wrangler deploy --dry-run
✨ Read 3 files from the assets directory .../dist
Total Upload: 0.31 KiB / gzip: 0.22 KiB
No bindings found.
--dry-run: exiting now.
After a real deploy, Wrangler prints a workers.dev URL for your site.
Option 2: Deploy a Server-Rendered Astro Site to Workers
For on-demand pages, add the adapter:
npx astro add cloudflare
In an Astro 7 project, this command does more than add one line of config. It installs @astrojs/cloudflare and wrangler, adds a generate-types script that runs wrangler types, adds worker-configuration.d.ts to your tsconfig.json, and creates a wrangler.jsonc:
// wrangler.jsonc
{
"$schema": "./node_modules/wrangler/config-schema.json",
"compatibility_date": "2026-10-19",
"compatibility_flags": ["global_fetch_strictly_public"],
"name": "astro-sandbox",
"main": "@astrojs/cloudflare/entrypoints/server",
"assets": {
"directory": "./dist",
"binding": "ASSETS"
},
"observability": {
"enabled": true
}
}
The name comes from your package.json. Change it to whatever you want the Worker to be called. Your Astro config now has the adapter:
// astro.config.mjs
import { defineConfig } from 'astro/config';
import cloudflare from '@astrojs/cloudflare';
export default defineConfig({
adapter: cloudflare(),
});
The Wrangler file is optional for simple projects. If you delete it, Astro generates a default configuration using your package name. You need it as soon as you add bindings, variables or other custom settings.
Build, Preview and Deploy
Build as usual:
npx astro build
The adapter splits the output into static files and a Worker, and generates a complete Wrangler config inside dist:
dist/client/index.html
dist/client/_headers
dist/client/.assetsignore
dist/server/entry.mjs
dist/server/wrangler.json
The build log shows what it configured for you:
[@astrojs/cloudflare] Enabling image processing with Cloudflare Images for production with the "IMAGES" Images binding.
[@astrojs/cloudflare] Enabling sessions with Cloudflare KV with the "SESSION" KV binding.
[@astrojs/cloudflare] Injected immutable Cache-Control for /_astro/* into _headers.
Preview the production build locally. astro preview runs it in workerd, the same runtime Cloudflare uses:
npx astro preview
Then deploy:
npx wrangler deploy
Wrangler finds the generated config automatically. A dry run shows the redirection and the bindings your Worker will get:
Using redirected Wrangler configuration.
- Configuration being used: "dist/server/wrangler.json"
- Deploy configuration file: ".wrangler/deploy/config.json"
...
Your Worker has access to the following bindings:
Binding Resource
env.SESSION KV Namespace
env.IMAGES Images
env.ASSETS Assets
The SESSION KV namespace and IMAGES binding are provisioned automatically when you deploy. If you don't use sessions, set session: false in your Astro config so no KV namespace is created. If you'd rather transform images only at build time, set the adapter's imageService option to 'compile'.
Deploying Automatically From Git With Workers Builds
To get the Pages-style "push to deploy" workflow on Workers, use Workers Builds:
- Set up the project as in Option 1 or 2 and push it to GitHub or GitLab.
- Open the Cloudflare dashboard and go to Compute > Workers & Pages, then select Create application.
- Under Import a repository, pick your Git account and repository.
- Set the commands: build command
npx astro build, deploy commandnpx wrangler deploy. - Click Save and Deploy. Your Worker is available at its
workers.devsubdomain, and you can add a custom domain afterwards.
Deploying a Static Site to Pages Anyway
If you have a static Astro site and prefer Pages, it still works. Pages only needs your built files, so no adapter is involved:
- In Workers & Pages, choose Create application, open the Pages tab and choose Import an existing Git repository.
- Select the repository, set the production branch, the build command
npm run buildand the build output directorydist. - Select Save and Deploy.
You can also upload a local build directly with Wrangler:
npx astro build
npx wrangler pages deploy dist
Be careful with Cloudflare's own "Deploy an Astro site" Pages guide: when we checked it, it still described SSR through Pages Functions and bindings through Astro.locals.runtime.env, both of which belong to the older adapter. Don't install the current adapter on a Pages project.
Environment Variables, Secrets and Bindings
On Workers, environment variables, secrets and bindings all live on the Worker's env object. Define non-sensitive variables in your Wrangler config:
// wrangler.jsonc
{
"vars": {
"GREETING": "Hello from wrangler.jsonc"
}
}
Set secrets with Wrangler, so they're encrypted and never stored in your repository:
npx wrangler secret put API_TOKEN
For local development and astro preview, put secrets in a .dev.vars file at the project root, and keep it out of Git:
# .dev.vars
API_TOKEN=local-dev-token
Read them by importing env from cloudflare:workers in any on-demand page, endpoint or middleware:
---
// src/pages/env.astro
export const prerender = false;
import { env } from 'cloudflare:workers';
const country = Astro.request.cf?.country ?? 'unknown';
---
<p>{env.GREETING}</p>
<p>Token set: {env.API_TOKEN ? 'yes' : 'no'}</p>
<p>Country: {country}</p>
Running this with astro preview returned:
<p>Hello from wrangler.jsonc</p><p>Token set: yes</p><p>Country: BD</p>
Astro.request.cf is Cloudflare's request metadata object, which includes geolocation. The same variables also work with Astro's typed astro:env API, which keeps your code portable if you ever change host.
To get TypeScript types for your variables and bindings, run wrangler types after every change to wrangler.jsonc or .dev.vars. It writes worker-configuration.d.ts, which astro add already included in your tsconfig.json:
npx wrangler types
The execution context, for example to call waitUntil() for work that should finish after the response is sent, is available as Astro.locals.cfContext.
Node.js APIs and Dependencies
Workers support many Node.js APIs through the nodejs_compat compatibility flag. Add it if your server code imports modules such as node:buffer or node:crypto:
// wrangler.jsonc
{
"compatibility_flags": ["nodejs_compat"]
}
Use the node: prefix in imports. If a build fails with a message saying a package "wasn't found on the file system but is built into node", a dependency needs a Node.js API that Workers doesn't provide, and you'll need to check with the package author or replace it.
By default, prerendered pages are also built inside workerd. If a prerendered page reads files with node:fs or uses a package that doesn't run in Workers, set prerenderEnvironment: 'node' in the adapter options. On-demand pages always run in workerd.
Migrating an Existing Pages Project to Workers
- Upgrade Astro and
@astrojs/cloudflareto current versions. - Update
wrangler.jsonc: replacepages_build_output_dirwith anassets.directory, and setmainto@astrojs/cloudflare/entrypoints/server(or remove the file if you have no custom settings). - Replace
Astro.locals.runtime: useimport { env } from 'cloudflare:workers'for variables and bindings,Astro.request.cffor request metadata, the globalcachesobject for the Cache API, andAstro.locals.cfContextfor the execution context. - Set
not_found_handlingif you rely on a custom 404 page. - Move variables, secrets and bindings from the Pages project to the Worker, and connect the repository with Workers Builds.
- Validate the Worker, move your custom domain across, then delete the Pages project.
If you deploy to several Cloudflare environments, note that the environment is now chosen at build time, so build separately for each one, for example CLOUDFLARE_ENV=staging npx astro build && npx wrangler deploy.
FAQ: Deploying Astro to Cloudflare
Yes for fully static sites, because Pages only needs the built dist folder. For on-demand rendering, actions, sessions or bindings, use Cloudflare Workers, since the current @astrojs/cloudflare adapter no longer supports Pages.
No. A static site only needs a Wrangler config whose assets.directory points to ./dist, then npx wrangler deploy. Add the adapter when you need on-demand rendering or Cloudflare bindings.
It was removed in @astrojs/cloudflare v13 for Astro 6. Import env from cloudflare:workers for variables and bindings, read request metadata from Astro.request.cf, and use Astro.locals.cfContext for the execution context.
Run astro build and then astro preview. The preview runs your Worker in workerd, Cloudflare's runtime, and loads local secrets from .dev.vars. astro dev also uses workerd with the current adapter.
Workers doesn't infer 404 handling the way Pages did. Set assets.not_found_handling to "404-page" in your Wrangler config so the 404.html file from your build is served for unknown URLs.
Cloudflare has a free Workers plan with daily request limits. Its docs say requests to static assets are free and unlimited, while requests that run your Worker, such as on-demand pages, are billed under Workers pricing. Check Cloudflare's current pricing for the limits that apply to your account.
Conclusion
Cloudflare Pages can still host a static Astro build, but the recommended path today is Cloudflare Workers. For a static site, that means a three-line Wrangler config and npx wrangler deploy. For a server-rendered site, npx astro add cloudflare sets up the adapter, Wrangler and types, the build generates the Worker config, and astro preview lets you test in the real workerd runtime before deploying.
If you're coming from an older Pages setup, the main changes are the deploy command, the Wrangler entry point, and how you reach variables and bindings: cloudflare:workers replaces Astro.locals.runtime. Move those across, set not_found_handling for your 404 page, connect Workers Builds for automatic deploys, and you'll be on the setup that both Astro and Cloudflare are actively developing.


