Skip to content

feat(cloudflare): Support @cloudflare/opennextjs / wrangler with full parity#142

Open
czxtm wants to merge 4 commits into
alchemy-run:mainfrom
czxtm:prototype/bundle-false-walk-modules
Open

feat(cloudflare): Support @cloudflare/opennextjs / wrangler with full parity#142
czxtm wants to merge 4 commits into
alchemy-run:mainfrom
czxtm:prototype/bundle-false-walk-modules

Conversation

@czxtm
Copy link
Copy Markdown
Contributor

@czxtm czxtm commented May 1, 2026

This is a follow-up to #117 - Both PR's should be merged but I separated them for clarity.

#117 adds an option to opt-out of re-bundling by rolldown - that is a dependency for supporting opennext. #117 on its own is not sufficient to support all cloudflare/opennextjs apps. This PR fixes that.

After more testing I discovered that wrangler includes not just the main file, but any other file in the same directory. It also has a few other special-cased behaviors that are required.

This PR should allow any wrangler-deployed app to be deployed by Alchemy, as it actually uses wrangler under-the-hood (or uses esbuild equivalently) and therefore produces the exact same bundle. There are 2 approaches that could be taken - I included both so you can simply delete one of them after deciding which path to take.

OpenNextWranglerSubprocess spawns a subprocess and runs wrangler --dry-run which is the officially recommended way to produce the exact bundle that wrangler would upload.

OpenNext does not use a subprocess but instead imports it from wrangler to do the same thing programatically by calling esbuild the same way wrangler does. The obvious risk here is that if that ever changes, then there would be drift. It's also not part of the public API so there's also maintenance risk. The other downside is it introduces a couple dependencies.

Both of them produce the exact same thing and successfully deploy the example.

czxtm and others added 4 commits April 29, 2026 21:24
…r-byte

When deploying a Worker whose `main` already points at a complete,
runtime-ready ESM bundle produced by an external tool (OpenNext,
wrangler, custom build pipelines, etc.), alchemy still runs that
artifact through `cloudflareRolldown`.

`isExternal: true` already disables alchemy's Effect-wrapping virtual
entry, but `prepareBundle` still calls `Bundle.build(...)` with
`cloudflareRolldown` on the way through. There is currently no way to
opt out of that re-bundle.

This adds a new `WorkerProps.bundle?: boolean` (default `true`). When
set to `false`, `prepareBundle` reads `props.main` directly and returns
a synthetic single-file `Bundle.BundleOutput` whose `content` is the
file's bytes verbatim and whose `hash` is sha256(bytes). The downstream
upload path is unchanged.

The accompanying integration test works as follows:

1. Deploy a hand-written ESM bundle that contains a SENTINEL comment
2. Verify the resulting `worker.hash?.bundle` equals `sha256(sourceBytes)` (which is
   impossible to satisfy if rolldown ran on the file)
3. Round-trip a request through the deployed Worker to confirm the literal sentinel
   string survives.
Minimal Next.js + OpenNext app deployed via `Cloudflare.Worker` with
`bundle: false`, demonstrating the new opt-out for alchemy's rolldown
step on pre-built worker bundles. Condensed from the upstream repro
(czxtm/repro-alchemy-bundle-false) — 11 files instead of ~36, no
Fumadocs/MDX/AI/Tailwind scaffolding.
Adds two prototype resources for deploying Next.js apps via OpenNext to
Cloudflare Workers, both bypassing alchemy's rolldown bundler:

- `Cloudflare.OpenNext` — bundles `.open-next/worker.js` in-process via
  esbuild + `@cloudflare/unenv-preset` + a vendored copy of wrangler's
  `nodejsHybridPlugin`. No subprocess; backed by a new
  `Cloudflare.OpenNextBundle` resource so esbuild runs during the apply
  phase where `Output`s materialize.
- `Cloudflare.OpenNextWranglerSubprocess` — shells out to
  `wrangler deploy --dry-run --outdir=...` to delegate bundling to
  wrangler's exact pipeline. Tracks wrangler precisely with no plugin
  drift risk.

Both compose `Build.Command` (next + opennext build) → bundler →
`Cloudflare.Worker` with `bundle: false`, and pass assets via
`AssetsWithHash` so `Worker` doesn't read `.open-next/assets` during
plan before the build has run.

Verified end-to-end (HTTP 200 + correct title) on clean deploy and
update re-deploy for both variants via the cloudflare-nextjs example.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant