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
Cloudflare.Website.Foldkit deploys them with a single declaration:
no main entrypoint, no build command, no output directory, no
Wrangler configuration.
Foldkit is Cloudflare.Website.Vite
with Foldkit’s defaults applied: client-side routing is assumed, so
deep links fall back to index.html without you configuring
anything.
Configure Vite
Section titled “Configure Vite”Your Vite config stays what Foldkit’s setup gives you:
import { foldkit } from "@foldkit/vite-plugin";import { defineConfig } from "vite";
export default defineConfig({ plugins: [foldkit()], optimizeDeps: { entries: ["src/entry.ts"], },});Alchemy runs Vite programmatically on the project root and layers its Cloudflare integration on top of this config — 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 Cloudflare from "alchemy/Cloudflare";
export const Website = Cloudflare.Website.Foldkit("Foldkit");For an app in a subdirectory of a monorepo, point rootDir at it:
export const Website = Cloudflare.Website.Foldkit("Foldkit", { rootDir: "applications/web",});Add it to the Stack
Section titled “Add it to the Stack”Yield the class from your Stack and return its URL — see examples/cloudflare-foldkit for the checked-in example:
import * as Alchemy from "alchemy";import * as Effect from "effect/Effect";
export default Alchemy.Stack( "CloudflareFoldkitExample", { providers: Cloudflare.providers(), state: Cloudflare.state(), }, Effect.gen(function* () { const worker = yield* Website;
return { url: worker.url, }; }),);Alchemy builds the client assets and serves them from a Worker at
the returned 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 at the server as a
request for a file that doesn’t exist.
Foldkit defaults assets.notFoundHandling to
"single-page-application", which returns index.html for unmatched
paths instead of a 404 — the Foldkit runtime resolves the route once
the app boots.
A Foldkit app that ships a real 404 page overrides the default with
"404-page" — Workers Assets serves the nearest 404.html:
export const Website = Cloudflare.Website.Foldkit("Foldkit", { assets: { notFoundHandling: "404-page", },});Add environment variables
Section titled “Add environment variables”A Foldkit SPA has no server, so anything it needs from the rest of
your Stack is baked into the bundle at build time — pass a
VITE_-prefixed key in env:
export const Website = Cloudflare.Website.Foldkit("Foldkit", { env: { VITE_API_URL: backend.url.as<string>(), },});Type it for your Foldkit code with Vite’s standard ImportMetaEnv
augmentation:
/// <reference types="vite/client" />
interface ImportMetaEnv { readonly VITE_API_URL: string;}Client code reads it as import.meta.env.VITE_API_URL (e.g. from a
Command that fetches it). See
Environment for the full
inlining semantics.
Add a Worker in front of the app
Section titled “Add a Worker in front of the app”A Foldkit deployment is pure assets by default — no Worker code runs
on a request. Point main at your own module when the deployment
must do something at the edge: serve an API route, wrap the app in
error reporting, or export Durable Object classes.
The module builds through Vite’s ssr environment and becomes the
deployed Worker entry. The client build is still served through the
Worker’s ASSETS binding, so the SPA fallback keeps working behind
it:
type Env = { ASSETS: { fetch(request: Request): Promise<Response> }; TICKER: KVNamespace;};
export default { async fetch(request: Request, env: Env): Promise<Response> { const url = new URL(request.url);
if (url.pathname === "/api/ticker") { const body = await env.TICKER.get("ticker:clubs"); return new Response(body, { headers: { "content-type": "application/json" }, }); }
return env.ASSETS.fetch(request); },};Your vite.config.ts needs no changes — the injected Cloudflare
plugin points the ssr environment at main itself. Just declare
the entry and bind what the Worker needs:
const ticker = yield* Cloudflare.KV.Namespace("Ticker");
export const Website = Cloudflare.Website.Foldkit("Foldkit", { main: "src/worker.ts", env: { TICKER: ticker }, assets: { runWorkerFirst: ["/api/*"], },});Bindings passed in env are reachable from this Worker entry (and
from cron handlers), not from browser code — a Foldkit app runs on
the client, so anything it needs must come from a route the Worker
serves. Type the Worker’s env with
Cloudflare.InferEnv when you want
the bindings inferred rather than hand-written.
alchemy dev runs the app’s own Vite dev server, so Foldkit’s HMR
with state preservation and its devtools wiring work unchanged.
Bindings are live, so a custom main entry sees real KV namespaces
and secrets.
Pin the dev server’s address when several apps run in one Stack:
export const Website = Cloudflare.Website.Foldkit("Foldkit", { dev: { host: "127.0.0.1", port: 5180, strictPort: true },});