Skip to content

Vocs

Railway.Website.Vocs deploys a Vocs documentation project as one Railway.Service from a container image. Vocs prerenders static HTML; a Node static-file server serves extensionless pages (/about not /about/). Your vocs.config.* loads natively — there is no adapter to install.

The build integration is not bundled with alchemy. Install @alchemy.run/frontend-frameworks; the resource loads /vocs/node from your project at deploy time. It is only used at build time, so a dev dependency is enough:

Terminal window
bun add -d @alchemy.run/frontend-frameworks

The project also needs vocs and Vocs’ Waku peer dependencies.

Your vocs.config.* file loads natively — title, sidebar, and any other Vocs options work exactly as they do outside Alchemy:

vocs.config.ts
import { defineConfig } from "vocs/config";
export default defineConfig({
title: "Docs",
sidebar: [
{ text: "Home", link: "/" },
{ text: "Guide", link: "/guide" },
],
});

If vocs.config.* sets outDir, pass the same value on the resource (default "dist") so generated output stays outside the rebuild hash:

export const Website = Railway.Website.Vocs("Website", {
rootDir: "./docs",
outDir: "build",
});

Declare the site as a module-level const (rather than inline in the Stack):

alchemy.run.ts
import * as Railway from "alchemy/Railway";
export const Website = Railway.Website.Vocs("Website", {
rootDir: "./docs",
});

Omit project to create a Railway.Project under this site’s namespace. Pass an existing Railway.Project when the site should live next to other Services:

export const Site = Railway.Project("Site");
export const Website = Railway.Website.Vocs("Website", {
rootDir: "./docs",
project: Site,
});

Yield the site from your Stack and return its URL:

alchemy.run.ts
import * as Alchemy from "alchemy";
import * as Effect from "effect/Effect";
export default Alchemy.Stack(
"MyVocsSite",
{
providers: Railway.providers(),
state: Alchemy.localState(),
},
Effect.gen(function* () {
const site = yield* Website;
return { url: site.url };
}),
);

The live URL is the generated https://{name}.up.railway.app (site.service.url). The Service listens on port 3000 with healthcheck: "/health". site.service and site.project are set on deploy.

Top-level env is copied onto process.env before build and alchemy dev, and onto the Service at deploy:

alchemy.run.ts
export const Website = Railway.Website.Vocs("Website", {
rootDir: "./docs",
env: {
DOCS_TITLE: "Hello from Alchemy!",
},
});

These are process environment variables, not Worker bindings.

Vocs prerenders at build time, so page content that reads process.env is baked into the HTML. The same values are set on the hosted Node process; there is no request-time Vocs server.

const title = process.env.DOCS_TITLE ?? "Docs";

alchemy dev runs Vocs’ own dev server (native HMR) instead of deploying; site.url is the local address and no Railway resources are created (service and project are undefined). Wrap the site in Alchemy.remote() to deploy the live Service even during dev:

export const Website = Railway.Website.Vocs("Website", {
rootDir: "./docs",
}).pipe(Alchemy.remote());

domain is a hostname string. Alchemy attaches a CustomDomain (targetPort: 3000); url becomes https://{domain} instead of *.up.railway.app:

export const Website = Railway.Website.Vocs("Website", {
rootDir: "./docs",
domain: "docs.example.com",
});