Skip to content

Railway.Website reference

Source: src/Railway/Website/Astro.ts

Deploy an Astro application to Railway: the Node SSR bundle (assets first, then the Astro fetch handler) on one Railway.Service. Pages render on demand by default (output: "server"); pages that export const prerender = true are prerendered at build time and served as static files. With astro: { output: "static" } every page is prerendered and the deploy is assets-only.

The build runs through @alchemy.run/frontend-frameworks/astro with the @alchemy.run/frontend-frameworks/astro/node deploy target (a Node adapter is injected — your astro.config.* must not declare one).

During alchemy dev the site is Astro’s own dev server and no cloud resources are created. Alchemy.remote() opts back into the live Service path.

const site = yield* Railway.Website.Astro("Web", {
rootDir: "./app",
});
const site = yield* Railway.Website.Astro("Docs", {
rootDir: "./docs",
astro: { output: "static" },
errorPage: "404.html",
});
const site = yield* Railway.Website.Astro("Web", {
rootDir: "./app",
env: {
API_BASE: "https://api.example.com",
},
});

Source: src/Railway/Website/Cdn.ts

Enable Railway’s built-in CDN on a Website Service. Static assets (by Content-Type) are cached at the edge; HTML follows CdnProps.htmlCaching.

Source: src/Railway/Website/Foldkit.ts

Deploy a Foldkit app to Railway: a Vite SPA with unmatched paths falling back to index.html so deep links boot the Foldkit router. Same Node static-file Service as Vite.

Foldkit apps are client-only Vite projects — the Foldkit Vite plugin in the app’s vite.config.ts composes with the project’s own Vite build.

During alchemy dev the site is Vite’s own dev server and no cloud resources are created. Alchemy.remote() opts back into the live Service path.

Foldkit app

const site = yield* Railway.Website.Foldkit("Website");

Foldkit project in a subdirectory

const site = yield* Railway.Website.Foldkit("Website", {
rootDir: "applications/web",
});
const site = yield* Railway.Website.Foldkit("Website", {
assets: { notFoundHandling: "404-page" },
});

Source: src/Railway/Website/Nextjs.ts

Deploy a Next.js application to Railway as a long-running Node process: next build, then a serve entry that import("next") + next({ dev: false }).prepare() + getRequestHandler() on PORT (default 3000). Not OpenNext — those wrappers are Lambda/workerd.

The image npm installs next, react, and react-dom, and bakes .next plus public/ into /app.

During alchemy dev the site is Next’s own dev server (next dev) and no cloud resources are declared; Alchemy.remote() opts back into the full live deployment.

const site = yield* Railway.Website.Nextjs("Web", {
rootDir: "./app",
});
const site = yield* Railway.Website.Nextjs("Web", {
rootDir: "./app",
env: {
API_BASE: "https://api.example.com",
},
});

Source: src/Railway/Website/Nuxt.ts

Deploy a Nuxt application to Railway: the nitro Node server plus prerendered assets on one Railway.Service. The Node deploy target enforces nitro’s node preset; do not set nitro.preset.

During alchemy dev the site is Nuxt’s own dev server and no cloud resources are created. Alchemy.remote() opts back into the live Service path.

const site = yield* Railway.Website.Nuxt("Web", {
rootDir: "./app",
});
const site = yield* Railway.Website.Nuxt("Web", {
rootDir: "./app",
env: {
NUXT_PUBLIC_API_BASE: "https://api.example.com",
},
});

Source: src/Railway/Website/Octane.ts

Deploy an OctaneJS application to Railway: Octane’s SSR server plus client assets on one Railway.Service.

Railway.Website.Octane selects hosting and automatically wraps Octane’s default native Node output. Keep compiler and route settings in octane.config.ts without an adapter. The legacy Node marker adapter remains optional for existing projects.

During alchemy dev the site is Octane/Vite’s own dev server and no cloud resources are created. Alchemy.remote() opts back into the live Service path.

const site = yield* Railway.Website.Octane("Web", {
rootDir: "./app",
});
const site = yield* Railway.Website.Octane("Web", {
rootDir: "./app",
env: {
API_BASE: "https://api.example.com",
},
});

Source: src/Railway/Website/ReactRouter.ts

Deploy a React Router v7 app (framework mode) to Railway: the SSR server plus client assets on one Railway.Service.

The build runs through @alchemy.run/frontend-frameworks/react-router with the @alchemy.run/frontend-frameworks/react-router/node deploy target — both must be installed in your project, alongside @react-router/dev, react-router, and vite.

Your vite.config.ts needs no adapter wiring. React Router’s server build is a ServerBuild manifest rather than a request handler, so the integration wraps the manifest with createRequestHandler and packages the resulting fetch handler as a Node HTTP server on port 3000.

