Skip to content

fix(vite-plugin): keep Miniflare alive when a build runs during serve - #14837

Merged
jamesopstad merged 3 commits into
cloudflare:mainfrom
1rgs:fix/vite-plugin-bundled-dev-miniflare-dispose
Jul 27, 2026
Merged

fix(vite-plugin): keep Miniflare alive when a build runs during serve#14837
jamesopstad merged 3 commits into
cloudflare:mainfrom
1rgs:fix/vite-plugin-bundled-dev-miniflare-dispose

Conversation

@1rgs

@1rgs 1rgs commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

What / why

The plugin uses buildEnd as its signal that the dev server is closing, and disposes the Miniflare instance there:

https://github.com/cloudflare/workers-sdk/blob/main/packages/vite-plugin-cloudflare/src/plugins/dev.ts#L58-L69

Vite's experimental.bundledDev runs a Rolldown build pass during serve, so buildEnd fires while the dev server is still live. Miniflare gets torn down mid-serve and every subsequent request fails:

[vite] Internal server error: Expected `miniflare` to be defined
    at get miniflare (@cloudflare/vite-plugin/dist/index.mjs)
    at cloudflarePreMiddleware (@cloudflare/vite-plugin/dist/index.mjs)

With NODE_DEBUG=@cloudflare:vite-plugin the sequence is unambiguous:

Creating new Miniflare instance
Miniflare is ready
buildEnd: disposing        <-- during serve, not on close
... next request -> 500

Reproduces on both the currently pinned 1.37.2 and latest 1.45.1 (with matching wrangler), so it isn't version lag — the dev architecture just assumes a pure module-runner serve rather than a build-during-serve.

Why bundledDev matters here (i.e. why fix this rather than avoid the flag)

Vite's unbundled dev server is optimised for the common case: the browser is on the same machine as the dev server, so one HTTP request per module is essentially free.

Our dev servers aren't local to the browser. Each runs inside a remote sandbox, and the browser reaches it through a Cloudflare Worker and a tunnel. Every module request is a full cross-network round trip, and the app is ~1000 modules — so a cold page load issues ~1000 sequential-ish round trips before it renders. Requests dominate; transform time is noise.

experimental.bundledDev is the direct fix for that shape: Rolldown bundles the graph and the dev server ships a handful of chunks instead of a thousand modules, while keeping HMR. It collapses the request count by orders of magnitude, which is exactly the axis that hurts when the dev server is behind a Worker.

This isn't a niche setup, and it's one Cloudflare users are likely to hit more, not less: remote/cloud dev environments, Codespaces-style sandboxes, and any workflow where the Vite dev server sits behind a Worker or tunnel all have the same profile. Today the combination of @cloudflare/vite-plugin + experimental.bundledDev is unusable — not slow, but 500 on every request — so the flag can't be evaluated at all by anyone using this plugin.

The fix is also small and independent of bundledDev itself: it just stops the plugin from conflating "a build finished" with "the dev server closed". That conflation is arguably wrong regardless of this flag — any plugin or future Vite feature that triggers a build during serve would hit the same teardown.

The change

During serve, dispose Miniflare from a patched server.close instead of from buildEnd. This mirrors the existing server.restart patch in index.ts, so the mechanism should look familiar:

https://github.com/cloudflare/workers-sdk/blob/main/packages/vite-plugin-cloudflare/src/index.ts#L93-L105

Preserved behaviour:

  • production builds — unchanged (buildEnd still disposes when command !== "serve")
  • dev-server restarts — Miniflare still stays warm (the !isRestartingDevServer guard moved with the disposal)
  • forceful exits (ctrl+C) — still covered by the existing exit handler, which the code comments already call out as the reason buildEnd alone was never sufficient

Test

packages/vite-plugin-cloudflare/src/__tests__/bundled-dev-dispose.spec.ts starts a real dev server, invokes the cloudflare plugins' buildEnd hooks (exactly what a bundledDev build pass does), and asserts the server still serves. It fails on main with Expected \miniflare` to be definedand passes with this change. Modeled on the existinghmr-events.spec.ts`.

Driving the hook directly (rather than enabling bundledDev in the fixture) keeps the test deterministic and independent of an experimental Vite flag's internals — it pins the actual invariant: a buildEnd during serve must not dispose Miniflare.

Validation beyond the unit test

Applied the equivalent change to the built plugin inside a large real app (~1000 modules, React + TanStack Router, Worker + SPA) and ran it with experimental.bundledDev enabled:

