Waku
Railway.Website.Waku deploys a Waku app (React
Server Components) to Railway. It builds the project programmatically:
the RSC server plus SSG pages run on one
Railway.Service. Alchemy creates a
Railway.Project if you omit
project. The URL is the generated *.up.railway.app hostname. No
waku.config.ts edits are required — if you have one it loads
natively — and there is no adapter to configure and no build command
to run.
Install
Section titled “Install”The build integration is not bundled with alchemy. Install
@alchemy.run/frontend-frameworks; the resource loads its /waku
and /waku/node exports from your project at deploy time. It is only
used at build time, so a dev dependency is enough:
bun add -d @alchemy.run/frontend-frameworksnpm install -D @alchemy.run/frontend-frameworkspnpm add -D @alchemy.run/frontend-frameworksyarn add -D @alchemy.run/frontend-frameworksConfigure Waku
Section titled “Configure Waku”No framework config is required — a fresh Waku project deploys
as-is. If your project has a waku.config.ts, Alchemy loads it
natively (exactly as Waku’s own CLI does) and uses it as the base
config — including Vite plugins under its vite field:
import tailwindcss from "@tailwindcss/vite";import { defineConfig } from "waku/config";
export default defineConfig({ basePath: "/", vite: { plugins: [tailwindcss()], },});Options set on the resource (srcDir, distDir, basePath) merge
over the file, per key. The one key Alchemy owns is
unstable_adapter — the Node deploy target sets it. Setting it
fails the build with an actionable error.
Declare the Website
Section titled “Declare the Website”Declare the site as a module-level const (rather than inline in the Stack):
import * as Railway from "alchemy/Railway";
export const Website = Railway.Website.Waku("Website");Pass project to put the Service in an existing Railway.Project.
rootDir defaults to ".".
Add it to the Stack
Section titled “Add it to the Stack”Yield the site from your Stack and return its URL:
import * as Alchemy from "alchemy";import * as Effect from "effect/Effect";
export default Alchemy.Stack( "MyWakuSite", { providers: Railway.providers(), state: Alchemy.localState(), }, Effect.gen(function* () { const site = yield* Website; return { url: site.url }; }),);Pages rendered as "static" are generated at build time and baked
into the image; dynamic pages and API routes run on the Service.
SSG pages are served at their extensionless URLs (/about).
The live url is https://{name}.up.railway.app. site.service
and site.project are the Railway resources underneath —
undefined during alchemy dev.
See the Waku API reference for every prop and attribute.
Add environment variables
Section titled “Add environment variables”Process environment is a top-level env map — plain strings, or
Redacted values:
export const Website = Railway.Website.Waku("Website", { env: { GREETING: "Hello from alchemy", API_BASE: "https://api.example.com", },});The values are copied onto process.env before the build and the
dev server, and onto the Service at deploy time, so server code
reads the same keys in both modes. Client inlining is whatever Vite
does (VITE_ prefixes).
Read the environment in server code
Section titled “Read the environment in server code”The RSC server is a plain Node process. Server components and API
routes read process.env (Waku’s getEnv reads the same values):
export default async function HomePage() { const greeting = process.env.GREETING ?? "hello"; return <h1>{greeting}</h1>;}
export const getConfig = async () => ({ render: "dynamic" }) as const;Local dev
Section titled “Local dev”bun alchemy devalchemy dev runs Waku’s own dev server (native HMR) instead of
deploying; site.url is the local address and no Railway resources
are created. Wrap the site in
Alchemy.remote() to deploy the live Service even during dev:
export const Website = Railway.Website.Waku("Website").pipe(Alchemy.remote());Custom domain
Section titled “Custom domain”const site = yield* Railway.Website.Waku("Web", { domain: "app.example.com",});domain attaches a Railway.CustomDomain
(targetPort 3000). url becomes https://app.example.com
instead of the generated *.up.railway.app.