Skip to content

StaticSite

Source: src/Cloudflare/Website/StaticSite.ts

A Cloudflare Worker that serves static assets built by a shell command.

StaticSite runs a build command (e.g. npm run build), content-hashes the output directory, and deploys the result as a Cloudflare Worker with static assets. Use this when your site has its own build step that produces a directory of files — Hugo, Zola, Eleventy, or any custom pipeline.

For Vite-based projects, prefer Cloudflare.Website.Vite which handles building automatically.

Point command at your build script and outdir at where it writes output. Alchemy runs the command, hashes the output, and deploys it as an assets-only Worker — no Worker code is uploaded, and Cloudflare’s asset layer serves every request itself.

Deploying a Hugo site

const site = yield* Cloudflare.Website.StaticSite("Blog", {
command: "hugo --minify",
outdir: "public",
});

Provide main to put your own Worker in front of the assets instead. The Worker receives an ASSETS binding it can delegate to:

src/worker.ts
export default {
fetch: (request: Request, env: { ASSETS: Fetcher }) =>
env.ASSETS.fetch(request),
};

Custom Worker in front of the assets

const site = yield* Cloudflare.Website.StaticSite("Blog", {
command: "hugo --minify",
outdir: "public",
main: "./src/worker.ts",
});

Use assets to control how Cloudflare handles routing for your static files — HTML handling, not-found behavior, etc.

const site = yield* Cloudflare.Website.StaticSite("App", {
command: "npm run build",
outdir: "dist",
main: "./src/worker.ts",
assets: {
htmlHandling: "auto-trailing-slash",
notFoundHandling: "single-page-application",
},
});

Set cwd to run the build command in a subdirectory (e.g. a monorepo package). outdir is resolved relative to cwd.

const site = yield* Cloudflare.Website.StaticSite("Web", {
cwd: "apps/web",
command: "npm run build",
outdir: "dist",
main: "apps/web/worker.ts",
});

By default, all non-gitignored files are hashed to decide whether the build should re-run. Use memo to narrow the scope.

Narrowing the memo scope

const site = yield* Cloudflare.Website.StaticSite("Docs", {
command: "npm run build",
outdir: "dist",
main: "./src/worker.ts",
memo: {
include: ["content/**", "templates/**", "config.toml"],
},
});

Rebuilding when a sibling workspace package changes

The default scope only hashes files under cwd (plus the nearest lockfile), so edits to a sibling workspace package the app imports do not retrigger the build on their own. Add the sibling’s sources with a ../ include glob — and keep lockfile: true, since providing include otherwise drops the lockfile from the hash:

const site = yield* Cloudflare.Website.StaticSite("Web", {
cwd: "apps/web",
command: "npm run build",
outdir: "dist",
main: "./src/worker.ts",
memo: {
include: ["**\/*", "../../packages/env/src/**"],
lockfile: true,
},
});

Calling StaticSite with no arguments returns a constructor you can extend to declare the Worker as a named class. The class is both an Effect you can yield* to deploy and a type you can reference elsewhere — useful when other resources need to bind to this Worker.

class Blog extends Cloudflare.Website.StaticSite<Blog>()("Blog", {
command: "hugo --minify",
outdir: "public",
main: "./src/worker.ts",
}) {}
const site = yield* Blog;