| 1 | --- |
| 2 | title: 2.0.0-beta.39 - HTTP Lifecycle & Vite Env |
| 3 | date: 2026-05-13T18:00:00Z |
| 4 | category: release |
| 5 | excerpt: A small, high-impact fix release — `VITE_*` env props are now inlined into the client bundle, the Cloudflare Worker HTTP adapter runs handlers through Effect's standard HTTP lifecycle (unblocking `RpcServer.toHttpEffect`), and the `SendEmail` binding from beta.38 is now wired into Worker binding inference. |
| 6 | --- |
| 7 | |
| 8 | `v2.0.0-beta.39` is a fix-only release that lands three |
| 9 | things people hit immediately after beta.38: SPAs reading |
| 10 | `import.meta.env.VITE_*`, Effect RPC servers running under |
| 11 | the Cloudflare Worker adapter, and the brand-new |
| 12 | `SendEmail` binding showing up in Worker binding types. |
| 13 | |
| 14 | ## `Cloudflare.Website.Vite` inlines `VITE_*` env into the bundle |
| 15 | |
| 16 | `Cloudflare.Website.Vite("Site", { env: { ... } })` was forwarding |
| 17 | `env` only as runtime Worker bindings — the values never |
| 18 | made it into the client (or SSR) bundle. SPAs reading |
| 19 | `import.meta.env.VITE_API_URL` fell back to whatever |
| 20 | default the code hardcoded, breaking dynamic backend URLs |
| 21 | at build time. |
| 22 | |
| 23 | The build step now passes `props.env` through Vite's |
| 24 | `define` hook, emulating `vite build`'s env semantics: |
| 25 | |
| 26 | ```typescript |
| 27 | const web = yield* Cloudflare.Website.Vite("Web", { |
| 28 | env: { VITE_API_URL: worker.url.as<string>() }, |
| 29 | }); |
| 30 | // SPA: `import.meta.env.VITE_API_URL` now contains the deployed worker URL. |
| 31 | ``` |
| 32 | |
| 33 | The rules match Vite itself: |
| 34 | |
| 35 | - Only `VITE_`-prefixed keys are inlined into the bundle |
| 36 | as `import.meta.env.<key>` (matches Vite's default |
| 37 | `envPrefix`). |
| 38 | - Non-`VITE_` entries stay out of the bundle but are still |
| 39 | attached to the deployed Worker as runtime bindings. |
| 40 | - `Redacted` values are unwrapped when they're |
| 41 | `VITE_`-prefixed — opting them into the public bundle |
| 42 | is an explicit choice you make by naming them that way. |
| 43 | |
| 44 | The tutorial gains an [Inject the Worker URL at build |
| 45 | time](/cloudflare/frontend/vite-spa) section covering the |
| 46 | pattern end-to-end. |
| 47 | |
| 48 | ## Cloudflare Worker HTTP adapter — Effect's handled lifecycle |
| 49 | |
| 50 | The Worker HTTP adapter previously hand-rolled the |
| 51 | `HttpServerRequest` / `HttpServerResponse` bridge. That |
| 52 | worked for simple handlers but diverged from Effect's |
| 53 | `HttpEffect.toHandled` / `toWebHandler` lifecycle in ways |
| 54 | that broke scoped HTTP apps under workerd — most visibly, |
| 55 | `RpcServer.toHttpEffect` hung when served through Alchemy |
| 56 | even though the same app worked under raw Wrangler. |
| 57 | |
| 58 | The adapter now runs handlers through Effect's standard |
| 59 | HTTP lifecycle while preserving every Worker-specific |
| 60 | behavior: |
| 61 | |
| 62 | - `cf-connecting-ip` → `remoteAddress` |
| 63 | - the original Cloudflare `Request` service |
| 64 | - patched `request.raw` access |
| 65 | - existing 500 response mapping from `serveWebRequest` |
| 66 | |
| 67 | …plus the parts that were missing: stream-scope transfer, |
| 68 | HEAD body suppression, and request-abort interruption. |
| 69 | |
| 70 | The net effect: anything that worked under |
| 71 | `@effect/platform`'s standard HTTP lifecycle now works |
| 72 | under `Cloudflare.Worker` too. `RpcServer.toHttpEffect`, |
| 73 | in particular, is unblocked. |
| 74 | |
| 75 | — *Thanks to **[Will King](https://github.com/wking-io)** |
| 76 | ([#328](https://github.com/alchemy-run/alchemy/pull/328)) |
| 77 | for the fix.* Fixes |
| 78 | [#327](https://github.com/alchemy-run/alchemy/issues/327). |
| 79 | |
| 80 | ## `SendEmail` binding type inference |
| 81 | |
| 82 | beta.38 added `Cloudflare.Email.SendEmail` but two wiring sites |
| 83 | in `Worker.ts` were missed — the binding type inference |
| 84 | didn't know about it, and the deploy-time binding |
| 85 | metadata didn't emit a `send_email` entry. The result: |
| 86 | typing `bindings: { EMAIL: Email }` on a Worker errored, |
| 87 | and deploys silently dropped the binding. |
| 88 | |
| 89 | Both are wired up now — `SendEmail` is a first-class |
| 90 | member of the Worker binding union, the env key carries |
| 91 | the right runtime type, and Cloudflare receives the |
| 92 | expected `send_email` binding metadata at deploy time. |
| 93 | |
| 94 | — *Thanks to **[Gerben Mulder](https://github.com/Gerbuuun)** |
| 95 | ([#326](https://github.com/alchemy-run/alchemy/pull/326)) |
| 96 | for catching this.* |
| 97 | |
| 98 | ## Contributors |
| 99 | |
| 100 | Big thank-you to everyone who shipped code in this beta: |
| 101 | |
| 102 | - **[Will King](https://github.com/wking-io)** — Cloudflare Worker HTTP effect lifecycle ([#328](https://github.com/alchemy-run/alchemy/pull/328)) |
| 103 | - **[Gerben Mulder](https://github.com/Gerbuuun)** — `SendEmail` Worker binding type and meta ([#326](https://github.com/alchemy-run/alchemy/pull/326)) |
| 104 | |
| 105 | ## Where to go next |
| 106 | |
| 107 | - [CHANGELOG](https://github.com/alchemy-run/alchemy/blob/main/CHANGELOG.md#v200-beta39) |
| 108 | - [Compare v2.0.0-beta.38 → v2.0.0-beta.39](https://github.com/alchemy-run/alchemy/compare/v2.0.0-beta.38...v2.0.0-beta.39) |