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