React Server Components (React Router’s unstable RSC plugin) and multi-environment builds are not supported yet — the build fails with an actionable error when more than one server entry is emitted.

During alchemy dev the site is React Router’s own Vite dev server and no cloud resources are created. Alchemy.remote() opts back into the live Service path.

Basic React Router App

const site = yield* Railway.Website.ReactRouter("Web", {
rootDir: "./app",
});

Custom Domain

const site = yield* Railway.Website.ReactRouter("Web", {
rootDir: "./app",
domain: "app.example.com",
});

Process Environment

const site = yield* Railway.Website.ReactRouter("Web", {
rootDir: "./app",
env: {
GREETING: "Hello from React Router on Railway!",
},
});

Read An Environment Variable From A Loader

app/routes/home.tsx
export function loader() {
return { apiBase: process.env.API_BASE ?? "unset" };
}

Source: src/Railway/Website/SolidStart.ts

Deploy a SolidStart application to Railway: the SSR server plus client assets (prerendered pages included) on one Railway.Service.

The build runs through @alchemy.run/frontend-frameworks/solidstart with the @alchemy.run/frontend-frameworks/solidstart/node deploy target — both must be installed in your project, alongside @solidjs/start and @solidjs/vite-plugin-nitro-2.

Your vite.config.ts needs no adapter wiring: the integration drives the project’s own vite build and appends its own nitro plugin instance carrying nitro’s node preset.

During alchemy dev the site is SolidStart’s own Vite dev server and no cloud resources are created. Alchemy.remote() opts back into the live Service path.

Basic SolidStart App

const site = yield* Railway.Website.SolidStart("Web", {
rootDir: "./app",
});

Custom Domain

const site = yield* Railway.Website.SolidStart("Web", {
rootDir: "./app",
domain: "app.example.com",
});
const site = yield* Railway.Website.SolidStart("Web", {
rootDir: "./app",
env: {
GREETING: "Hello from SolidStart on Railway!",
},
});

The integration owns the nitro plugin instance (a nitroV2Plugin() in your vite.config.* is rejected), so nitro options — prerendering included — go on the nitro prop. Prerendered pages are baked into the Service image and served as static files.

const site = yield* Railway.Website.SolidStart("Web", {
rootDir: "./app",
nitro: { prerender: { routes: ["/", "/about"] } },
});

Source: src/Railway/Website/StaticSite.ts

A Railway Service that serves static assets built by a shell command.

StaticSite runs a build command (e.g. npm run build), content-hashes the output directory, and deploys the result as a Node static-file server on one Railway.Service. Use this when your site has its own build step that produces a directory of files — Hugo, Zola, Eleventy, or any custom pipeline.

For Vite-based projects, prefer Railway.Website.Vite which handles building automatically.

Cloudflare DX: top-level command + outdir (Command.BuildProps). Only Command.Build("Build") / Command.Dev("Dev") are pushed under the site id; the Service stays in the caller namespace.

During alchemy dev, dev.command skips the build and is the site. Without dev.command, the site still builds and a local static server serves outdir — no Project or Service is created.

Deploying a Hugo site

const site = yield* Railway.Website.StaticSite("Blog", {
command: "hugo --minify",
outdir: "public",
});

SPA-style routing

const site = yield* Railway.Website.StaticSite("App", {
command: "npm run build",
outdir: "dist",
spa: true,
});
const site = yield* Railway.Website.StaticSite("Web", {
cwd: "apps/web",
command: "npm run build",
outdir: "dist",
});
const site = yield* Railway.Website.StaticSite("App", {
command: "npm run build",
outdir: "dist",
dev: { command: "npm run dev" },
});

Source: src/Railway/Website/SvelteKit.ts

Deploy a SvelteKit application to Railway: kit’s SSR server plus prerendered assets on one Railway.Service. The Node deploy target injects an in-memory kit adapter; do not set adapter in kit.

During alchemy dev the site is SvelteKit’s own dev server and no cloud resources are created. Alchemy.remote() opts back into the live Service path.

const site = yield* Railway.Website.SvelteKit("Web", {
rootDir: "./app",
});
const site = yield* Railway.Website.SvelteKit("Web", {
rootDir: "./app",
env: {
API_BASE: "https://api.example.com",
},
});

Source: src/Railway/Website/TanStackStart.ts

Deploy a TanStack Start application to Railway: the SSR server plus client assets on one Railway.Service.

The build runs through @alchemy.run/frontend-frameworks/tanstack-start with the @alchemy.run/frontend-frameworks/tanstack-start/node deploy target — both must be installed in your project, alongside @tanstack/react-start (or @tanstack/solid-start) and vite.

Your vite.config.ts needs no adapter wiring: TanStack Start is pure Vite, so the integration drives the project’s own vite build, forces the SSR bundle to be self-contained, and wraps its fetch handler as a Node HTTP server on port 3000.

