| 1 | # @alchemy.run/frontend-frameworks/nextjs |
| 2 | |
| 3 | Next.js framework integration for Cloudflare Workers — an OpenNext-based |
| 4 | build pipeline and two dev-server modes, fully **wrangler-free**. |
| 5 | |
| 6 | - `build` runs the `@opennextjs/cloudflare` pipeline in a disposable child |
| 7 | process (no wrangler code is ever imported), performs the final bundle pass |
| 8 | wrangler would normally do at deploy time, and returns the framework-core |
| 9 | `BuildOutput` (server modules entry-first + the static-assets directory). |
| 10 | - `dev` starts a local server in one of two modes (`dev.mode`). |
| 11 | |
| 12 | ## Dev modes |
| 13 | |
| 14 | ### `"preview"` (default) — production parity, no HMR |
| 15 | |
| 16 | Builds the OpenNext worker and serves it under |
| 17 | `@alchemy.run/cloudflare-runtime/core` (workerd) with the OpenNext worker |
| 18 | shape: `ASSETS` (run-worker-first), a `WORKER_SELF_REFERENCE` self service |
| 19 | binding, and the same-script SQLite-backed revalidation-queue Durable Object. |
| 20 | What you run is what you deploy. Any change requires a rebuild. |
| 21 | |
| 22 | ### `"hmr"` — real `next dev`, Cloudflare bindings proxied |
| 23 | |
| 24 | Runs the programmatic `next({ dev: true })` custom-server API (Turbopack HMR) |
| 25 | on an http server this package owns, and opens a `cloudflare-runtime` |
| 26 | platform proxy (our wrangler-free `getPlatformProxy` equivalent) with the |
| 27 | worker's bindings. The proxy's `{ env, cf, ctx }` is planted on |
| 28 | `globalThis[Symbol.for("__cloudflare-context__")]` — the exact contract |
| 29 | `@opennextjs/cloudflare`'s `getCloudflareContext()` reads — and |
| 30 | `vm.runInContext` is patched so edge-runtime middleware/routes see it too. |
| 31 | App code works without `initOpenNextCloudflareForDev()` and without wrangler. |
| 32 | |
| 33 | ```ts |
| 34 | import * as Nextjs from "@alchemy.run/frontend-frameworks/nextjs"; |
| 35 | |
| 36 | const layer = Nextjs.make({ |
| 37 | dev: { mode: "hmr" }, |
| 38 | vite: { |
| 39 | compatibilityDate: "2026-08-31", |
| 40 | worker: { name: "my-app", bindings: [Text.local("TEST_TEXT", "value")] }, |
| 41 | }, |
| 42 | }); |
| 43 | ``` |
| 44 | |
| 45 | #### Fidelity: what `"hmr"` mode is NOT |
| 46 | |
| 47 | `"hmr"` mode runs your app code in **Node.js** (and Next's edge-runtime VM |
| 48 | sandbox for middleware/edge routes), not in workerd: |
| 49 | |
| 50 | - **Cloudflare-specific runtime behavior differs.** workerd APIs, runtime |
| 51 | limits, `nodejs_compat` shimming, `cloudflare:*` modules, request |
| 52 | lifecycles (`waitUntil` is a no-op), and the `cf` object (a static mock) |
| 53 | only behave faithfully in `"preview"` mode. |
| 54 | - **Bindings round-trip over a local proxy.** `env.*` stubs forward method |
| 55 | chains to a workerd instance hosting the real local bindings. Method |
| 56 | results must be JSON-compatible values, bytes, dates, streams, or |
| 57 | `DurableObjectId`s; rich class returns (e.g. `R2Object`) and `connect()` |
| 58 | are not supported (see `cloudflare-runtime/platform-proxy`). Durable Object |
| 59 | classes defined in your app cannot run in this mode (the proxy worker does |
| 60 | not host your compiled code). |
| 61 | - **ISR/caching semantics differ.** `next dev` never uses the OpenNext |
| 62 | incremental-cache/queue/tag-cache overrides — it uses Next's own dev cache. |
| 63 | Prerender/ISR behavior, `_next/image` via the `IMAGES` binding, and the |
| 64 | revalidation queue only exist in `"preview"` (and production). |
| 65 | |
| 66 | Rule of thumb: iterate with `"hmr"`, verify with `"preview"`. |
| 67 | |
| 68 | ## Alchemy source provider |
| 69 | |
| 70 | `@alchemy.run/frontend-frameworks/nextjs/source` implements alchemy's `WorkerSourceModule` |
| 71 | contract. The descriptor options (`NextjsSourceOptions`) are JSON-stable and |
| 72 | include `dev.mode` to select the dev-server mode: |
| 73 | |
| 74 | ```ts |
| 75 | source: { |
| 76 | provider: "@alchemy.run/frontend-frameworks/nextjs/source", |
| 77 | options: { dev: { mode: "hmr" } }, |
| 78 | } |
| 79 | ``` |
| 80 | |
| 81 | ## Notes |
| 82 | |
| 83 | - The OpenNext pipeline runs `next build` internally via a child process |
| 84 | (`runner.mjs`); a `wrangler` stub package (`wrangler-stub/`) satisfies |
| 85 | OpenNext's version probe without installing wrangler. |
| 86 | - `next` itself is resolved from the _project's_ `node_modules`, never from a |
| 87 | hoisted sibling. |
| 88 | |
| 89 | ## Upstream references |
| 90 | |
| 91 | - Next.js: [`55d30fb91d8197597c5c400174f1d38a89333de8`](https://github.com/vercel/next.js/tree/55d30fb91d8197597c5c400174f1d38a89333de8) |
| 92 | - OpenNext for Cloudflare: [`da4b7fc8c89441a6f96205635fa70e329d1f0df7`](https://github.com/opennextjs/opennextjs-cloudflare/tree/da4b7fc8c89441a6f96205635fa70e329d1f0df7) |