Foldkit
Cloudflare.Website.Foldkit deploys a Foldkit app
as a Cloudflare Worker. Alchemy runs the project’s Vite build and uploads
the client output as Worker static assets. With ssr.build enabled, the
generated Fetch handler becomes the Worker entry.
Configure rendering in the application’s Foldkit plugin. The resource supports browser-only SPAs, server-side rendering (SSR), static site generation (SSG), and hybrid rendering. Asset routing follows the build’s output.
Configure Vite
Section titled “Configure Vite”Keep the foldkit() plugin from the Foldkit scaffold and your normal Vite
plugins:
import { foldkit } from "@foldkit/vite-plugin";import { defineConfig } from "vite";
export default defineConfig({ plugins: [foldkit()],});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. This is a
client-only app: a plain vite build, deployed as assets.
Server rendering and prerendering
Section titled “Server rendering and prerendering”Configure rendering in the application’s foldkit(...) plugin. This example
uses Foldkit 0.167.0, @foldkit/vite-plugin 0.27.0 and compatible Effect
dependencies:
import { foldkit } from "@foldkit/vite-plugin";import { defineConfig } from "vite";
export default defineConfig({ plugins: [ foldkit({ ssr: { serverEntry: "/src/entry.server.ts", clientEntry: "/src/entry.ts", build: true, }, }), ],});The server entry exports renderPage and renderDocument. Import stylesheets
from the client entry and use Runtime.hydrate to start the browser application.
Remove the source index.html for this build: Foldkit generates the complete
HTML document, including the client script, stylesheets and module preload links.
import { Server } from "foldkit/experimental";
export const renderDocument = Server.renderDocument;Keep your application’s renderPage export alongside the document renderer.
Foldkit currently exposes its server rendering API through foldkit/experimental;
see its server rendering guide for
the application and hydration contract.
For static site generation (SSG), change build to { prerender: true } and
export the URLs to generate from the server entry:
export const prerenderPaths = ["/", "/about"];SSG renders those URLs during the build. SSR renders a page when its request
arrives. A hybrid application prerenders some URLs and renders others on request.
The same renderPage and renderDocument exports serve both delivery policies.
The same Website declaration deploys the generated handler and client assets.
Prerendered pages are served as files. Requests that match no file reach the
handler, where renderPage chooses whether to render a page, redirect or return
404. For an SSG-only application, return 404 for URLs outside its generated
route set. Rendering those URLs on request makes the deployment hybrid.
Foldkit generates the shared client/server build identity automatically.
Declare the Website
Section titled “Declare the Website”Declare the site as a module-level const (rather than inline in the Stack) and derive the typed shape of its bindings from it:
import * as Cloudflare from "@alchemy.run/cloudflare";
export const Website = Cloudflare.Website.Foldkit("Foldkit");
export type WebsiteEnv = Cloudflare.InferEnv<typeof Website>;WebsiteEnv is the typed shape of the Worker’s bindings. A client-only app
with a custom Worker imports it in Custom Worker entry.
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 Website Effect from your Stack and return its URL — see examples/cloudflare-foldkit-spa for the client-only shape and examples/cloudflare-foldkit-ssr for the server-rendered one:
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, }; }),);Asset routing
Section titled “Asset routing”GET and HEAD requests serve matching client assets and prerendered pages first.
The browser build contains a rendered homepage only when / was prerendered;
otherwise the homepage reaches the handler. Other application methods reach
the Worker rather than a prerendered file.
Browser-only builds use notFoundHandling: "single-page-application". An
unmatched path serves index.html, and the browser router resolves the URL.
Server builds use normal asset lookup and send misses to Foldkit’s handler.
Explicit assets settings override these defaults. An application that
ships a real 404 page opts out of the fallback:
export const Website = Cloudflare.Website.Foldkit("Foldkit", { assets: { notFoundHandling: "404-page", },});Add environment variables
Section titled “Add environment variables”Pass browser configuration through VITE_-prefixed keys in env. Alchemy
inlines these values into import.meta.env.* during the Vite build:
export const Website = Cloudflare.Website.Foldkit("Foldkit", { env: { VITE_API_URL: backend.url.as<string>(), },});Read the environment in client code
Section titled “Read the environment in client code”Type a custom public key 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. See
Environment for the full
inlining semantics.
The same env prop carries runtime bindings, including KV namespaces, secrets
and other Workers. A custom Worker reads them from its env argument. Keep
secrets out of VITE_* keys and serialized Foldkit Flags; both reach the browser.
Custom Worker entry
Section titled “Custom Worker entry”A client-only app that must also do something at the edge — serve an
API route, wrap the app in error reporting, export Durable Object
classes — points main at its own module. The module builds through
Vite’s ssr environment and becomes the deployed Worker entry. The
client build is still reachable through the Worker’s ASSETS
binding, so the Worker answers its own routes and hands everything
else back to the asset layer:
import type { WebsiteEnv } from "../alchemy.run.ts";
export default { async fetch(request: Request, env: WebsiteEnv): 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:
export const Ticker = Cloudflare.KV.Namespace("Ticker");
export const Website = Cloudflare.Website.Foldkit("Foldkit", { main: "src/worker.ts", env: { TICKER: Ticker }, assets: { runWorkerFirst: ["/api/*"], },});runWorkerFirst sends /api/* to the Worker ahead of the asset
layer. The client-only fallback is still derived — a deep link the
Worker hands back through env.ASSETS.fetch is answered with the
template as before.
env.TICKER is typed as a KV namespace, and Alchemy supplies the ASSETS
binding. These runtime bindings are available to Worker code; browser code
receives only the public build-time values.
A server-rendered or prerendered app cannot take a main: with
ssr.build set, Foldkit’s plugin owns the server entry, and the
build fails if another module claims it. Handle page redirects, status and
response headers in renderPage. Put data APIs or additional Worker handlers in a separate Worker.
Local dev
Section titled “Local dev”pnpm alchemy devbun alchemy devnpx alchemy devyarn alchemy devalchemy dev runs Foldkit’s native Vite dev server with state-preserving live
reload and devtools. SSR uses the same server entry as the production build.
A development reload restores the Model and rebuilds the DOM; refresh manually
to exercise hydration from the server-rendered HTML.
A client-only app with a custom main runs its Worker through workerd and can
use Cloudflare bindings. The native Foldkit SSR dev host runs in Node; it does
not expose the generated Worker’s bindings as an env argument to renderPage.
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 },});Where next
Section titled “Where next”- The Vite resource — build model, env inlining, runtime bindings, memo, dev mode.
- SPA / CSR example — the generated browser-only counter.
- SSG example — prerendered home and about pages with hydration.
- SSR example — the generated cookie-backed counter, rendered per request.
- Hybrid example — prerendered pages and a request-rendered counter in one application.
- Foldkit server rendering
— the hydration contract, automatic build identity,
ssr.build, andfoldkit.build.json. - Frontend overview — the rest of the Cloudflare frontend family.