1---
2title: Octane
3description: Deploy an OctaneJS app to AWS with AWS.Website.Octane — SSR on a streaming Lambda Function URL, assets on S3 + CloudFront, and Octane's own Vite dev server under alchemy dev.
4sidebar:
5 order: 14
6---
7
8import { Code, Tabs, TabItem } from "@astrojs/starlight/components";
9
10export const devDeps = "@alchemy.run/frontend-frameworks";
11
12`AWS.Website.Octane` deploys an [OctaneJS](https://octanejs.dev)
13fullstack app to AWS. Octane wraps Vite, so the resource is
14deliberately thin: it runs your project's own `vite build` — Octane's
15plugin builds the client bundle and the self-contained SSR server
16bundle, and the AWS deploy target's finishing pass wraps its fetch
17handler as a streaming Lambda handler. The server deploys on a Lambda
18Function URL; `dist/client` is served from a private S3 bucket
19through CloudFront. No CloudFormation, no build command to run.
20
21## Install
22
23The build integration is not bundled with alchemy. Install
24`@alchemy.run/frontend-frameworks`; the resource loads the package's
25`/octane` and `/octane/aws` exports from your project at deploy time.
26It is only used at build time, so a dev dependency is enough:
27
28<Tabs syncKey="pkgManager">
29 <TabItem label="bun" icon="bun">
30 <Code code={`bun add -d ${devDeps}`} lang="sh" />
31 </TabItem>
32 <TabItem label="npm" icon="npm">
33 <Code code={`npm install -D ${devDeps}`} lang="sh" />
34 </TabItem>
35 <TabItem label="pnpm" icon="pnpm">
36 <Code code={`pnpm add -D ${devDeps}`} lang="sh" />
37 </TabItem>
38 <TabItem label="yarn" icon="seti:yarn">
39 <Code code={`yarn add -D ${devDeps}`} lang="sh" />
40 </TabItem>
41</Tabs>
42
43## Configure Octane
44
45Keep native compiler and route settings in `octane.config.ts`, without
46an adapter:
47
48```typescript
49// octane.config.ts
50import { defineConfig, RenderRoute } from "@octanejs/vite-plugin";
51
52export default defineConfig({
53 router: {
54 routes: [new RenderRoute({ path: "/", entry: ["App", "/src/App.tsx"] })],
55 },
56});
57```
58
59`AWS.Website.Octane` selects hosting and automatically wraps Octane's
60default native Node output as a streaming Lambda handler. The legacy AWS
61marker adapter remains optional for existing projects.
62
63## Declare the Website
64
65Declare the site as a module-level const (rather than inline in the
66Stack):
67
68```typescript
69// alchemy.run.ts
70import * as AWS from "alchemy/AWS";
71
72export const Website = AWS.Website.Octane("Website");
73```
74
75## Add it to the Stack
76
77Yield the site from your Stack and return its URL:
78
79```typescript
80// alchemy.run.ts
81import * as Alchemy from "alchemy";
82import * as Effect from "effect/Effect";
83
84export default Alchemy.Stack(
85 "MyOctaneSite",
86 {
87 providers: AWS.providers(),
88 state: AWS.state(),
89 },
90 Effect.gen(function* () {
91 const site = yield* Website;
92 return { url: site.url };
93 }),
94);
95```
96
97## Add environment variables
98
99The server function's environment is configured under `env` — plain
100values, or outputs from other resources in the Stack:
101
102```diff lang="typescript"
103// alchemy.run.ts
104export const Website = AWS.Website.Octane("Website", {
105+ env: {
106+ GREETING: "Hello from alchemy",
107+ API_BASE: api.url,
108+ },
109});
110```
111
112The values are set on the Lambda at deploy time and injected into the
113dev server's process environment under `alchemy dev`, so server code
114reads the same values in both modes.
115
116## Read the environment in server code
117
118Octane's server runs in a plain Node Lambda, so middleware and
119`ServerRoute` handlers read the environment from `process.env`:
120
121```typescript
122// octane.config.ts
123new ServerRoute({
124 path: "/api/greeting",
125 methods: ["GET"],
126 handler: async () => {
127 return Response.json({ greeting: process.env.GREETING ?? "hello" });
128 },
129});
130```
131
132## Asset routing
133
134Octane's intended setup is asset-first with SSR on miss: exact files
135in `dist/client` serve from S3 via the CloudFront edge router without
136invoking the server, and every miss streams from Octane SSR on the
137Lambda. That is the default — there is nothing to configure.
138
139## Local dev
140
141```sh
142bun alchemy dev
143```
144
145`alchemy dev` runs Octane's own Vite dev server — the plugin's
146in-process SSR middleware serves rendering, server routes, and RPC
147with full HMR. `site.url` is the local address and no AWS resources
148are created. Wrap the site in `Alchemy.remote()` to deploy the real
149infrastructure even during dev.
150
151## Custom domain
152
153```typescript
154const site = yield* AWS.Website.Octane("Web", {
155 domain: { name: "app.example.com" },
156});
157```
158
159The hosted zone is inferred from the hostname. Pass `hostedZoneId` to
160pin it when several zones could match.