Foldkit
AWS.Website.Foldkit deploys a Foldkit app to
AWS. It supports browser-only SPAs, server-side rendering (SSR), static site
generation (SSG), and hybrid rendering. Foldkit’s Vite plugin generates the
handler and assets; Alchemy packages them for deployment.
Install
Section titled “Install”The build integration is not bundled with alchemy. Install
@alchemy.run/frontend-frameworks; the resource loads its /foldkit and
/foldkit/aws exports from your project at deploy time. It is only used at
build time, so a dev dependency is enough:
pnpm add -D https://pkg.alchemy.run/@alchemy.run/frontend-frameworks/pr:2930:a10573ebun add -d https://pkg.alchemy.run/@alchemy.run/frontend-frameworks/pr:2930:a10573enpm install -D https://pkg.alchemy.run/@alchemy.run/frontend-frameworks/pr:2930:a10573eyarn add -D https://pkg.alchemy.run/@alchemy.run/frontend-frameworks/pr:2930:a10573eConfigure 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.
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.
AWS chooses its deployment resources before building. Keep the default for a
browser-only SPA; select output: "server" for request-time rendering:
export const Website = AWS.Website.Foldkit("Website", { output: "server",});CloudFront serves assets and prerendered pages from S3. Requests that match no
file, and methods other than GET or HEAD, reach the server: a Lambda Web
Function by default, or a Lambda Function with server: "function". The
application owns its response status and headers through renderPage.
For static-only SSG, deploy the prerendered client output without a server:
export const Website = AWS.Website.Foldkit("Website", { output: "static", errorPage: "404.html",});Supply public/404.html for this example. URLs that were not generated return
404; browser interaction still works through hydration. output: "server"
retains the handler even when all known pages were prerendered. A server build
with no output choice fails rather than discarding its handler.
spa and errorPage apply only to assets-only deployments.
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.run/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.
pnpm alchemy deploybun alchemy deploynpx alchemy deployyarn alchemy deployDeep links
Section titled “Deep links”A browser-only Foldkit app that uses URL routing (Runtime.makeApplication with
routing, 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”Set server environment variables through env when deploying a server build:
export const Website = AWS.Website.Foldkit("Website", { output: "server", env: { API_BASE: "https://api.example.com" },});Server code reads these values from process.env. Alchemy applies them to the
native dev server under alchemy dev and to the server on deploy.
An Output from another resource can be passed inside the Stack generator:
Effect.gen(function* () { const api = yield* Api; const site = yield* AWS.Website.Foldkit("Website", { output: "server", env: { API_BASE: api.url }, }); return { url: site.url };});Read the environment in client code
Section titled “Read the environment in client code”Vite inlines VITE_* values as import.meta.env.VITE_* during the build.
Set browser configuration in the app’s .env file or the deployment environment:
VITE_API_URL=https://api.example.comconst apiUrl = import.meta.env.VITE_API_URL;Values forwarded through env are also available to the build. A VITE_* key
becomes public browser configuration; keep credentials in unprefixed server
variables. Values serialized into Foldkit Flags also reach the browser.
Build configuration
Section titled “Build configuration”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: { outDir: "build", },});outDir is relative to rootDir. For server builds, Alchemy places client and
server outputs under client/ and server/ in this directory. Without an
override, the app’s resolved output directories are used. config selects an
alternate Vite configuration file relative to rootDir.
Local dev
Section titled “Local dev”alchemy dev runs the app’s own Vite dev server, so Foldkit’s
state-preserving live reload 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 };});