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
AWS.Website.Foldkit deploys one with a single declaration: the client
build lands in a private S3 bucket and serves through CloudFront, routed
at the edge. There is no server function, no build command, and no
CloudFormation to write.
Foldkit is AWS.Website.Vite with
Foldkit’s defaults applied: client-side routing is assumed, so deep
links fall back to the app shell without you configuring anything.
Install
Section titled “Install”The build integration is not bundled with alchemy. Install
@alchemy.run/frontend-frameworks; the resource loads its /vite and
/vite/aws 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:
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, so 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 AWS from "alchemy/AWS";
export const Website = AWS.Website.Foldkit("Website");For an app in a subdirectory of a monorepo, point rootDir at it:
export const Website = AWS.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: AWS.providers(), state: AWS.state(), }, Effect.gen(function* () { const site = yield* Website; return { url: site.url }; }),);One declaration expands into the whole stack — an S3 bucket, the asset upload, a CloudFront KeyValueStore holding the file manifest, a viewer-request CloudFront Function, and the distribution itself.
bun alchemy deployDeep 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 edge as a
request for a file that does not exist.
Foldkit sets spa by default, which answers those misses with
index.html and a 200 — the Foldkit runtime resolves the route once
the app boots.
An app that ships a real 404 page opts out with errorPage. The two are
mutually exclusive:
export const Website = AWS.Website.Foldkit("Website", { spa: false, errorPage: "404.html",});Add environment variables
Section titled “Add environment variables”A Foldkit app has no server, so anything it needs is baked into the
bundle at build time. Vite inlines VITE_-prefixed variables from the
environment the build runs in — a .env file in the project root, or
the shell that runs alchemy deploy:
VITE_API_URL=https://api.example.comType 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 your API.
A value produced by another resource in the Stack (a Function URL, a
table name) is not known when this build runs, and the resource takes no
env prop. Either serve it from an
API the app fetches at runtime, or deploy
with AWS.Website.StaticSite instead —
it runs the build as a command and passes environment to it, so an
Output can be inlined at build time.
Deploy-time overrides
Section titled “Deploy-time overrides”Vite configuration — the Foldkit plugin included — lives in your
project’s own vite.config.*. The vite bag is for deploy-time
overrides merged over that file, for values decided by the
deployment (config files can’t consume alchemy Outputs):
export const Website = AWS.Website.Foldkit("Docs", { vite: { base: "/docs/", outDir: "build", },});base is the public path the site deploys under (it also applies to
the dev server under alchemy dev); outDir is the build output
directory, relative to rootDir. Leave the bag unset and the resolved
values from your vite.config.* are used — Vite’s own outDir
default is dist. config selects an alternate config file, relative
to rootDir.
Local dev
Section titled “Local dev”alchemy dev runs the app’s own Vite dev server, so Foldkit’s HMR with
state preservation and its devtools wiring work unchanged. site.url is
the local address and no AWS resources are created. Wrap the site in
Alchemy.remote() to deploy the real infrastructure even during dev.
Pin the dev server’s address when several apps run in one Stack:
export const Website = AWS.Website.Foldkit("Website", { dev: { host: "127.0.0.1", port: 5180, strictPort: true },});Custom domain
Section titled “Custom domain”export const Website = AWS.Website.Foldkit("Website", { domain: { name: "app.example.com" },});The hosted zone is inferred from the hostname. Pass hostedZoneId to
pin it when several zones could match.
Serve several sites under one distribution by pointing domain.router
at an existing
AWS.Website.Router.
The site takes the Router’s outputs, so both are yielded inside the
Stack generator:
Effect.gen(function* () { const router = yield* AWS.Website.Router("Router", {});
const site = yield* AWS.Website.Foldkit("Website", { domain: { router, name: "app.example.com" }, });
return { url: site.url };});