Skip to content

Astro

Source: src/Cloudflare/Website/Astro.ts

A Cloudflare Worker deployed from an Astro project.

Astro runs Astro’s programmatic build with a wrangler-free Cloudflare adapter (@alchemy.run/frontend-frameworks/astro): server-rendered pages execute in the Worker, prerendered pages and client assets deploy as static assets. Your astro.config.* loads natively — no adapter setup or Wrangler configuration required.

Input files are content-hashed (respecting .gitignore by default) so unchanged projects skip the build and deploy entirely.

The @alchemy.run/frontend-frameworks package must be installed in your project; its /astro export is loaded dynamically at deploy time:

Terminal window
bun add -d @alchemy.run/frontend-frameworks

A single call builds the project and deploys the server bundle plus static assets. Pages are server-rendered by default; pages that export const prerender = true are served as static assets. Astro’s server runtime is built against Node APIs, so nodejs_compat is always included in the Worker’s compatibility flags.

const site = yield* Cloudflare.Website.Astro("Website");

With astro: { output: "static" } every page is prerendered at build time and the deploy is assets-only: no server bundle is uploaded — Cloudflare’s asset layer answers every request (serve the built 404.html via assets: { notFoundHandling: "404-page" }). Session provisioning is skipped for declared-static sites since no Worker code runs at request time.

const site = yield* Cloudflare.Website.Astro("Docs", {
astro: { output: "static" },
assets: {
notFoundHandling: "404-page",
},
});

Bind resources through env like any other Worker. Astro code reads them via import { env } from "cloudflare:workers" (or Astro.locals.runtime.env).

const kv = yield* Cloudflare.KV.Namespace("Cache");
const bucket = yield* Cloudflare.R2.Bucket("Uploads");
const site = yield* Cloudflare.Website.Astro("Website", {
env: {
CACHE: kv,
UPLOADS: bucket,
},
});

Astro’s session API is backed by a KV namespace. One is provisioned and bound under the session binding name (SESSION by default) automatically, so Astro.session works with zero configuration. Bind your own namespace under that name to use it instead, or set sessionKVBindingName: false to opt out of session provisioning.

Bringing your own session namespace

const sessions = yield* Cloudflare.KV.Namespace("Sessions");
const site = yield* Cloudflare.Website.Astro("Website", {
env: {
SESSION: sessions,
},
});

Opting out of session provisioning

const site = yield* Cloudflare.Website.Astro("Website", {
sessionKVBindingName: false,
});

By default, every non-gitignored file is hashed to decide whether a rebuild is needed. Use memo to narrow the scope when your project has large directories that don’t affect the build output.

const site = yield* Cloudflare.Website.Astro("Docs", {
memo: {
include: ["src/**", "public/**", "package.json"],
},
});

Your astro.config.* is the home for Astro configuration (integrations, Vite plugins, site, base, …) and loads natively. The Cloudflare adapter is injected for you — declaring an adapter in the config file fails the build. The astro prop is a deploy-time override bag merged OVER the file (values here win) for settings that vary per stage or derive from other resources’ Outputs, which a config file cannot consume. output defaults to "server" — astro’s zero-config "static" default would prerender every page inside workerd, where the Worker’s bindings don’t exist. Use config to point at an alternate config file (relative to rootDir).

const site = yield* Cloudflare.Website.Astro("Blog", {
astro: { site: "https://blog.example.com" },
});

Calling Astro with no arguments returns a constructor you can extend to declare the Worker as a named class. The class is both an Effect you can yield* to deploy and a type you can reference elsewhere — useful when other resources need to bind to this Worker.

class Website extends Cloudflare.Website.Astro<Website>()("Website") {}
const site = yield* Website;