1---
2title: Foldkit
3description: Deploy a Foldkit app to Railway with Railway.Website.Foldkit — a client-only Vite SPA on a container Service, and Vite's own dev server under alchemy dev.
4sidebar:
5 order: 8
6---
7
8import { Code, Tabs, TabItem } from "@astrojs/starlight/components";
9
10export const devDeps = "@alchemy.run/frontend-frameworks";
11
12[Foldkit](https://foldkit.dev) is an Elm-architecture frontend
13framework built on Effect. Its apps are client-only Vite projects —
14the Foldkit Vite plugin only adds HMR and devtools wiring — so
15`Railway.Website.Foldkit` is
16[`Railway.Website.Vite`](/railway/frontend/vite) with SPA fallback
17to `index.html`. Deep links boot the app and the Foldkit router
18takes over.
19
20Deploy creates a [`Railway.Project`](/railway/compute/projects)
21(unless you pass `project`) and a
22[`Railway.Service`](/railway/compute/services) from a container
23image. The URL is `https://{name}.up.railway.app`.
24
25## Install
26
27The build integration is not bundled with alchemy. Install
28`@alchemy.run/frontend-frameworks`; the resource loads its `/vite`
29and `/vite/node` exports from your project at deploy time. It is only
30used at build time, so a dev dependency is enough:
31
32<Tabs syncKey="pkgManager">
33 <TabItem label="bun" icon="bun">
34 <Code code={`bun add -d ${devDeps}`} lang="sh" />
35 </TabItem>
36 <TabItem label="npm" icon="npm">
37 <Code code={`npm install -D ${devDeps}`} lang="sh" />
38 </TabItem>
39 <TabItem label="pnpm" icon="pnpm">
40 <Code code={`pnpm add -D ${devDeps}`} lang="sh" />
41 </TabItem>
42 <TabItem label="yarn" icon="seti:yarn">
43 <Code code={`yarn add -D ${devDeps}`} lang="sh" />
44 </TabItem>
45</Tabs>
46
47## Configure Vite
48
49Your Vite config stays what Foldkit's setup gives you. Vite plugins
50like Tailwind go in `plugins` as usual:
51
52```typescript
53// vite.config.ts
54import { foldkit } from "@foldkit/vite-plugin";
55import tailwindcss from "@tailwindcss/vite";
56import { defineConfig } from "vite";
57
58export default defineConfig({
59 plugins: [foldkit(), tailwindcss()],
60 optimizeDeps: {
61 entries: ["src/entry.ts"],
62 },
63});
64```
65
66Alchemy runs Vite programmatically on the project root. The Foldkit
67plugin and the rest of your setup are preserved as-is.
68
69:::note[Pinned Effect versions]
70Foldkit pins its `effect` peer dependency to an exact version.
71Alchemy runs your app's own Vite build with your app's own
72`node_modules`, so that pin is between Foldkit and your package
73manager — Alchemy imposes no Effect version on your frontend.
74:::
75
76## Declare the Website
77
78Declare the site as a module-level const (rather than inline in the
79Stack):
80
81```typescript
82// alchemy.run.ts
83import * as Railway from "alchemy/Railway";
84
85export const Website = Railway.Website.Foldkit("Website");
86```
87
88For an app in a subdirectory of a monorepo, point `rootDir` at it:
89
90```typescript
91export const Website = Railway.Website.Foldkit("Website", {
92 rootDir: "applications/web",
93});
94```
95
96## Add it to the Stack
97
98Yield the site from your Stack and return its URL:
99
100```typescript
101// alchemy.run.ts
102import * as Alchemy from "alchemy";
103import * as Effect from "effect/Effect";
104
105export default Alchemy.Stack(
106 "MyFoldkitSite",
107 {
108 providers: Railway.providers(),
109 state: Alchemy.localState(),
110 },
111 Effect.gen(function* () {
112 const site = yield* Website;
113 return { url: site.url };
114 }),
115);
116```
117
118`site.url` is the generated `https://{name}.up.railway.app`. The
119site also exposes `service` and `project` — they are `undefined`
120under `alchemy dev`.
121
122## Add environment variables
123
124A Foldkit SPA has no server of its own, so anything it needs from
125the rest of your Stack is baked into the bundle at build time — pass
126a `VITE_`-prefixed key in top-level `env`. Values must be strings (or
127`Redacted`); resolve other resources' URLs before passing them.
128
129```diff lang="typescript"
130// alchemy.run.ts
131export const Website = Railway.Website.Foldkit("Website", {
132+ env: {
133+ VITE_API_URL: "https://api.example.com",
134+ },
135});
136```
137
138The values are copied onto `process.env` before the Vite build and
139onto the Service. Client code reads the inlined value:
140
141```typescript
142// src/vite-env.d.ts
143/// <reference types="vite/client" />
144
145interface ImportMetaEnv {
146 readonly VITE_API_URL: string;
147}
148```
149
150```typescript
151const apiUrl = import.meta.env.VITE_API_URL;
152```
153
154## Deep links
155
156A Foldkit app that uses URL routing (`Runtime.makeApplication` with
157`route`, `onUrlRequest`, and `onUrlChange`) resolves routes on the
158client, so a deep link like `/counter/42` arrives as a request for a
159file that doesn't exist.
160
161`Foldkit` defaults `assets.notFoundHandling` to
162`"single-page-application"`, which returns `index.html` (200) for
163unmatched paths so the Foldkit runtime can resolve the route. Use
164`"404-page"` to serve a real 404 instead:
165
166```typescript
167export const Website = Railway.Website.Foldkit("Website", {
168 assets: { notFoundHandling: "404-page" },
169});
170```
171
172## Local dev
173
174`alchemy dev` runs Vite's own dev server (native HMR, Foldkit
175devtools) instead of deploying; `site.url` is the local address and
176no Project or Service is created. Wrap the
177site in `Alchemy.remote()` to deploy the live Service even during
178dev.
179
180Pin the dev server's address when several apps run in one Stack:
181
182```typescript
183export const Website = Railway.Website.Foldkit("Website", {
184 dev: { host: "127.0.0.1", port: 5180, strictPort: true },
185});
186```
187
188## Custom domain
189
190Pass a hostname to attach a
191[`Railway.CustomDomain`](/railway/networking) (`targetPort` 3000).
192`url` becomes `https://{domain}`.
193
194```typescript
195const site = yield* Railway.Website.Foldkit("Web", {
196 domain: "app.example.com",
197});
198```
199
200## Where next
201
202- [`Foldkit` reference](/providers/railway/website/foldkit) — every
203 prop and attribute
204- [Vite](/railway/frontend/vite) — the same static-file Service for
205 any Vite app
206- [Websites](/railway/frontend/websites) — the rest of the Railway
207 frontend family
208- [Projects](/railway/compute/projects),
209 [Services](/railway/compute/services),
210 [Custom domains](/railway/networking)