Octane
Source:
src/Cloudflare/Website/Octane.ts
A Cloudflare Worker deployed from an OctaneJS fullstack project.
Octane wraps Vite, so Octane is deliberately thin: it drives the
project’s own vite build — @octanejs/vite-plugin (from the app’s
vite.config.ts) builds the client bundle and the SSR server bundle, and
the app’s adapter: cloudflare() (from @octanejs/adapter-cloudflare,
selected in octane.config.ts) emits the module Worker entry at
dist/server/worker.js. That entry deploys as the Worker script and
dist/client deploys as static assets — no Wrangler configuration and
no build command required.
Requires the @distilled.cloud/octane package to be installed in your
project (alongside octane, @octanejs/vite-plugin, and
@octanejs/adapter-cloudflare). 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.
Deploying an Octane App
Section titled “Deploying an Octane App”A single call builds and deploys the app — server-rendered routes,
server (API) routes, and client assets included. The app’s own
octane.config.ts must select the Cloudflare adapter:
octane.config.ts
import { cloudflare } from "@octanejs/adapter-cloudflare";import { defineConfig, RenderRoute } from "@octanejs/vite-plugin";
export default defineConfig({ adapter: cloudflare(), 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",});Bindings
Section titled “Bindings”Values passed via env reach Octane middleware and ServerRoute
handlers through the adapter’s runtime contract: context.platform is
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, },});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.
Custom Rebuild Scope
Section titled “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"], },});Class Form
Section titled “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;