Nuxt
Cloudflare.Website.Nuxt deploys a Nuxt app as
a Cloudflare Worker. It builds the app through your project’s own
@nuxt/kit with nitro’s cloudflare_module preset: the nitro server
bundle deploys as the Worker script, and client assets plus
prerendered pages deploy as Worker static assets. There is no
nitro.preset to edit, no Wrangler file, and no build command to
run.
Install
Section titled “Install”The build integration is not bundled with alchemy. Install
@alchemy.run/cloudflare-frameworks; the resource loads its /nuxt
export from your project at deploy time. It is only used at build time,
so a dev dependency is enough:
bun add -d @alchemy.run/cloudflare-frameworksnpm install -D @alchemy.run/cloudflare-frameworkspnpm add -D @alchemy.run/cloudflare-frameworksyarn add -D @alchemy.run/cloudflare-frameworksConfigure Nuxt
Section titled “Configure Nuxt”Your nuxt.config.ts loads natively — modules, layers, and all —
so configure Nuxt exactly as you would outside Alchemy:
export default defineNuxtConfig({ modules: ["@nuxtjs/tailwindcss"], routeRules: { "/about": { prerender: true }, },});Deploy-specific overrides are merged over it via the nuxt prop
(the override wins) — see Prerendering below for an
example.
Don’t set nitro.preset — the Cloudflare deploy target owns the
preset, and a foreign preset is a hard error.
Declare the Website
Section titled “Declare the Website”Declare the site as a module-level const (rather than inline in the Stack) and derive the typed shape of its bindings from it:
import * as Cloudflare from "alchemy/Cloudflare";
export const Website = Cloudflare.Website.Nuxt("Website");
export type WebsiteEnv = Cloudflare.InferEnv<typeof Website>;WebsiteEnv is the typed shape of the Worker’s bindings, derived
from the class — you’ll import it into your Nuxt code in
Read bindings in server code.
Add it to the Stack
Section titled “Add it to the Stack”Yield the class from your Stack and return its URL:
import * as Alchemy from "alchemy";import * as Effect from "effect/Effect";
export default Alchemy.Stack( "MyNuxtSite", { providers: Cloudflare.providers(), state: Cloudflare.state(), }, Effect.gen(function* () { const site = yield* Website; return { url: site.url }; }),);The server build uses nitro’s hybrid workerd Node compatibility, so
the nodejs_compat compatibility flag is added automatically — you
don’t need to pass it.
See examples/cloudflare-website-nuxt for the checked-in example.
Add bindings
Section titled “Add bindings”The resource returns a plain Worker, so env accepts the full
binding vocabulary — KV namespaces, R2 buckets, Durable Objects,
secrets:
import * as Config from "effect/Config";
export const Uploads = Cloudflare.R2.Bucket("Uploads");
export const Website = Cloudflare.Website.Nuxt("Website", { env: { UPLOADS: Uploads, API_KEY: Config.redacted("API_KEY"), },});Uploads is a description, not a deploy — Alchemy provisions the
real bucket because the Website binds it. Config.redacted reads
API_KEY from your environment at deploy time and binds it as a
Worker secret — see Secrets & env.
Read bindings in server code
Section titled “Read bindings in server code”Server routes and SSR read bindings through nitro’s Cloudflare
runtime contract, event.context.cloudflare.env. Type it once by
augmenting h3’s event context with the inferred env type:
import type { WebsiteEnv } from "../alchemy.run.ts";
declare module "h3" { interface H3EventContext { cloudflare: { env: WebsiteEnv; }; }}Every server route now gets fully typed bindings:
export default defineEventHandler(async (event) => { const uploads = event.context.cloudflare.env.UPLOADS; await uploads.put("hello.txt", "Hello!"); return { ok: true };});event.context.cloudflare.env.UPLOADS is typed as an R2 bucket and
.API_KEY as a string — renaming a binding in alchemy.run.ts is a
type error in your routes. event.context.cf and
event.context.cloudflare.context.waitUntil are available the same
way.
Prerendering
Section titled “Prerendering”Routes marked for prerendering in routeRules (or via
nitro.prerender) render at build time into .output/public and are
served as static assets, with no Worker invocation:
export const Website = Cloudflare.Website.Nuxt("Website", { nuxt: { routeRules: { "/about": { prerender: true }, }, },});Nitro’s isr route rule 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.
Custom Worker exports (Durable Objects)
Section titled “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 and adds
extra exports — Durable Object classes must live on the deployed
Worker for their namespace bindings to resolve:
import nitroHandler from "nitropack/presets/cloudflare/runtime/cloudflare-module";export class Counter extends DurableObject {}export default nitroHandler;export const Website = Cloudflare.Website.Nuxt("Website", { main: "worker-entry.ts", env: { COUNTER: Cloudflare.DurableObject("Counter", { className: "Counter", }), },});Every framework route keeps working through the re-exported handler.
Local dev
Section titled “Local dev”bun alchemy devalchemy dev runs Nuxt’s own dev server (nitro dev, full HMR) with
the Worker’s real bindings served on event.context.cloudflare —
wrangler-free, through the cloudflare-runtime platform proxy.
Resource bindings (KV, R2, D1) round-trip to a local workerd
instance, so dev state is live and shared; literal env values
overlay the proxied bindings. Your server code is identical in dev
and production.
One limitation: Durable Object classes declared via a custom main
entry only exist in the production build, so they are not servable in
dev yet.