Skip to content

fix(build): stub cloudflare:* imports while prerendering in Node (#3319) - #3332

Open
Divkix wants to merge 1 commit into
cloudflare:mainfrom
Divkix:fix/3319-prerender-cloudflare-imports
Open

Divkix wants to merge 1 commit into
cloudflare:mainfrom
Divkix:fix/3319-prerender-cloudflare-imports

Conversation

@Divkix

@Divkix Divkix commented Sep 20, 2026

Copy link
Copy Markdown
Contributor

Fixes #3319

Summary

vinext build --prerender-all (and vinext-cloudflare deploy --prerender-all) renders routes by starting a Node production server against the built output. A Cloudflare target builds a Worker bundle that keeps cloudflare:* imports external - only workerd resolves them - so importing that entry under Node died with ERR_UNSUPPORTED_ESM_URL_SCHEME before a single route rendered, even for apps whose prerendered routes never touch a binding.

  • Prerender-purpose servers now register node:module loader hooks that resolve every cloudflare:* specifier to one inert stub module: the scoped helpers run their callback, waitUntil is dropped, and reading env/exports/cache/tracing throws a message naming the cause, so a binding-dependent route fails per-route instead of taking the whole build down.
  • Serving servers are untouched and the deployed bundle still imports the real modules - the hooks live only in the Node prerender process (all five prerender entry points already pass purpose: "prerender").
  • Chose stubbing over executing the prerun under workerd/miniflare (both options are listed in the issue): stubbing keeps the existing Node harness that per-route static generation already uses and adds no new runtime dependency, while still failing loudly for routes that genuinely need bindings.

Test plan

  • tests/prerender-workerd-stubs.test.ts - drives the loader hooks and the stub surface directly (resolve/load short-circuit, binding reads throw, probes stay inert, exported names).
  • tests/prerender-cloudflare-bindings.test.ts - runs the real CLI (vinext build --prerender-all) against tests/fixtures/cf-prerender-bindings, whose server graph imports cloudflare:workers; asserts the prerendered routes exist and that dist/server/index.js still keeps the import external.
  • Local CI-equivalent run: vp run build, vp run check, vp run knip, bash syntax + shard manifest, vitest unit + integration, response-store e2e, create-next-app/create-vinext-app jobs, and the Playwright projects. The only local failures were a build-hash-dependent action-id assertion (fails with and without this change) and e2e specs whose fixed ports were held by another worktree on the same machine.

…udflare#3319)

`vinext build --prerender-all` (and `vinext-cloudflare deploy --prerender-all`)
renders routes by starting a Node production server against the built output.
A Cloudflare target builds a Worker bundle that keeps `cloudflare:*` imports
external - only workerd resolves them - so importing that entry under Node died
with ERR_UNSUPPORTED_ESM_URL_SCHEME before a single route rendered, even for
apps whose prerendered routes never touch a binding.

Prerender-purpose servers now register `node:module` loader hooks that resolve
every `cloudflare:*` specifier to one inert stub module: the scoped helpers run
their callback, `waitUntil` is dropped, and reading `env`/`exports`/`cache`/
`tracing` throws a message naming the cause, so a binding-dependent route fails
per-route instead of taking the whole build down. Serving servers are untouched
and the deployed bundle still imports the real modules.

Tests:
- `tests/prerender-workerd-stubs.test.ts` drives the loader hooks and the stub
  surface directly.
- `tests/prerender-cloudflare-bindings.test.ts` runs the real CLI
  (`vinext build --prerender-all`) against a Cloudflare fixture whose server
  graph imports `cloudflare:workers`, then asserts the prerendered routes exist
  and the built bundle still keeps the import external.
@pkg-pr-new

pkg-pr-new Bot commented Sep 20, 2026

Copy link
Copy Markdown

Open in StackBlitz

@vinext/cloudflare

npm i https://pkg.pr.new/@vinext/cloudflare@3332

create-vinext-app

npm i https://pkg.pr.new/create-vinext-app@3332

@vinext/types

npm i https://pkg.pr.new/@vinext/types@3332

vinext

npm i https://pkg.pr.new/vinext@3332

@cloudflare/workers-response-store

npm i https://pkg.pr.new/@cloudflare/workers-response-store@3332

commit: 6fcbd14

@github-actions

Copy link
Copy Markdown
Contributor

Performance benchmarks

Compared 6fcbd14 against base e97de29 using alternating same-runner rounds. Next.js was unchanged and skipped.

0 improved · 0 regressed · 6 within ±1.5%

Scenario Framework Baseline Current Change
Client bundle size (gzip) vinext 142.2 KB 142.2 KB ⚫ +0.0%
Client entry size (gzip) vinext 129.6 KB 129.6 KB ⚫ +0.0%
Dev server cold start vinext 3.12 s 3.13 s ⚫ +0.2%
Production build time vinext 3.64 s 3.63 s ⚫ -0.1%
RSC entry closure size (gzip) vinext 121.0 KB 121.0 KB ⚫ -0.0%
Server bundle size (gzip) vinext 218.3 KB 218.3 KB ⚫ +0.0%

View detailed results and traces

🟢 improvement · 🔴 regression · ⚫ change below 1.5% · paired base/head

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.

build --prerender-all crashes on cloudflare:* imports (ERR_UNSUPPORTED_ESM_URL_SCHEME)

1 participant