During alchemy dev the site is TanStack Start’s own Vite dev server and no cloud resources are created. Alchemy.remote() opts back into the live Service path.

TanStackStart: Creating TanStack Start Sites

Section titled “TanStackStart: Creating TanStack Start Sites”

Basic TanStack Start App

const site = yield* Railway.Website.TanStackStart("Web", {
rootDir: "./app",
});

Custom Domain

const site = yield* Railway.Website.TanStackStart("Web", {
rootDir: "./app",
domain: "app.example.com",
});

Process Environment

const site = yield* Railway.Website.TanStackStart("Web", {
rootDir: "./app",
env: {
GREETING: "Hello from TanStack Start on Railway!",
},
});

Read An Environment Variable From A Server Function

src/routes/index.tsx
const getApiBase = createServerFn({ method: "GET" }).handler(() => ({
apiBase: process.env.API_BASE,
}));

Source: src/Railway/Website/Vinext.ts

Deploy a vinext application to Railway as a long-running Node process: vinext’s Vite build, then vinext’s production server (startProdServer) on PORT (default 3000). Not the Cloudflare Worker path — those wrappers are workerd.

The image npm installs vinext, react, react-dom, and react-server-dom-webpack, and bakes dist/ into /app.

During alchemy dev the site is vinext’s own dev server (vinext dev) and no cloud resources are declared; Alchemy.remote() opts back into the full live deployment.

const site = yield* Railway.Website.Vinext("Web", {
rootDir: "./app",
});

Process Environment

const site = yield* Railway.Website.Vinext("Web", {
rootDir: "./app",
env: {
GREETING: "Hello from vinext on Railway!",
},
});

Redis data cache

const project = yield* Railway.Project("Project");
const redis = yield* Railway.Redis("Cache", { project });
const site = yield* Railway.Website.Vinext("Web", {
project,
redis,
});

Source: src/Railway/Website/Vite.ts

Deploy a plain Vite application to Railway: static assets (the vite build output) served by a generated Node static-file server on one Railway.Service. For client-only projects — React/Vue/Solid SPAs, index.html multi-page apps — whose entire deployable output is static assets.

The build runs through @alchemy.run/frontend-frameworks/vite with the @alchemy.run/frontend-frameworks/vite/node deploy target — the package must be installed in your project. Your project’s own vite.config.* (plugins included) drives the build.

During alchemy dev the site is Vite’s own dev server (native HMR) and no cloud resources are created — the site’s url is the dev server’s local address. Alchemy.remote() opts back into the full deployment.

SSR frameworks that wrap Vite deploy through their own composites (Railway.Website.Astro, Railway.Website.SvelteKit, Railway.Website.Octane, …) — this composite never creates a framework server module.

Basic Vite SPA

const site = yield* Railway.Website.Vite("Web");

Project in a Subdirectory

const site = yield* Railway.Website.Vite("Web", {
rootDir: "./app",
});

Existing Project

const project = yield* Railway.Project("Site");
const site = yield* Railway.Website.Vite("Web", {
project,
});
const site = yield* Railway.Website.Vite("Docs", {
assets: { notFoundHandling: "404-page" },
});
const site = yield* Railway.Website.Vite("Docs", {
vite: { outDir: "build", base: "/docs/" },
});
// `alchemy dev` starts `vite` programmatically: site.url is the local
// dev server (HMR included); no Project or Service is created.
const site = yield* Railway.Website.Vite("Web");

Source: src/Railway/Website/Vocs.ts

Deploy a Vocs documentation project to Railway: static assets first, then Vocs’ Waku RSC handler (/about not /about/) from one Railway.Service.

Requires @alchemy.run/frontend-frameworks, vocs, and Vocs’ Waku peer dependencies in the project.

During alchemy dev the site is Vocs’ own dev server and no cloud resources are created. Alchemy.remote() opts back into the live Service path.

const docs = yield* Railway.Website.Vocs("Docs", {
rootDir: "./docs",
});
// vocs.config.ts: defineConfig({ outDir: "build" })
const docs = yield* Railway.Website.Vocs("Docs", {
rootDir: "./docs",
outDir: "build",
});

Source: src/Railway/Website/Waku.ts

Deploy a Waku application to Railway: the RSC server plus SSG pages on one Railway.Service. The Node deploy target selects waku’s node adapter; do not set unstable_adapter. SSG pages are served extensionless (/about).

During alchemy dev the site is Waku’s own dev server and no cloud resources are created. Alchemy.remote() opts back into the live Service path.

const site = yield* Railway.Website.Waku("Web", {
rootDir: "./app",
});
const site = yield* Railway.Website.Waku("Web", {
rootDir: "./app",
env: {
API_BASE: "https://api.example.com",
},
});