Skip to content

Nuxt

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 @distilled.cloud/nuxt package to be installed in your project (alongside nuxt itself). 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.

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",
});

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,
},
});

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.

const site = yield* Cloudflare.Website.Nuxt("Website", {
nuxt: {
routeRules: {
"/about": { prerender: true },
},
},
});

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.

worker-entry.ts
// 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",
}),
},
});

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" },
});

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"],
},
});

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.

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;