Deploy a static site
AWS.Website.StaticSite deploys a directory of files as a website: it uploads
the files to a private S3 bucket, creates a CloudFront distribution with a
CloudFront Function that routes requests at the edge via a KeyValueStore file
manifest, and invalidates the cache when your assets change. Use it whenever
the output is plain files — docs, marketing pages, or a built SPA.
Deploy a directory
Section titled “Deploy a directory”Point path at a directory of files and return the site’s url as a stack
output:
import * as Alchemy from "alchemy";import * as AWS from "alchemy/AWS";import * as Effect from "effect/Effect";
export default Alchemy.Stack( "MyStaticSite", { providers: AWS.providers(), state: Alchemy.localState(), }, Effect.gen(function* () { const site = yield* AWS.Website.StaticSite("MarketingSite", { path: "./site", forceDestroy: true, });
return { url: site.url, }; }),);One StaticSite 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. forceDestroy: true lets
destroy empty the bucket before deleting it; site.url resolves to the
CloudFront domain (or https://<your-domain> when you pass domain).
bun alchemy deployThe deployed example lives at
examples/aws-static-site
if you want a runnable starting point.
Run a build step first
Section titled “Run a build step first”For framework sites (Vite, Astro, etc.), add build so the framework’s build
runs before upload and the output directory is what gets deployed:
const site = yield* AWS.Website.StaticSite("Web", { path: "./frontend", build: { command: "bun run build", output: "dist", }, environment: { VITE_API_URL: api.url, },});The build step is a Command.Build resource —
memoized, so unchanged inputs skip the build
(details). The input hash covers, by default, all
files under path (filtered by your gitignore rules) plus the nearest
package-manager lockfile. Tune the hash with build.include, build.exclude, and
build.lockfile — e.g. include: ["src/**", "package.json"] to re-run only
when source files change. environment variables are passed to the build
command, so outputs from other resources (like an API URL) flow straight into
the frontend bundle.
Compose sites with a Router
Section titled “Compose sites with a Router”AWS.Website.Router is a shared CloudFront front door: one distribution whose
routes live in a KeyValueStore, matched at the edge by a CloudFront Function.
Create the router, then attach sites to it instead of giving each site its own
distribution:
const router = yield* AWS.Website.Router("WebsiteRouter", { domain: { name: "example.com", hostedZoneId },});
const site = yield* AWS.Website.StaticSite("Docs", { path: "./docs", router: { instance: router, path: "/docs", },});The site registers itself by writing its file manifest and metadata into the
router’s KV store — no distribution redeploy is needed to add or remove a
site. When attaching to a router, put domain and edge on the Router, not
the StaticSite; passing either alongside router is an error.
Route to non-site origins
Section titled “Route to non-site origins”The router also takes inline routes for origins that aren’t managed by
StaticSite, keyed by path pattern:
const router = yield* AWS.Website.Router("WebsiteRouter", { routes: { "/*": { url: api.functionUrl }, },});A route value can be a URL (proxied as a custom origin) or a
{ bucket } route serving straight from S3, optionally with a
rewrite: { regex, to } applied to the URI before forwarding. Longest
host-then-path match wins, so a StaticSite mounted at /docs takes
precedence over a /* fallback route.
Cache invalidation on deploy
Section titled “Cache invalidation on deploy”By default every deploy that changes your files creates a CloudFront
invalidation for /* and moves on without waiting
({ paths: "all", wait: false }). The invalidation is keyed to a content hash
of the uploaded assets, so deploys that change nothing don’t invalidate
anything. Tune it with invalidation:
const site = yield* AWS.Website.StaticSite("MarketingSite", { path: "./site", invalidation: { paths: "all", wait: true, },});paths: "all"— invalidate/*(the default)paths: "versioned"— invalidate only the index page, for sites whose assets are content-hashed and immutablepaths: [...]— an explicit list of pathswait: true— block the deploy until CloudFront reports the invalidation completeinvalidation: false— skip invalidation entirely
Where next
Section titled “Where next”- Websites on AWS — the full websites surface, including server-side rendering
- StaticSite reference and Router reference — every prop and attribute
- AWS on Alchemy — the AWS provider hub