before after
GET / 500 Expected \miniflare` to be defined` 200, real app HTML
GET /src/main.tsx 500 200
repeated requests 500 stable 200

Unrelated note in case it's useful to maintainers: with bundledDev there's also a separate non-fatal TypeError: Cannot read properties of undefined (reading 'config') from @vitejs/plugin-react-swc's transformIndexHtml — different repo, and it doesn't block serving once this fix is in.

Changeset

Patch for @cloudflare/vite-plugin.


  • Tests
    • Tests included/updated
    • Automated tests not possible - manual testing has been completed as follows:
    • Additional testing not necessary because:
  • Public documentation
    • Cloudflare docs PR(s):
    • Documentation not necessary because: bug fix

Vite's experimental.bundledDev runs a Rolldown build pass during serve,
firing buildEnd while the dev server is still live. The plugin treated
buildEnd as the dev-server-closing signal and disposed Miniflare there,
so the next request failed with 'Expected `miniflare` to be defined'.

Dispose Miniflare from a patched server.close during serve (mirroring
the existing server.restart patch) rather than from buildEnd. Production
builds, dev-server restarts (Miniflare stays warm), and forceful ctrl+C
exits are unaffected.

Validated against a large real app (Ramp Penny chat, ~1000 modules):
bundledDev serve went from 500 (miniflare disposed) to 200 with the
module graph loading normally.
@1rgs
1rgs requested a review from workers-devprod as a code owner July 24, 2026 17:38
@changeset-bot

changeset-bot Bot commented Jul 24, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: a2d11e7

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@cloudflare/vite-plugin Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@workers-devprod
workers-devprod requested review from a team and NuroDev and removed request for a team July 24, 2026 17:38
@workers-devprod

workers-devprod commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

Codeowners approval required for this PR:

  • ✅ @cloudflare/wrangler
Show detailed file reviewers

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Devin Review: No Issues Found

Devin Review analyzed this PR and found no potential bugs to report.

View in Devin Review to see 2 additional findings.

Open in Devin Review

@pkg-pr-new

pkg-pr-new Bot commented Jul 26, 2026

Copy link
Copy Markdown
@cloudflare/autoconfig

npm i https://pkg.pr.new/@cloudflare/autoconfig@14837

@cloudflare/config

npm i https://pkg.pr.new/@cloudflare/config@14837

create-cloudflare

npm i https://pkg.pr.new/create-cloudflare@14837

@cloudflare/deploy-helpers

npm i https://pkg.pr.new/@cloudflare/deploy-helpers@14837

@cloudflare/kv-asset-handler

npm i https://pkg.pr.new/@cloudflare/kv-asset-handler@14837

miniflare

npm i https://pkg.pr.new/miniflare@14837

@cloudflare/pages-shared

npm i https://pkg.pr.new/@cloudflare/pages-shared@14837

@cloudflare/unenv-preset

npm i https://pkg.pr.new/@cloudflare/unenv-preset@14837

@cloudflare/vite-plugin

npm i https://pkg.pr.new/@cloudflare/vite-plugin@14837

@cloudflare/vitest-pool-workers

npm i https://pkg.pr.new/@cloudflare/vitest-pool-workers@14837

@cloudflare/workers-auth

npm i https://pkg.pr.new/@cloudflare/workers-auth@14837

@cloudflare/workers-editor-shared

npm i https://pkg.pr.new/@cloudflare/workers-editor-shared@14837

@cloudflare/workers-utils

npm i https://pkg.pr.new/@cloudflare/workers-utils@14837

wrangler

npm i https://pkg.pr.new/wrangler@14837

commit: a2d11e7

@jamesopstad
jamesopstad requested review from jamesopstad and removed request for NuroDev July 27, 2026 07:31

@jamesopstad jamesopstad left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the PR! I'm working on getting some new APIs added in Vite that would enable us to remove the server patching (vitejs/vite#22913) but this is a good workaround for now. I pushed a couple of commits.

We also need to ensure that HTML files are served correctly in experimental.bundledDev. I'll try and get a PR up for that today.

@workers-devprod workers-devprod left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Codeowners reviews satisfied

@github-project-automation github-project-automation Bot moved this from Untriaged to Approved in workers-sdk Jul 27, 2026
@jamesopstad
jamesopstad merged commit de6a951 into cloudflare:main Jul 27, 2026
80 of 84 checks passed
@github-project-automation github-project-automation Bot moved this from Approved to Done in workers-sdk Jul 27, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

3 participants