1---
2title: Vocs
3description: Deploy a Vocs docs site to Fly with Fly.Website.Vocs — prerendered HTML on a Node static-file server, 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`Fly.Website.Vocs` deploys a [Vocs](https://vocs.dev) documentation
13project to Fly. Vocs prerenders static HTML; the node target drops
14server modules and Alchemy hosts a tiny static-file server on a Fly
15[Service](/fly/compute/services) — one [App](/fly/compute/apps) plus
16a shared IPv4 so `https://{app}.fly.dev` answers. Extensionless
17pages work (`/about` → `about/index.html`). Your `vocs.config.*`
18loads natively — there is no adapter to install.
19
20## Install
21
22The build integration is not bundled with alchemy. Install
23`@alchemy.run/frontend-frameworks`; the resource loads
24`/vocs/node` from your project at deploy time. It is only used at
25build 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
42Your Vocs project already needs `vocs` installed.
43
44## Configure Vocs
45
46Your `vocs.config.*` file loads natively — title, sidebar, and any
47other Vocs options work exactly as they do outside Alchemy:
48
49```typescript
50// vocs.config.ts
51import { defineConfig } from "vocs/config";
52
53export default defineConfig({
54 title: "Docs",
55 sidebar: [
56 { text: "Home", link: "/" },
57 { text: "Guide", link: "/guide" },
58 ],
59});
60```
61
62If `vocs.config.*` sets `outDir`, pass the same value on the
63resource (default `"dist"`) so generated output stays outside the
64rebuild hash:
65
66```typescript
67export const Website = Fly.Website.Vocs("Website", {
68 rootDir: "./docs",
69 outDir: "build",
70});
71```
72
73## Declare the Website
74
75Declare the site as a module-level const (rather than inline in the
76Stack):
77
78```typescript
79// alchemy.run.ts
80import * as Fly from "alchemy/Fly";
81
82export const Website = Fly.Website.Vocs("Website", {
83 rootDir: "./docs",
84});
85```
86
87Omit `app` to create a `Fly.App` under this site's namespace. Pass
88an existing `Fly.App` when the site should live on an App you
89already declared.
90
91## Add it to the Stack
92
93Yield the site from your Stack and return its URL:
94
95```typescript
96// alchemy.run.ts
97import * as Alchemy from "alchemy";
98import * as Effect from "effect/Effect";
99
100export default Alchemy.Stack(
101 "MyVocsSite",
102 {
103 providers: Fly.providers(),
104 state: Alchemy.localState(),
105 },
106 Effect.gen(function* () {
107 const site = yield* Website;
108 return { url: site.url };
109 }),
110);
111```
112
113The live URL is `https://{app}.fly.dev`. The hosted server listens
114on port 3000 and answers `GET /health`. `site.app`, `site.service`,
115`site.ip`, and `site.certificate` are set on deploy.
116
117## Add environment variables
118
119Top-level `env` is copied onto `process.env` before build and
120`alchemy dev`, and onto the Machine at deploy:
121
122```diff lang="typescript"
123// alchemy.run.ts
124export const Website = Fly.Website.Vocs("Website", {
125 rootDir: "./docs",
126+ env: {
127+ DOCS_TITLE: "Hello from Alchemy!",
128+ },
129});
130```
131
132These are process environment variables, not Worker bindings.
133
134## Read the environment
135
136Vocs prerenders at build time, so page content that reads
137`process.env` is baked into the HTML. The same values are set on
138the hosted Node process; there is no request-time Vocs server.
139
140```typescript
141const title = process.env.DOCS_TITLE ?? "Docs";
142```
143
144## Local development
145
146`alchemy dev` runs Vocs' own dev server (native HMR) instead of
147deploying; `site.url` is the local address and no Fly resources are
148created (`app`, `service`, `ip`, and `certificate` are
149`undefined`). Wrap the site in `Alchemy.remote()` to deploy the
150live App and Service even during dev:
151
152```typescript
153export const Website = Fly.Website.Vocs("Website", {
154 rootDir: "./docs",
155}).pipe(Alchemy.remote());
156```
157
158## Custom domain
159
160`domain` is a hostname string. Alchemy requests ACME
161(`Fly.Certificate`) on the App; `url` becomes `https://{domain}`.
162v1 expects DNS for that hostname to already point at the App:
163
164```typescript
165export const Website = Fly.Website.Vocs("Website", {
166 rootDir: "./docs",
167 domain: "docs.example.com",
168});
169```
170
171## Where next
172
173- [`Vocs` reference](/providers/fly/website/vocs) — every prop and
174 attribute
175- [Apps](/fly/compute/apps) and [Services](/fly/compute/services) —
176 the Machine the site runs on
177- [IPs & certificates](/fly/networking) — fly.dev IPv4 and ACME
178- [Fly](/fly) — the Fly provider hub