Cloudflare.Website reference
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:
bun add -d @alchemy.run/frontend-frameworksAstro: Deploying an Astro Site
Section titled “Astro: Deploying an Astro Site”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");Astro: Static Sites
Section titled “Astro: Static Sites”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", },});Astro: Bindings
Section titled “Astro: Bindings”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: Sessions
Section titled “Astro: Sessions”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,});Astro: Custom Rebuild Scope
Section titled “Astro: Custom Rebuild Scope”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"], },});Astro: Astro Configuration
Section titled “Astro: Astro Configuration”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" },});Astro: Class Form
Section titled “Astro: Class Form”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;Foldkit
Section titled “Foldkit”Source:
src/Cloudflare/Website/Foldkit.ts
A Cloudflare Worker deployed from a Foldkit app.
Foldkit apps are client-only Vite projects, so Foldkit drives the
project’s own vite build — the Foldkit Vite plugin in the app’s
vite.config.ts composes with the injected Cloudflare plugin — and
deploys the client output as static assets. No Wrangler configuration,
build command, or output directory required.
Input files are content-hashed (respecting .gitignore by default) so
unchanged projects skip the build and deploy entirely.
Foldkit apps route on the client, so assets.notFoundHandling
defaults to "single-page-application" — deep links serve
index.html and the Foldkit router takes over.
Foldkit: Deploying a Foldkit App
Section titled “Foldkit: Deploying a Foldkit App”A single call builds the project and deploys the client output as static assets — no configuration required.
Foldkit app
const site = yield* Cloudflare.Website.Foldkit("Website");Foldkit project in a subdirectory
const site = yield* Cloudflare.Website.Foldkit("Website", { rootDir: "applications/web",});Foldkit: Single-Page Application Routing
Section titled “Foldkit: Single-Page Application Routing”Unmatched paths serve index.html by default so deep links boot the
app and the Foldkit router resolves the route. A site that ships real
404 content overrides the default with notFoundHandling: "404-page".
const site = yield* Cloudflare.Website.Foldkit("Website", { assets: { notFoundHandling: "404-page", },});Foldkit: Custom Worker Entry
Section titled “Foldkit: Custom Worker Entry”By default the deployment is assets-only. When code must run at the
edge — API routes, error reporting, Durable Object classes — point
main at your own module that serves the client build through the
ASSETS binding (see FoldkitProps.main). Bindings passed in
env are reachable from the entry (and from cron handlers), not from
browser code — a Foldkit app runs on the client, so anything it needs
must come from a route the Worker serves.
const ticker = yield* Cloudflare.KV.Namespace("Ticker");
const site = yield* Cloudflare.Website.Foldkit("Platform", { main: "src/worker.ts", env: { TICKER: ticker, },});Foldkit: Custom Rebuild Scope
Section titled “Foldkit: Custom Rebuild Scope”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.Foldkit("Website", { memo: { include: ["src/**", "public/**", "package.json"], },});Foldkit: Class Form
Section titled “Foldkit: Class Form”Calling Foldkit 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.Foldkit<Website>()("Website") {}
const site = yield* Website;Nextjs
Section titled “Nextjs”Source:
src/Cloudflare/Website/Nextjs.ts
A Cloudflare Worker deployed from a Next.js project.
Nextjs builds the app with the wrangler-free OpenNext pipeline from
@alchemy.run/frontend-frameworks/nextjs:
next build runs through @opennextjs/cloudflare, the resulting worker
is bundled into a self-contained ES module set, and the static assets
(including prerendered pages and the read-only incremental cache) deploy
as Workers static assets. Input files are content-hashed so unchanged
projects skip the build and deploy entirely.
Both @alchemy.run/frontend-frameworks and its peer
@opennextjs/cloudflare must be installed in the deploying project. The
source provider is loaded from the package’s /nextjs export with a dynamic
import().
Local dev (alchemy dev) defaults to preview parity — the built worker
served under workerd. Set dev: { mode: "hmr" } for the real next dev
(Turbopack HMR) with the Worker’s bindings proxied onto
getCloudflareContext().
If open-next.config.ts exists in the project root, Alchemy loads it through
OpenNext’s native compiler without rewriting it. Otherwise, Alchemy generates
temporary defaults: a read-only static-assets cache, or KV adapters when
isr is set. Static-assets revalidation writes are a no-op.
Native Next.js and Tailwind configuration files are left unchanged.
Known limitations (upstream @opennextjs/cloudflare):
- Edge-runtime routes/pages (
export const runtime = "edge") are not supported — the build fails with the offending route list; remove the directive (the node runtime runs on Workers). Middleware is fine. next/imageoptimization requires a zone with Cloudflare Images; onworkers.dev, useunoptimized(images serve as raw assets).- Partial Prerendering /
"use cache"(cacheComponents) and Pages-Routeri18nconfig are untested/out of scope for now. App Router i18n via middleware works (middleware is fully supported).
Nextjs: Deploying a Next.js App
Section titled “Nextjs: Deploying a Next.js App”A single call builds the app and deploys the Worker plus static assets.
No open-next.config.ts, Wrangler config, or Alchemy plugin is required.
Basic Next.js site
const site = yield* Cloudflare.Website.Nextjs("Site");Explicit project root
const site = yield* Cloudflare.Website.Nextjs("Site", { rootDir: "./apps/web",});Nextjs: Optional Native OpenNext Configuration
Section titled “Nextjs: Optional Native OpenNext Configuration”The same Cloudflare.Website.Nextjs("Site") call works with or without a
config file. To customize OpenNext, add open-next.config.ts under rootDir
(the working directory by default). Imports and callbacks are preserved;
the native OpenNextConfig is passed to OpenNext’s compiler, subject to
Cloudflare’s adapter constraints rather than AWS runtime feature parity.
import { defineCloudflareConfig, type OpenNextConfig } from "@opennextjs/cloudflare";import staticAssetsIncrementalCache from "@opennextjs/cloudflare/overrides/incremental-cache/static-assets-incremental-cache";
export default { ...defineCloudflareConfig({ incrementalCache: staticAssetsIncrementalCache }), buildCommand: "pnpm exec next build",} satisfies OpenNextConfig;Nextjs: Bindings
Section titled “Nextjs: Bindings”Resources passed via env become Worker bindings, readable in route
handlers and server components through OpenNext’s
getCloudflareContext().
const bucket = yield* Cloudflare.R2.Bucket("Uploads");const site = yield* Cloudflare.Website.Nextjs("Site", { env: { UPLOADS: bucket, },});import { getCloudflareContext } from "@opennextjs/cloudflare";
export async function PUT(request: Request) { const { env } = getCloudflareContext(); await env.UPLOADS.put("key", await request.text()); return Response.json({ ok: true });}Nextjs: Writable ISR
Section titled “Nextjs: Writable ISR”Supply the cache namespaces on the resource. Alchemy binds them and adds
the Durable Object queue for background regeneration. Without a native
config, it also selects the matching adapters. With a native config, select
kv-incremental-cache, kv-next-tag-cache, and do-queue there; isr
does not override the file’s adapter choices.
const incCache = yield* Cloudflare.KV.Namespace("NextIncCache");const tagCache = yield* Cloudflare.KV.Namespace("NextTagCache");
const site = yield* Cloudflare.Website.Nextjs("Site", { isr: { incrementalCache: incCache, tagCache },});Nextjs: Custom Rebuild Scope
Section titled “Nextjs: Custom Rebuild Scope”By default, every project file outside build outputs is hashed to decide
whether a rebuild is needed. Use memo to narrow the scope when the
project has large directories that don’t affect the build output.
open-next.config.ts itself is always hashed. Include any imported config
helpers in a narrowed memo.include too (for example, config/**).
const site = yield* Cloudflare.Website.Nextjs("Site", { memo: { include: ["app/**", "public/**", "package.json", "next.config.mjs", "config/**"], },});Nextjs: Build Configuration
Section titled “Nextjs: Build Configuration”An explicit openNext.buildCommand overrides the native config’s command.
openNext.minify and openNext.debug remain resource-level build controls.
const site = yield* Cloudflare.Website.Nextjs("Site", { openNext: { buildCommand: "pnpm exec next build", minify: true },});Nextjs: Class Form
Section titled “Nextjs: Class Form”Calling Nextjs 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 Site extends Cloudflare.Website.Nextjs<Site>()("Site", { rootDir: "./apps/web",}) {}
const site = yield* Site;Source:
src/Cloudflare/Website/Nuxt.ts
A Cloudflare Worker deployed from a Nuxt project.
Nuxt builds the app programmatically through the project’s own
@nuxt/kit with nitro’s cloudflare_module preset — the project’s
nuxt.config.ts loads natively, no nitro.preset edits, no Wrangler
configuration, and no build command required. The nitro server bundle
deploys as the Worker script; client assets and prerendered pages
(.output/public) deploy as Worker static assets.
Requires the @alchemy.run/frontend-frameworks package to be installed in
your project; the integration is loaded from its /nuxt export. Input files
are content-hashed
(respecting .gitignore by default) so unchanged projects skip the
build and deploy entirely.
The server build uses nitro’s hybrid workerd Node compatibility
(cloudflare.nodeCompat), which relies on workerd’s native node:*
modules — the nodejs_compat compatibility flag is always included in
the Worker’s compatibility flags to match.
On local dev: alchemy dev runs Nuxt’s own dev server (nitro dev, SSR
in a Node worker thread, full HMR) with the Worker’s bindings served on
event.context.cloudflare through cloudflare-runtime’s platform proxy —
wrangler-free. Literal env values overlay the proxied bindings;
resource bindings (KV, R2, D1, …) round-trip to the proxy’s local
workerd instance, so dev state is live and shared. Durable Objects
declared via a custom main entry only exist in the production build
and are not servable in dev yet.
Nuxt: Deploying a Nuxt App
Section titled “Nuxt: Deploying a Nuxt App”A single call builds and deploys the app — server-rendered pages, API routes, prerendered pages, and client assets included.
Basic Nuxt site
const site = yield* Cloudflare.Website.Nuxt("Website");Nuxt project in a subdirectory
const site = yield* Cloudflare.Website.Nuxt("Website", { rootDir: "apps/web",});Nuxt: Bindings
Section titled “Nuxt: Bindings”Values passed via env are exposed to server routes and SSR through
nitro’s cloudflare_module runtime contract:
event.context.cloudflare.env (plus event.context.cf and
event.context.cloudflare.context.waitUntil).
Reading env from an API route
const site = yield* Cloudflare.Website.Nuxt("Website", { env: { API_KEY: Config.Redacted("API_KEY"), },});
// server/api/hello.ts// export default defineEventHandler((event) => ({// hasKey: event.context.cloudflare?.env?.API_KEY !== undefined,// }));Binding an R2 bucket
const bucket = yield* Cloudflare.R2.Bucket("Uploads");
const site = yield* Cloudflare.Website.Nuxt("Website", { env: { UPLOADS: bucket, },});Nuxt: Prerendering
Section titled “Nuxt: Prerendering”Routes marked for prerendering in routeRules (or via
nitro.prerender) render at build time into .output/public and are
served as static assets — no Worker invocation. Configure them in
your nuxt.config.ts, which loads natively:
export default defineNuxtConfig({ routeRules: { "/about": { prerender: true }, },});Nuxt: Config Overrides
Section titled “Nuxt: Config Overrides”nuxt.config.ts is the primary home for Nuxt configuration — it loads
natively. The nuxt prop layers deploy-time overrides on top (the
highest-priority c12 layer) for values the file can’t express, like
per-stage settings. The bag must be JSON-serializable — no functions,
plugins, or modules — and nitro.preset stays owned by the deploy
target.
const site = yield* Cloudflare.Website.Nuxt("Website", { nuxt: { app: { baseURL: "/docs/" }, runtimeConfig: { public: { apiBase: "https://api.example.com" }, }, },});Nuxt: Custom Worker Exports (Durable Objects)
Section titled “Nuxt: Custom Worker Exports (Durable Objects)”Nitro’s entry module is the Worker’s exports seam. Point main at your
own module that re-exports nitro’s runtime handler (imported from
nitropack/presets/cloudflare/runtime/cloudflare-module) and adds
extra exports — Durable Object classes must live on the deployed
Worker for their namespace bindings to resolve. Every framework route
keeps working through the re-exported handler.
// import nitroHandler from "nitropack/presets/cloudflare/runtime/cloudflare-module";// export class Counter extends DurableObject { ... }// export default nitroHandler;
const site = yield* Cloudflare.Website.Nuxt("Website", { main: "worker-entry.ts", env: { COUNTER: Cloudflare.DurableObject("Counter", { className: "Counter", }), },});Nuxt: Dev
Section titled “Nuxt: Dev”alchemy dev runs Nuxt’s own dev server (nitro dev, full HMR) with
event.context.cloudflare served wrangler-free through
cloudflare-runtime’s platform proxy: resource bindings resolve against
a local workerd instance, and literal env values overlay them.
// server/api/greeting.ts — identical code in dev and deployed// export default defineEventHandler((event) => ({// greeting: event.context.cloudflare?.env?.GREETING,// }));const site = yield* Cloudflare.Website.Nuxt("Website", { env: { GREETING: "hello" },});Nuxt: Custom Rebuild Scope
Section titled “Nuxt: Custom Rebuild Scope”By default, every non-gitignored file is hashed to decide whether a
rebuild is needed. Use memo to narrow the scope when the project
lives in a large repository.
const site = yield* Cloudflare.Website.Nuxt("Website", { memo: { include: ["app/**", "server/**", "public/**", "nuxt.config.ts", "package.json"], },});Nuxt: Limitations
Section titled “Nuxt: Limitations”Nitro’s isr route rule (incremental static regeneration) is
implemented only by the Vercel and Netlify presets — on Cloudflare it
is silently ignored at build time, and the route renders on demand in
the Worker like any other SSR route. Use prerender for build-time
static routes, or cache route rules for runtime caching.
Nuxt: Class Form
Section titled “Nuxt: Class Form”Calling Nuxt 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.Nuxt<Website>()( "Website",) {}
const site = yield* Website;Octane
Section titled “Octane”Source:
src/Cloudflare/Website/Octane.ts
A Cloudflare Worker deployed from an OctaneJS fullstack project.
Alchemy preserves Octane’s native compiler and client build, builds the
server for Workers, and generates the Worker entry. By default,
dist/server/worker.js deploys as the Worker script and dist/client
deploys as static assets; custom Octane build.outDir values are also
supported. No hosting adapter, Wrangler configuration, or separate build
command is required.
Requires @alchemy.run/frontend-frameworks alongside octane and
@octanejs/vite-plugin in your project. Keep the native octane() plugin,
Tailwind, and other Vite plugins in vite.config.ts, and application
routes and callbacks in octane.config.ts. These files load without
being rewritten. Existing adapter: cloudflare() declarations from
@octanejs/adapter-cloudflare remain supported but are optional; adapters
for other hosting targets are rejected.
Input files are content-hashed (respecting .gitignore by default) so
unchanged projects skip the build and deploy entirely.
Octane’s server runtime needs synchronous SHA-256 and
AsyncLocalStorage, so the nodejs_compat compatibility flag (enabled
by default for every Worker) is required.
A client-only Octane SPA (no octane.config.ts routes) is a plain Vite
project — deploy it with Cloudflare.Website.Vite instead,
where the octane() compiler plugin composes with the injected
Cloudflare Vite plugin.
Octane: Deploying an Octane App
Section titled “Octane: Deploying an Octane App”A single call builds and deploys the app — server-rendered routes,
server (API) routes, and client assets included. Define the app’s routes
in octane.config.ts; Cloudflare.Website.Octane selects the Worker build:
octane.config.ts
import { defineConfig, RenderRoute } from "@octanejs/vite-plugin";
export default defineConfig({ router: { routes: [new RenderRoute({ path: "/", entry: ["App", "/src/App.tsx"] })], },});alchemy.run.ts
const site = yield* Cloudflare.Website.Octane("Website");Octane project in a subdirectory
const site = yield* Cloudflare.Website.Octane("Website", { rootDir: "apps/web",});Octane: Bindings
Section titled “Octane: Bindings”Values passed via env reach Octane middleware and ServerRoute
handlers through context.platform. The generated Worker entry supplies
the Cloudflare { env, ctx } pair, so platform.env.MY_KV is the live
binding and platform.ctx.waitUntil schedules background work.
Reading a binding from a ServerRoute
// octane.config.ts route// new ServerRoute({// path: "/api/hello",// methods: ["GET"],// handler: (context) => {// const platform = context.platform as { env: { API_KEY: string } };// return Response.json({ hasKey: platform.env.API_KEY !== undefined });// },// })
const site = yield* Cloudflare.Website.Octane("Website", { env: { API_KEY: Config.Redacted("API_KEY"), },});Binding a KV namespace
const cache = yield* Cloudflare.KV.Namespace("Cache");
const site = yield* Cloudflare.Website.Octane("Website", { env: { CACHE: cache, },});Octane: Dev
Section titled “Octane: Dev”alchemy dev runs Octane’s own Vite dev server (the plugin’s in-process
SSR middleware — rendering, server routes, and RPC with full HMR).
NOTE: Octane’s dev middleware does not supply request-scoped platform
bindings (context.platform is undefined in dev — an upstream
limitation), so code touching platform.env must tolerate undefined
during dev; bindings are live in deployed Workers.
Octane: Custom Rebuild Scope
Section titled “Octane: Custom Rebuild Scope”By default, every non-gitignored file is hashed to decide whether a
rebuild is needed. Use memo to narrow the scope when the project
lives in a large repository.
const site = yield* Cloudflare.Website.Octane("Website", { memo: { include: ["src/**", "public/**", "octane.config.ts", "vite.config.ts", "package.json"], },});Octane: Class Form
Section titled “Octane: Class Form”Calling Octane 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.Octane<Website>()( "Website",) {}
const site = yield* Website;StaticSite
Section titled “StaticSite”Source:
src/Cloudflare/Website/StaticSite.ts
A Cloudflare Worker that serves static assets built by a shell command.
StaticSite runs a build command (e.g. npm run build), content-hashes
the output directory, and deploys the result as a Cloudflare Worker with
static assets. Use this when your site has its own build step that
produces a directory of files — Hugo, Zola, Eleventy, or any custom
pipeline.
For Vite-based projects, prefer Cloudflare.Website.Vite which handles
building automatically.
StaticSite: Basic Usage
Section titled “StaticSite: Basic Usage”Point command at your build script and outdir at where it writes
output. Alchemy runs the command, hashes the output, and deploys it as
an assets-only Worker — no Worker code is uploaded, and Cloudflare’s
asset layer serves every request itself.
Deploying a Hugo site
const site = yield* Cloudflare.Website.StaticSite("Blog", { command: "hugo --minify", outdir: "public",});Provide main to put your own Worker in front of the assets instead.
The Worker receives an ASSETS binding it can delegate to:
export default { fetch: (request: Request, env: { ASSETS: Fetcher }) => env.ASSETS.fetch(request),};Custom Worker in front of the assets
const site = yield* Cloudflare.Website.StaticSite("Blog", { command: "hugo --minify", outdir: "public", main: "./src/worker.ts",});StaticSite: Asset Configuration
Section titled “StaticSite: Asset Configuration”Use assets to control how Cloudflare handles routing for
your static files — HTML handling, not-found behavior, etc.
const site = yield* Cloudflare.Website.StaticSite("App", { command: "npm run build", outdir: "dist", main: "./src/worker.ts", assets: { htmlHandling: "auto-trailing-slash", notFoundHandling: "single-page-application", },});StaticSite: Building from a Subdirectory
Section titled “StaticSite: Building from a Subdirectory”Set cwd to run the build command in a subdirectory (e.g. a
monorepo package). outdir is resolved relative to cwd.
const site = yield* Cloudflare.Website.StaticSite("Web", { cwd: "apps/web", command: "npm run build", outdir: "dist", main: "apps/web/worker.ts",});StaticSite: Custom Rebuild Scope
Section titled “StaticSite: Custom Rebuild Scope”By default, all non-gitignored files are hashed to decide whether
the build should re-run. Use memo to narrow the scope.
Narrowing the memo scope
const site = yield* Cloudflare.Website.StaticSite("Docs", { command: "npm run build", outdir: "dist", main: "./src/worker.ts", memo: { include: ["content/**", "templates/**", "config.toml"], },});Rebuilding when a sibling workspace package changes
The default scope only hashes files under cwd (plus the nearest
lockfile), so edits to a sibling workspace package the app imports do
not retrigger the build on their own. Add the sibling’s sources with a
../ include glob — and keep lockfile: true, since providing
include otherwise drops the lockfile from the hash:
const site = yield* Cloudflare.Website.StaticSite("Web", { cwd: "apps/web", command: "npm run build", outdir: "dist", main: "./src/worker.ts", memo: { include: ["**\/*", "../../packages/env/src/**"], lockfile: true, },});StaticSite: Class Form
Section titled “StaticSite: Class Form”Calling StaticSite 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 Blog extends Cloudflare.Website.StaticSite<Blog>()("Blog", { command: "hugo --minify", outdir: "public", main: "./src/worker.ts",}) {}
const site = yield* Blog;SvelteKit
Section titled “SvelteKit”Source:
src/Cloudflare/Website/SvelteKit.ts
A Cloudflare Worker deployed from a SvelteKit project.
SvelteKit builds the app with SvelteKit’s own Vite pipeline and a
wrangler-free in-memory Cloudflare adapter, then re-bundles the
Node-flavored server output for workerd. A project-owned
vite.config.* loads natively (its sveltekit(...) options apply) —
no svelte.config.js (kit v3 dropped it), no
@sveltejs/adapter-cloudflare, no Wrangler configuration required.
Client assets and prerendered pages are deployed as Worker static
assets; dynamic routes are served by the generated Worker.
The @alchemy.run/frontend-frameworks package must be installed in your
project — its /sveltekit export is loaded dynamically at deploy time.
Input files are content-hashed (respecting .gitignore by default) so
unchanged projects skip the build and deploy entirely.
SvelteKit’s server code runs under nodejs_compat (the server graph is
built for Node), so the flag is always included in the Worker’s
compatibility flags.
Note on local dev: alchemy dev runs SvelteKit’s own Vite dev server
(Node SSR with full HMR). platform.env carries the Worker’s real
Cloudflare bindings (KV, R2, D1, …) served by the cloudflare-runtime
platform proxy, with literal env values (strings and secrets)
overlaid.
SvelteKit: Deploying a SvelteKit App
Section titled “SvelteKit: Deploying a SvelteKit App”A single call builds and deploys the app — server-rendered routes, prerendered pages, and client assets included.
const site = yield* Cloudflare.Website.SvelteKit("Website");SvelteKit: Bindings
Section titled “SvelteKit: Bindings”Values passed via env are exposed to server routes through
SvelteKit’s platform.env.
const site = yield* Cloudflare.Website.SvelteKit("Website", { env: { API_KEY: Config.Redacted("API_KEY"), },});
// src/routes/+page.server.ts// export const load = ({ platform }) => ({// hasKey: platform?.env?.API_KEY !== undefined,// });SvelteKit: Kit Options and 404 Handling
Section titled “SvelteKit: Kit Options and 404 Handling”Kit options live in the sveltekit(...) call in your
vite.config.ts, which loads natively. Fallback-page behavior is
driven by the platform-native assets.notFoundHandling knob — the
build generates the matching fallback page (rendering the app shell,
so kit’s own error page shows).
App-shell 404 fallback
const site = yield* Cloudflare.Website.SvelteKit("Website", { assets: { notFoundHandling: "404-page", },});The kit prop is a deploy-time override bag merged over your own
sveltekit(...) options (the prop wins) — useful for per-stage values
the config file can’t compute. JSON-serializable values only.
Deploy-time kit overrides
const site = yield* Cloudflare.Website.SvelteKit("Website", { kit: { paths: { base: "/docs" }, },});SvelteKit: Custom Rebuild Scope
Section titled “SvelteKit: Custom Rebuild Scope”By default, every non-gitignored file is hashed to decide whether a
rebuild is needed. Use memo to narrow the scope when the project
lives in a large repository.
const site = yield* Cloudflare.Website.SvelteKit("Website", { memo: { include: ["src/**", "static/**", "package.json"], },});SvelteKit: Class Form
Section titled “SvelteKit: Class Form”Calling SvelteKit 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.SvelteKit<Website>()( "Website",) {}
const site = yield* Website;Vinext
Section titled “Vinext”Source:
src/Cloudflare/Website/Vinext.ts
A Cloudflare Worker deployed from a vinext app.
vinext reimplements the Next.js API surface on Vite. Vinext is the
Alchemy-shaped deploy path: it runs the project’s own vite.config.ts
(which must register vinext()) through Alchemy’s wrangler-free
Cloudflare Vite plugin, then deploys the RSC Worker plus client
assets. There is no wrangler.jsonc and no
@vinext/cloudflare deploy.
This is not Cloudflare.Website.Nextjs. That resource runs
next build through OpenNext. vinext never consumes next build
output — mixing the two stacks will fail.
Install vinext, @vitejs/plugin-rsc,
react-server-dom-webpack, and @alchemy.run/frontend-frameworks
in the deploying project. Do not also register
@cloudflare/vite-plugin in vite.config.ts — Alchemy injects its
own Cloudflare plugin (vite-plugin-cloudflare:alchemy; vinext
matches the vite-plugin-cloudflare: prefix) and no-ops an official
plugin if one is still present. The Worker source is
@alchemy.run/frontend-frameworks/vinext/source (loaded with a
dynamic import(), like the other Website framework resources).
Bindings are declared on this resource (env) and read from
import { env } from "cloudflare:workers" in server components,
route handlers, and server actions.
The resource injects Alchemy’s KV data-cache adapter into the Vite build
and local server. No Alchemy plugin or cache configuration is needed in
vite.config.ts (the same ISR codec is used by Redis / S3).
Website.Vinext provisions VINEXT_KV_CACHE (do not bind it in
env) and alchemy deploy seeds prerender pairs into it. Workers
Cache is enabled (cache.enabled) and CF_VERSION_METADATA is
bound. There is no @vinext/cloudflare data-cache adapter.
Vinext: Deploying a vinext App
Section titled “Vinext: Deploying a vinext App”Basic vinext site
const site = yield* Cloudflare.Website.Vinext("Site");Bindings on the Worker
const site = yield* Cloudflare.Website.Vinext("Site", { env: { GREETING: "Hello from vinext on Cloudflare!", },});In a server component:
import { env } from "cloudflare:workers";
export default function Page() { return <h1>{env.GREETING}</h1>;}Custom Worker entry
const site = yield* Cloudflare.Website.Vinext("Site", { main: "worker/index.ts",});Vinext: Class Form
Section titled “Vinext: Class Form”class Site extends Cloudflare.Website.Vinext<Site>()("Site") {}
const site = yield* Site;Source:
src/Cloudflare/Website/Vite.ts
A Cloudflare Worker deployed from a Vite project.
Vite uses the Cloudflare Vite plugin to build both the server bundle
and client assets in a single vite build invocation — no manual
main entrypoint, build command, output directory, or Wrangler
configuration required.
Input files are content-hashed (respecting .gitignore by default) so
unchanged projects skip the build and deploy entirely.
Vite: Deploying a Static Site
Section titled “Vite: Deploying a Static Site”For a pure static site (no SSR), a single call is all you need. Vite builds the project and Alchemy deploys the output as a Cloudflare Worker with static assets.
const site = yield* Cloudflare.Website.Vite("Website");Vite: SSR Frameworks
Section titled “Vite: SSR Frameworks”SSR frameworks like TanStack Start or SolidStart work with a single
call — the nodejs_compat compatibility flag is enabled by default
so the server bundle can use Node.js APIs.
TanStack Start
const app = yield* Cloudflare.Website.Vite("TanStackStart");SolidStart with worker-first routing
const app = yield* Cloudflare.Website.Vite("SolidStart", { assets: { runWorkerFirst: true },});React Router
React Router’s server build (virtual:react-router/server-build) is a
build manifest with no default export, so it cannot be deployed as the
Worker entry directly. Point main at a module that wraps it with
createRequestHandler (React Router’s Cloudflare template ships this
as workers/app.ts):
const app = yield* Cloudflare.Website.Vite("ReactRouter", { main: "workers/app.ts",});Vite: React Server Components
Section titled “Vite: React Server Components”Frameworks that emit more than one server environment (e.g. React
Server Components, which split into an rsc environment and an ssr
environment) need viteEnvironments to declare which environment
produces the deployed Worker entry and which additional server
environments to bundle alongside it. The client environment is
always deployed as static assets.
const app = yield* Cloudflare.Website.Vite("ReactRouterRSC", { viteEnvironments: { entry: "rsc", children: ["ssr"], },});Vite: Custom Worker Entry
Section titled “Vite: Custom Worker Entry”By default the deployed Worker entry is the server bundle the
framework produces. When the Worker must export more than the
framework’s fetch handler — Durable Object classes, additional
handlers — point main at your own module that wraps the framework
handler and re-exports the extras. main takes precedence over any
entry configured in the Vite config.
const app = yield* Cloudflare.Website.Vite("App", { main: "worker/index.ts", viteEnvironments: { entry: "rsc", children: ["ssr"], },});Vite: Single-Page Applications
Section titled “Vite: Single-Page Applications”For SPAs (React, Vue, etc.), configure asset handling so unmatched
routes fall back to index.html and the client router takes over.
Vue SPA
const app = yield* Cloudflare.Website.Vite("Vue", { assets: { notFoundHandling: "single-page-application", },});Foldkit
Foldkit apps are client-only Vite projects, so a
single call deploys them — the Foldkit Vite plugin in the app’s own
vite.config.ts composes with the injected Cloudflare plugin. Enable
single-page-application not-found handling so deep links boot the app:
const app = yield* Cloudflare.Website.Vite("Foldkit", { assets: { notFoundHandling: "single-page-application", },});Cloudflare.Website.Foldkit is the same thing with that default already applied.
Octane SPA
A client-only OctaneJS app (no octane.config.ts
routes) is a plain Vite SPA — the octane() compiler plugin in the app’s
own vite.config.ts composes with the injected Cloudflare plugin:
const app = yield* Cloudflare.Website.Vite("Octane", { assets: { notFoundHandling: "single-page-application", },});Fullstack Octane apps (routes + SSR in octane.config.ts) run their own
two-pass build through Octane’s Cloudflare adapter — deploy those with
Cloudflare.Website.Octane instead.
Vite: Serving on a Zone Route with a Path Prefix
Section titled “Vite: Serving on a Zone Route with a Path Prefix”Cloudflare matches static assets against the full request pathname,
so a site attached to a route like example.com/docs* only serves
assets whose uploaded paths carry the /docs prefix. Set Vite’s
base in your vite.config.ts — the emitted HTML references its
assets under the prefix, and Alchemy keys the uploaded asset manifest
with the same resolved base so the two always agree.
vite.config.ts
import { defineConfig } from "vite";
export default defineConfig({ base: "/docs/",});alchemy.run.ts
const docs = yield* Cloudflare.Website.Vite("Docs", { routes: [{ pattern: "example.com/docs*", zoneName: "example.com" }],});Vite: Custom Rebuild Scope
Section titled “Vite: Custom Rebuild Scope”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.
Narrowing the memo scope
const site = yield* Cloudflare.Website.Vite("Docs", { memo: { include: ["src/**", "content/**", "package.json"], },});Rebuilding when a sibling workspace package changes
The default scope only hashes files under the project root (plus the
nearest lockfile), so edits to a sibling workspace package the app
imports do not retrigger the build on their own. Add the sibling’s
sources with a ../ include glob — and keep lockfile: true, since
providing include otherwise drops the lockfile from the hash:
const site = yield* Cloudflare.Website.Vite("Web", { rootDir: "apps/web", memo: { include: ["**\/*", "../../packages/env/src/**"], lockfile: true, },});Vite: Class Form
Section titled “Vite: Class Form”Calling Vite 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.Vite<Website>()("Website") {}
const site = yield* Website;Source:
src/Cloudflare/Website/Vocs.ts
A Cloudflare Worker deployed from a Vocs documentation project.
Vocs’ vocs.config.* loads natively. Alchemy runs Vocs’ Waku/RSC build,
deploys its server environments as a Worker, and publishes the client and
prerendered output as static assets. No Vite or Wrangler config is required.
Requires @alchemy.run/frontend-frameworks, vocs, and Vocs’ Waku peer
dependencies in the project.
Input files are content-hashed (respecting .gitignore by default), so an
unchanged project skips its build and deployment. Vocs’ server runtime uses
Node APIs, enabled by the Worker’s compatibility date
flags automatically.
Vocs: Deploying a Vocs Site
Section titled “Vocs: Deploying a Vocs Site”A single resource builds the documentation project and deploys its server runtime, prerendered pages, generated files, and public assets.
const docs = yield* Cloudflare.Website.Vocs("Docs", { rootDir: "./docs",});Vocs: Bindings
Section titled “Vocs: Bindings”Pass Cloudflare resources through env like any other Worker. Server-side
Vocs and MDX code can access them from cloudflare:workers.
const searchCache = yield* Cloudflare.KV.Namespace("SearchCache");
const docs = yield* Cloudflare.Website.Vocs("Docs", { rootDir: "./docs", env: { SEARCH_CACHE: searchCache, },});Vocs: Custom Build Output
Section titled “Vocs: Custom Build Output”Vocs configuration continues to own the output directory. When
vocs.config.* changes outDir, mirror that value on the resource so the
generated directory is excluded from the rebuild hash and read correctly.
// vocs.config.ts: defineConfig({ outDir: "build" })const docs = yield* Cloudflare.Website.Vocs("Docs", { rootDir: "./docs", outDir: "build",});Vocs: Custom Rebuild Scope
Section titled “Vocs: Custom Rebuild Scope”Use memo to narrow the files that trigger a rebuild in large projects.
const docs = yield* Cloudflare.Website.Vocs("Docs", { rootDir: "./docs", memo: { include: ["src/**", "public/**", "vocs.config.ts", "package.json"], },});Vocs: Class Form
Section titled “Vocs: Class Form”Calling Vocs without arguments returns a constructor for declaring the
deployed Worker as a named class.
class Docs extends Cloudflare.Website.Vocs<Docs>()("Docs", { rootDir: "./docs",}) {}
const docs = yield* Docs;Source:
src/Cloudflare/Website/Waku.ts
A Cloudflare Worker deployed from a Waku project.
Waku builds the project programmatically — no waku.config.ts edits,
no Wrangler configuration, and no build command required. A project’s
waku.config.* loads natively (same as waku’s CLI) and is where all
Waku configuration (srcDir, distDir, basePath, …) lives; the
waku prop overrides it per key at deploy time.
unstable_adapter is owned by Alchemy and must not be set. Note that a
standalone vite.config.ts is NOT loaded (same as waku’s CLI) — Vite
config belongs in waku.config.*’s vite field. The RSC server
bundle deploys as the Worker script and the client output (including
SSG-prerendered pages) deploys as static assets.
Requires the @alchemy.run/frontend-frameworks package to be installed in
your project; the integration is loaded from its /waku export. Input files
are content-hashed
(respecting .gitignore by default) so unchanged projects skip the
build and deploy entirely.
Waku’s server runtime uses AsyncLocalStorage, so the nodejs_als
compatibility flag is enabled automatically when your compatibility
flags include neither nodejs_als nor nodejs_compat. SSG pages are
served at their extensionless URLs (/about) via the default
drop-trailing-slash asset handling.
Waku: Deploying a Waku Site
Section titled “Waku: Deploying a Waku Site”A single call builds the project and deploys the RSC server bundle plus the client assets — no configuration required.
Waku site
const site = yield* Cloudflare.Website.Waku("Site");Waku project in a subdirectory
const site = yield* Cloudflare.Website.Waku("Site", { rootDir: "apps/web",});Waku: Waku Config Overrides
Section titled “Waku: Waku Config Overrides”Waku configuration lives in your waku.config.*. The waku prop
overrides it per key at deploy time — for values that vary by stage
or come from other resources.
const site = yield* Cloudflare.Website.Waku("Site", { waku: { basePath: "/docs/", },});Waku: Bindings
Section titled “Waku: Bindings”Pass resources through env like any other Worker. Server components
and API routes read them from the cloudflare:workers env at request
time. Prefer a guarded dynamic import in page modules — Waku’s SSG step
renders static pages in Node, where a top-level
import { env } from "cloudflare:workers" cannot resolve.
const bucket = yield* Cloudflare.R2.Bucket("Uploads");
const site = yield* Cloudflare.Website.Waku("Site", { env: { UPLOADS: bucket, },});Waku: Custom Worker Entry
Section titled “Waku: Custom Worker Entry”By default the deployed Worker entry is Waku’s own RSC server entry.
When the Worker must export more than Waku’s fetch handler — Durable
Object classes, additional handlers — point main at your own module
that wraps Waku’s handler (imported from virtual:waku/server-entry)
and re-exports the extras.
// import wakuHandler from "virtual:waku/server-entry";// export class Counter extends DurableObject { ... }// export default { fetch: (req, env, ctx) => wakuHandler.fetch(req, env, ctx) };
const site = yield* Cloudflare.Website.Waku("Site", { main: "src/worker-entry.ts", env: { COUNTER: Cloudflare.DurableObject("Counter", { className: "Counter", }), },});Waku: Custom Rebuild Scope
Section titled “Waku: Custom Rebuild Scope”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.Waku("Site", { memo: { include: ["src/**", "public/**", "package.json"], },});Waku: Class Form
Section titled “Waku: Class Form”Calling Waku 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 Site extends Cloudflare.Website.Waku<Site>()("Site") {}
const site = yield* Site;