Summary
Production client bundles crash during hydration on every route with TypeError: <Class> is not a constructor. The failing class is vinext's own RestorableClientStateController, instantiated by AppBrowserHistoryController at the top level of the browser entry. vinext dev is fine; only vinext build output is broken. Reproduces on 1.0.0-beta.0 and 1.0.0-beta.1. Last working version for us is 0.0.46.
Because the crash happens at module-evaluation time in the client bootstrap, server-rendered HTML still appears but the page never hydrates: static pages look alive, while any client component (ours: an /internal route) freezes on its loading state, and client-side navigation is dead everywhere.
Environment
- vinext:
1.0.0-beta.0 and 1.0.0-beta.1 (both reproduce)
- Bundler: vinext-bundled
vite = npm:@voidzero-dev/vite-plus-core (0.2.1 in beta.0, 0.2.2 in beta.1) — i.e. Rolldown
- Peers:
vite@8.0.10, @vitejs/plugin-rsc@0.5.26, @vitejs/plugin-react@6, react/react-dom@19.2.6
- Target: Cloudflare Workers (
@cloudflare/vite-plugin), App Router
- Node: 24/25, macOS arm64
Error
Minified (beta.1):
TypeError: I is not a constructor
at <client-entry chunk>
With build: { minify: false } the names resolve (beta.0 dist):
TypeError: RestorableClientStateController is not a constructor
at AppBrowserHistoryController (index-*.js) // this.#restorableClientState = new RestorableClientStateController({...})
at <top-level of client entry> (index-*.js) // const historyController = new AppBrowserHistoryController({...})
Root cause — circular chunk dependency
dist/server/app-browser-entry.js runs a top-level side effect:
const historyController = new AppBrowserHistoryController({
initialHistoryState: window.history.state,
maxHistoryStateSnapshots: 50,
...
});
and AppBrowserHistoryController's constructor does this.#restorableClientState = new RestorableClientStateController({ initialHistoryState, maxHistoryStateSnapshots }).
In the production build these two land in separate chunks with a direct cycle:
- the entry/
index chunk contains AppBrowserHistoryController + the top-level new AppBrowserHistoryController(...), and imports RestorableClientStateController from the navigation chunk;
- the
navigation chunk defines RestorableClientStateController and imports back from the index chunk.
Chunk execution enters navigation first; navigation imports index, which runs its top-level new AppBrowserHistoryController(...) before navigation has reached the RestorableClientStateController class definition. So the imported binding is still undefined → "is not a constructor".
0.0.46 (plain Vite/Rollup, no bundled vite-plus) handled this cycle correctly. dev works because native ESM evaluates the dependency before the importer's body. Only the Rolldown production chunking reorders it unsafely.
What I ruled out
- Not minification — reproduces identically with
build.rolldownOptions/build.minify:false.
- Not treeshake side-effects — patching the client build's
getClientTreeshakeConfig() from { moduleSideEffects: "no-external" } to { moduleSideEffects: true } had no effect.
- Not app-fixable — vinext owns
output.codeSplitting, so app-level build.rolldownOptions.output.manualChunks / advancedChunks are ignored ("advancedChunks/manualChunks option is ignored because the codeSplitting option is specified").
Reproduce
- App Router app on
vinext@1.0.0-beta.1, Cloudflare target, Vite 8.
vinext build then vinext start (or deploy).
- Load any route in a browser → console throws
TypeError: … is not a constructor; page never hydrates. vinext dev is unaffected.
- Add
build: { minify: false } to see the real names (RestorableClientStateController / AppBrowserHistoryController).
I haven't reduced this to a from-scratch minimal repo yet (it depends on the app's module graph producing the index↔navigation chunk split), but the failing code is entirely within vinext's own client runtime. Happy to provide a minimal reproduction repo if that helps triage.
Suggested direction
Either keep the interdependent runtime modules (app-browser-entry / app-browser-history-controller / navigation RestorableClientStateController) in one chunk, or avoid the top-level new AppBrowserHistoryController(...) side effect during client-entry evaluation (lazy/first-use init), so a cross-chunk cycle can't observe the class before it's defined.
Summary
Production client bundles crash during hydration on every route with
TypeError: <Class> is not a constructor. The failing class is vinext's ownRestorableClientStateController, instantiated byAppBrowserHistoryControllerat the top level of the browser entry.vinext devis fine; onlyvinext buildoutput is broken. Reproduces on1.0.0-beta.0and1.0.0-beta.1. Last working version for us is0.0.46.Because the crash happens at module-evaluation time in the client bootstrap, server-rendered HTML still appears but the page never hydrates: static pages look alive, while any client component (ours: an
/internalroute) freezes on its loading state, and client-side navigation is dead everywhere.Environment
1.0.0-beta.0and1.0.0-beta.1(both reproduce)vite=npm:@voidzero-dev/vite-plus-core(0.2.1in beta.0,0.2.2in beta.1) — i.e. Rolldownvite@8.0.10,@vitejs/plugin-rsc@0.5.26,@vitejs/plugin-react@6,react/react-dom@19.2.6@cloudflare/vite-plugin), App RouterError
Minified (beta.1):
With
build: { minify: false }the names resolve (beta.0 dist):Root cause — circular chunk dependency
dist/server/app-browser-entry.jsruns a top-level side effect:and
AppBrowserHistoryController's constructor doesthis.#restorableClientState = new RestorableClientStateController({ initialHistoryState, maxHistoryStateSnapshots }).In the production build these two land in separate chunks with a direct cycle:
indexchunk containsAppBrowserHistoryController+ the top-levelnew AppBrowserHistoryController(...), and importsRestorableClientStateControllerfrom thenavigationchunk;navigationchunk definesRestorableClientStateControllerand imports back from theindexchunk.Chunk execution enters
navigationfirst;navigationimportsindex, which runs its top-levelnew AppBrowserHistoryController(...)beforenavigationhas reached theRestorableClientStateControllerclass definition. So the imported binding is stillundefined→ "is not a constructor".0.0.46(plain Vite/Rollup, no bundled vite-plus) handled this cycle correctly.devworks because native ESM evaluates the dependency before the importer's body. Only the Rolldown production chunking reorders it unsafely.What I ruled out
build.rolldownOptions/build.minify:false.getClientTreeshakeConfig()from{ moduleSideEffects: "no-external" }to{ moduleSideEffects: true }had no effect.output.codeSplitting, so app-levelbuild.rolldownOptions.output.manualChunks/advancedChunksare ignored ("advancedChunks/manualChunksoption is ignored because thecodeSplittingoption is specified").Reproduce
vinext@1.0.0-beta.1, Cloudflare target, Vite 8.vinext buildthenvinext start(or deploy).TypeError: … is not a constructor; page never hydrates.vinext devis unaffected.build: { minify: false }to see the real names (RestorableClientStateController/AppBrowserHistoryController).I haven't reduced this to a from-scratch minimal repo yet (it depends on the app's module graph producing the
index↔navigationchunk split), but the failing code is entirely within vinext's own client runtime. Happy to provide a minimal reproduction repo if that helps triage.Suggested direction
Either keep the interdependent runtime modules (
app-browser-entry/app-browser-history-controller/navigationRestorableClientStateController) in one chunk, or avoid the top-levelnew AppBrowserHistoryController(...)side effect during client-entry evaluation (lazy/first-use init), so a cross-chunk cycle can't observe the class before it's defined.