Foldkit
Foldkit is an Elm-architecture frontend
framework built on Effect. Its apps are client-only Vite projects —
the Foldkit Vite plugin only adds HMR and devtools wiring — so
Fly.Website.Foldkit is Fly.Website.Vite
with SPA fallback to index.html. Deep links boot the app and the
Foldkit router takes over.
Deploy creates a Fly.App (unless you pass
app), a Fly.Service running a static-file
Node server on port 3000, and a shared IPv4 so
https://{app}.fly.dev answers.
Install
Section titled “Install”The build integration is not bundled with alchemy. Install
@alchemy.run/frontend-frameworks; the resource loads its /vite
and /vite/node exports from your project at deploy time. It is only
used at build time, so a dev dependency is enough:
bun add -d @alchemy.run/frontend-frameworksnpm install -D @alchemy.run/frontend-frameworkspnpm add -D @alchemy.run/frontend-frameworksyarn add -D @alchemy.run/frontend-frameworksConfigure Vite
Section titled “Configure Vite”Your Vite config stays what Foldkit’s setup gives you. Vite plugins
like Tailwind go in plugins as usual:
import { foldkit } from "@foldkit/vite-plugin";import tailwindcss from "@tailwindcss/vite";import { defineConfig } from "vite";
export default defineConfig({ plugins: [foldkit(), tailwindcss()], optimizeDeps: { entries: ["src/entry.ts"], },});Alchemy runs Vite programmatically on the project root. The Foldkit plugin and the rest of your setup are preserved as-is.
Declare the Website
Section titled “Declare the Website”Declare the site as a module-level const (rather than inline in the Stack):
import * as Fly from "alchemy/Fly";
export const Website = Fly.Website.Foldkit("Website");For an app in a subdirectory of a monorepo, point rootDir at it:
export const Website = Fly.Website.Foldkit("Website", { rootDir: "applications/web",});Add it to the Stack
Section titled “Add it to the Stack”Yield the site from your Stack and return its URL:
import * as Alchemy from "alchemy";import * as Effect from "effect/Effect";
export default Alchemy.Stack( "MyFoldkitSite", { providers: Fly.providers(), state: Alchemy.localState(), }, Effect.gen(function* () { const site = yield* Website; return { url: site.url }; }),);site.url is https://{app}.fly.dev. The site also exposes app,
service, ip, and certificate — they are undefined under
alchemy dev.
Add environment variables
Section titled “Add environment variables”A Foldkit SPA has no server of its own, so anything it needs from
the rest of your Stack is baked into the bundle at build time — pass
a VITE_-prefixed key in top-level env. Values must be strings (or
Redacted); resolve other resources’ URLs before passing them.
export const Website = Fly.Website.Foldkit("Website", { env: { VITE_API_URL: "https://api.example.com", },});The values are copied onto process.env before the Vite build and
onto the Machine. Client code reads the inlined value:
/// <reference types="vite/client" />
interface ImportMetaEnv { readonly VITE_API_URL: string;}const apiUrl = import.meta.env.VITE_API_URL;Deep links
Section titled “Deep links”A Foldkit app that uses URL routing (Runtime.makeApplication with
route, onUrlRequest, and onUrlChange) resolves routes on the
client, so a deep link like /counter/42 arrives as a request for a
file that doesn’t exist.
Foldkit defaults assets.notFoundHandling to
"single-page-application", which returns index.html (200) for
unmatched paths so the Foldkit runtime can resolve the route. Use
"404-page" to serve a real 404 instead:
export const Website = Fly.Website.Foldkit("Website", { assets: { notFoundHandling: "404-page" },});Local dev
Section titled “Local dev”alchemy dev runs Vite’s own dev server (native HMR, Foldkit
devtools) instead of deploying; site.url is the local address and
no Fly App, Service, IP, or certificate is created. Wrap the site in
Alchemy.remote() to deploy the live Service even during dev.
Pin the dev server’s address when several apps run in one Stack:
export const Website = Fly.Website.Foldkit("Website", { dev: { host: "127.0.0.1", port: 5180, strictPort: true },});Custom domain
Section titled “Custom domain”Pass a hostname to request ACME (Fly.Certificate) on the App.
url becomes https://{domain}. v1 expects that DNS already points
at the App.
const site = yield* Fly.Website.Foldkit("Web", { domain: "app.example.com",});Where next
Section titled “Where next”Foldkitreference — every prop and attribute- Vite — the same static-file Service for any Vite app
- Websites — the rest of the Fly frontend family
- Apps, Services, IPs & certificates