Skip to content

2.0.0-beta.52 - Dev Servers & Browser Actions

beta.52 is a dev-loop release: alchemy dev can now run your static site’s own framework dev server instead of rebuilding on every change, backed by a new Build.DevServer resource that survives HMR restarts. Browser Rendering quick actions become first-class typed Effects, and Vite / StaticSite gain the same class form as Worker.

Cloudflare.StaticSite grows a dev prop. On deploy nothing changes — the build command runs and the output directory ships as Worker assets. Under alchemy dev, the build is skipped and dev.command is spawned as your local server instead:

const site = yield* Cloudflare.StaticSite("Website", {
command: "zola build",
outdir: "public",
dev: {
command: "zola serve",
},
assets: {
notFoundHandling: "404-page",
},
});

site.url is the deployed URL on deploy and the local dev server’s URL in dev. The repo’s cloudflare-static-site example was rebuilt around Zola to show the flow.

Under the hood this is a new Build.DevServer resource: a long-lived shell process scoped to the stack instance, started during alchemy dev, restarted when its inputs change, and a no-op on deploy. It runs inside the dev sidecar so it survives user-code HMR restarts, mirrors the child’s stdout/stderr to your terminal, and scans output for the first http(s):// URL, exposed as its url output:

const dev = yield* DevServer("Frontend", {
command: "npm run dev",
});
console.log(dev.url); // e.g. "http://localhost:5173"

For servers that don’t print a URL, StaticSite’s dev.url prop sets it explicitly. (#537)

Worker, StaticSite, and Vite previously took three different assets shapes (assets.config, assetsConfig, and { path, hash, config }). Now AssetsProps extends AssetsConfig, so routing options like runWorkerFirst, htmlHandling, and notFoundHandling sit directly on assets for all three:

const app = yield* Cloudflare.Vite("SolidStart", {
compatibility: {
flags: ["nodejs_compat"],
},
assets: {
config: {
runWorkerFirst: true,
},
runWorkerFirst: true,
},
});

For StaticSite, rename the assetsConfig: key to assets:. (#534)

The BrowserClient returned by yield* Cloudflare.Browser(...) now mirrors Cloudflare’s Browser Rendering quick actions as typed Effect operations. JSON actions (content, markdown, links, scrape, snapshot, json) resolve to their parsed success payloads and fail with a typed BrowserError; binary actions (screenshot, pdf) return a Stream of the response bytes. No more hand-rolling Response.json() against the raw binding:

const browser = yield* Cloudflare.Browser({ name: "BROWSER" });
const url = "https://example.com";
// JSON quick actions resolve to their parsed payload.
const markdown = yield* browser.markdown({ url });
const { result: links } = yield* browser.links({ url });
// Binary actions stream bytes — collect or pipe them.
const png = yield* browser.screenshot({ url }).pipe(Stream.runCollect);

Provide Cloudflare.BrowserBindingLive on the Worker’s init Effect. raw and fetch remain as escape hatches for promise-based libraries like @cloudflare/puppeteer. (#525)

Cloudflare.Vite and Cloudflare.StaticSite support the same zero-arg class-constructor form as Cloudflare.Worker: calling with no arguments returns a constructor you extend to declare the site as a named class — both a yieldable Effect and a type other resources can reference:

export class Website extends Cloudflare.Vite<Website>()("Website", {
compatibility: {
flags: ["nodejs_compat"],
},
assets: {
runWorkerFirst: true,
},
}) {}
export type WebsiteEnv = Cloudflare.InferEnv<typeof Website>;
// in the stack:
const site = yield* Website;

The cloudflare-tanstack example uses it. (#527)

  • Version Metadata bindingyield* Cloudflare.VersionMetadata() attaches Cloudflare’s version_metadata binding and returns an accessor for the deployed version’s id, tag, and timestamp; also declarable on env. Thanks Alex (#524).
  • Workflow runs get a per-invocation ExecutionContext (scope + cache), so binding helpers like Drizzle.postgres resolve inside workflow steps just like in a fetch or queue handler. Thanks Saatvik Arya (#515).
  • retain removal policy is honored on replace — the old generation of a replaced resource is retained, not just orphan deletes (#548).
  • Changing a Redacted config/secret value now triggers an update — redacted wrappers are unwrapped during diffing (#549).
  • Local dev fixes — the assets binding is populated for Vite websites (#547), cross-script Durable Object bindings work (#540), and multi-worker dev is more reliable in bun (#535).
  • Drizzle Schema exposes out as a cwd-relative path so drizzle-kit resolves migrations regardless of where alchemy runs (#551).
  • The Vite SPA tutorial documents the local dev workflow (#526).
  • @distilled.cloud/* bumped to 0.23.1 (#539).