1---
2title: Vocs
3description: Deploy a Vocs docs site to Railway with Railway.Website.Vocs — prerendered HTML on a Railway Service, extensionless routes, and Vocs' own dev server under alchemy dev.
4sidebar:
5 order: 10
6---
7
8import { Code, Tabs, TabItem } from "@astrojs/starlight/components";
9
10export const devDeps = "@alchemy.run/frontend-frameworks";
11
12`Railway.Website.Vocs` deploys a [Vocs](https://vocs.dev)
13documentation project as one
14[Railway.Service](/railway/compute/services) from a container
15image. Vocs prerenders static HTML; a Node static-file server
16serves extensionless pages (`/about` not `/about/`). Your `vocs.config.*` loads natively — there is
17no adapter to install.
18
19## Install
20
21The build integration is not bundled with alchemy. Install
22`@alchemy.run/frontend-frameworks`; the resource loads
23`/vocs/node` from your project at deploy time. It is only used at
24build time, so a dev dependency is enough:
25
26<Tabs syncKey="pkgManager">
27 <TabItem label="bun" icon="bun">
28 <Code code={`bun add -d ${devDeps}`} lang="sh" />
29 </TabItem>
30 <TabItem label="npm" icon="npm">
31 <Code code={`npm install -D ${devDeps}`} lang="sh" />
32 </TabItem>
33 <TabItem label="pnpm" icon="pnpm">
34 <Code code={`pnpm add -D ${devDeps}`} lang="sh" />
35 </TabItem>
36 <TabItem label="yarn" icon="seti:yarn">
37 <Code code={`yarn add -D ${devDeps}`} lang="sh" />
38 </TabItem>
39</Tabs>
40
41The project also needs `vocs` and Vocs' Waku peer dependencies.
42
43## Configure Vocs
44
45Your `vocs.config.*` file loads natively — title, sidebar, and any
46other Vocs options work exactly as they do outside Alchemy:
47
48```typescript
49// vocs.config.ts
50import { defineConfig } from "vocs/config";
51
52export default defineConfig({
53 title: "Docs",
54 sidebar: [
55 { text: "Home", link: "/" },
56 { text: "Guide", link: "/guide" },
57 ],
58});
59```
60
61If `vocs.config.*` sets `outDir`, pass the same value on the
62resource (default `"dist"`) so generated output stays outside the
63rebuild hash:
64
65```typescript
66export const Website = Railway.Website.Vocs("Website", {
67 rootDir: "./docs",
68 outDir: "build",
69});
70```
71
72## Declare the Website
73
74Declare the site as a module-level const (rather than inline in the
75Stack):
76
77```typescript
78// alchemy.run.ts
79import * as Railway from "alchemy/Railway";
80
81export const Website = Railway.Website.Vocs("Website", {
82 rootDir: "./docs",
83});
84```
85
86Omit `project` to create a `Railway.Project` under this site's
87namespace. Pass an existing `Railway.Project` when the site should
88live next to other Services:
89
90```typescript
91export const Site = Railway.Project("Site");
92
93export const Website = Railway.Website.Vocs("Website", {
94 rootDir: "./docs",
95 project: Site,
96});
97```
98
99## Add it to the Stack
100
101Yield the site from your Stack and return its URL:
102
103```typescript
104// alchemy.run.ts
105import * as Alchemy from "alchemy";
106import * as Effect from "effect/Effect";
107
108export default Alchemy.Stack(
109 "MyVocsSite",
110 {
111 providers: Railway.providers(),
112 state: Alchemy.localState(),
113 },
114 Effect.gen(function* () {
115 const site = yield* Website;
116 return { url: site.url };
117 }),
118);
119```
120
121The live URL is the generated `https://{name}.up.railway.app`
122(`site.service.url`). The Service listens on port 3000 with
123`healthcheck: "/health"`. `site.service` and `site.project` are set
124on deploy.
125
126## Add environment variables
127
128Top-level `env` is copied onto `process.env` before build and
129`alchemy dev`, and onto the Service at deploy:
130
131```diff lang="typescript"
132// alchemy.run.ts
133export const Website = Railway.Website.Vocs("Website", {
134 rootDir: "./docs",
135+ env: {
136+ DOCS_TITLE: "Hello from Alchemy!",
137+ },
138});
139```
140
141These are process environment variables, not Worker bindings.
142
143## Read the environment
144
145Vocs prerenders at build time, so page content that reads
146`process.env` is baked into the HTML. The same values are set on
147the hosted Node process; there is no request-time Vocs server.
148
149```typescript
150const title = process.env.DOCS_TITLE ?? "Docs";
151```
152
153## Local development
154
155`alchemy dev` runs Vocs' own dev server (native HMR) instead of
156deploying; `site.url` is the local address and no Railway resources
157are created (`service` and `project` are `undefined`). Wrap the
158site in `Alchemy.remote()` to deploy the live
159Service even during dev:
160
161```typescript
162export const Website = Railway.Website.Vocs("Website", {
163 rootDir: "./docs",
164}).pipe(Alchemy.remote());
165```
166
167## Custom domain
168
169`domain` is a hostname string. Alchemy attaches a
170[`CustomDomain`](/railway/networking) (`targetPort: 3000`); `url`
171becomes `https://{domain}` instead of `*.up.railway.app`:
172
173```typescript
174export const Website = Railway.Website.Vocs("Website", {
175 rootDir: "./docs",
176 domain: "docs.example.com",
177});
178```
179
180## Where next
181
182- [`Vocs` reference](/providers/railway/website/vocs) — every prop
183 and attribute
184- [Projects](/railway/compute/projects) and
185 [Services](/railway/compute/services) — the container the site
186 runs in
187- [Custom domains](/railway/networking) — hostnames on a Service
188- [Railway](/railway) — the Railway provider hub