1---
2title: Waku
3description: Deploy a Waku app to AWS with AWS.Website.Waku — RSC server on a streaming Lambda Function URL, SSG pages and assets on S3 + CloudFront, and Waku's own dev server under alchemy dev.
4sidebar:
5 order: 13
6---
7
8import { Code, Tabs, TabItem } from "@astrojs/starlight/components";
9
10export const devDeps = "@alchemy.run/frontend-frameworks";
11
12`AWS.Website.Waku` deploys a [Waku](https://waku.gg) app (React
13Server Components) to AWS. It builds the project programmatically:
14the RSC server runs on a Lambda Function URL with response streaming,
15and the client output — including SSG-prerendered pages — is served
16from a private S3 bucket through CloudFront. No `waku.config.ts`
17edits are required — if you have one it loads natively — and there is
18no adapter to configure and no build command to run.
19
20## Install
21
22The build integration is not bundled with alchemy. Install
23`@alchemy.run/frontend-frameworks`; the resource loads its `/waku`
24and `/waku/aws` exports from your project at deploy time. It is only
25used at build time, so a dev dependency is enough:
26
27<Tabs syncKey="pkgManager">
28 <TabItem label="bun" icon="bun">
29 <Code code={`bun add -d ${devDeps}`} lang="sh" />
30 </TabItem>
31 <TabItem label="npm" icon="npm">
32 <Code code={`npm install -D ${devDeps}`} lang="sh" />
33 </TabItem>
34 <TabItem label="pnpm" icon="pnpm">
35 <Code code={`pnpm add -D ${devDeps}`} lang="sh" />
36 </TabItem>
37 <TabItem label="yarn" icon="seti:yarn">
38 <Code code={`yarn add -D ${devDeps}`} lang="sh" />
39 </TabItem>
40</Tabs>
41
42## Configure Waku
43
44No framework config is required — a fresh Waku project deploys
45as-is. If your project has a `waku.config.ts`, Alchemy loads it
46natively (exactly as Waku's own CLI does) and uses it as the base
47config — including Vite plugins under its `vite` field:
48
49```typescript
50// waku.config.ts
51import { defineConfig } from "waku/config";
52import tsconfigPaths from "vite-tsconfig-paths";
53
54export default defineConfig({
55 basePath: "/",
56 vite: {
57 plugins: [tsconfigPaths()],
58 },
59});
60```
61
62`waku.config.*` is the home for static configuration. For deploy-time
63values — anything that varies by stage — the `waku` prop merges
64overrides over the file, per key:
65
66```typescript
67export const Website = AWS.Website.Waku("Website", {
68 waku: {
69 basePath: "/docs/",
70 },
71});
72```
73
74The one key Alchemy owns is `unstable_adapter` — setting it fails the
75build with an actionable error.
76
77:::note
78Vite configuration must live in `waku.config.ts`'s `vite` field —
79a standalone `vite.config.ts` is not loaded, same as Waku's own CLI.
80:::
81
82## Declare the Website
83
84Declare the site as a module-level const (rather than inline in the
85Stack):
86
87```typescript
88// alchemy.run.ts
89import * as AWS from "alchemy/AWS";
90
91export const Website = AWS.Website.Waku("Website");
92```
93
94## Add it to the Stack
95
96Yield the site from your Stack and return its URL:
97
98```typescript
99// alchemy.run.ts
100import * as Alchemy from "alchemy";
101import * as Effect from "effect/Effect";
102
103export default Alchemy.Stack(
104 "MyWakuSite",
105 {
106 providers: AWS.providers(),
107 state: AWS.state(),
108 },
109 Effect.gen(function* () {
110 const site = yield* Website;
111 return { url: site.url };
112 }),
113);
114```
115
116Pages rendered as `"static"` are generated at build time, uploaded
117to S3, and served from the edge; dynamic pages and API routes stream
118from the Lambda.
119
120See
121[examples/aws-website-waku](https://github.com/alchemy-run/alchemy/tree/main/examples/aws-website-waku)
122for the checked-in example.
123
124## Add environment variables
125
126The server function's environment is configured under `env` — plain
127values, or outputs from other resources in the Stack:
128
129```diff lang="typescript"
130// alchemy.run.ts
131export const Website = AWS.Website.Waku("Website", {
132+ env: {
133+ GREETING: "Hello from alchemy",
134+ API_BASE: api.url,
135+ },
136});
137```
138
139The values are set on the Lambda at deploy time and injected into the
140dev server's process environment under `alchemy dev`, so server code
141reads the same values in both modes.
142
143## Read the environment in server code
144
145Server components and API routes read the environment through waku's
146`getEnv` — on AWS it is backed by the Lambda's `process.env` at
147request time, and it keeps page modules portable across deploy
148targets:
149
150```typescript
151// src/pages/index.tsx
152import { getEnv } from "waku";
153
154export default async function HomePage() {
155 const greeting = getEnv("GREETING") ?? "hello";
156 return <h1>{greeting}</h1>;
157}
158
159export const getConfig = async () => ({ render: "dynamic" }) as const;
160```
161
162## Local dev
163
164```sh
165bun alchemy dev
166```
167
168`alchemy dev` runs Waku's own dev server (native HMR) instead of
169deploying; `site.url` is the local address and no AWS resources are
170created. Wrap the site in `Alchemy.remote()` to deploy the real
171infrastructure even during dev.
172
173## Custom domain
174
175```typescript
176const site = yield* AWS.Website.Waku("Web", {
177 domain: { name: "app.example.com" },
178});
179```
180
181The hosted zone is inferred from the hostname. Pass `hostedZoneId` to
182pin it when several zones could match.