Bundler helper prelude is emitted after its call site in the SSR chunk, so the worker throws __exportAll is not a function
Environment
Environment
- nitro 3.0.260603-beta (also reproduced on 3.0.260610-beta)
- vite 8.2.2
- @tanstack/react-start 1.168.49
- @tanstack/react-router 1.170.32
- react 19.2.8
- wrangler 4.98.0
- bun 1.3.14, node v26.8.1
- macOS arm64
- preset:
nitro({ defaultPreset: "cloudflare-module" }), added to the Vite plugin list forcommand === "build"only
Reproduction
How to reproduce
The trigger is having @clerk/tanstack-react-start in the SSR module graph. On a TanStack Start app that builds and runs correctly:
bun add @clerk/tanstack-react-start(1.5.9)- In
src/routes/__root.tsx, wrap the shell in<ClerkProvider publishableKey="pk_test_probe">. Nothing else. No middleware, no server-side Clerk import, no real keys. rm -rf .output node_modules/.vite && bun run buildwrangler dev --config .output/server/wrangler.jsonand request/
Before step 1 and 2, the same project builds a chunk where the helper is declared at line 11 and used at line 1640, and the worker serves normally. After, the declaration moves below the call site.
I have not reduced this to a fresh scaffold, so I cannot say whether Clerk is the only package that triggers it or whether it is one of a class of module shapes. Clerk is what surfaced it for us.
Comparison: same code built without the nitro plugin
Removing only the nitro(...) entry from the Vite plugin list, and building with TanStack Start alone, produces dist/server/server.js plus dist/client/. In that output the helper is not inlined into the entry chunk. It lives in a shared chunk and is imported:
// dist/server/assets/server-Bqn8B5z-.js
import { U as __exportAll } from "./router-DKPZmNT_.js"dist/server/assets/router-DKPZmNT_.js declares it at line 17 and uses it at line 2843, so the order is correct.
Running that output under wrangler (main: dist/server/server.js, assets: { directory: "dist/client" }, compatibility_flags: ["nodejs_compat"]) works with the same Clerk code in place: / returns a 307 to the app's own normalised search params, and /settings returns 200. No errors.
So the application code and Clerk are fine on Workers. The difference is in how the nitro build step places the helpers.
What I ruled out
- Not caused by stale build output. Reproduced after
rm -rf .output node_modules/.vite. - Not caused by missing or malformed Clerk keys. Reproduced with both keys set, with only one set, and with none.
- Not caused by
clerkMiddleware(). Removing it from the request middleware entirely still reproduces, becauseClerkProvideralone keeps Clerk in the SSR graph. - Not fixed by changing import order in the server entry.
- Not fixed by
build.rollupOptions.output.manualChunksisolating@clerk/*into its own chunk. - Not fixed by nitro 3.0.260610-beta. nitro 3.0.260903-beta fails earlier, during
writeWranglerConfigin_presets.mjs, so I could not test it.
Expected
Either the helper prelude is emitted at the top of the chunk that uses it, or it is imported from a shared chunk the way the plain Vite build does it. A build that produces a worker which throws on its first request should not report success.
Describe the bug
What happens
bun run build succeeds. Running the output with wrangler dev --config .output/server/wrangler.json and requesting / gives:
✘ [ERROR] TypeError: __exportAll is not a function
at null.<anonymous> (.output/server/_ssr/server-DChvPAdW.mjs:1637:38)
[wrangler:info] GET / 500 Internal Server ErrorThe generated chunk is 4504 lines. At line 1637:
var server_exports = /* @__PURE__ */ __exportAll({
createServerEntry: () => createServerEntry,
default: () => server_default
});At line 1712, 75 lines later:
var __exportAll = (all, no_symbols) => {
let target = {};
for (var name in all) __defProp(target, name, {
...Line 1707 onward, in order: var authEnabled, var __defProp, var __getOwnPropDesc, var __getOwnPropNames, var __hasOwnProp, var __exportAll. So the helpers are placed after a piece of application code, not at the top of the chunk.
There is no import of the helper in that chunk:
$ grep -c "import.*__exportAll" .output/server/_ssr/server-DChvPAdW.mjs
0Additional context
Summary
With the cloudflare-module preset, the built server chunk calls __exportAll(...) at line 1637 but declares it at line 1712. Because the declaration uses var, hoisting makes the binding undefined at the point of the call, and the worker throws on the first request. Every request returns 500. The build itself reports success.
The whole helper prelude (__defProp, __getOwnPropDesc, __getOwnPropNames, __hasOwnProp, __exportAll) ends up in the middle of the chunk instead of at the top, directly after application code.
Logs
Source: nitrojs/nitro