nitrojs / nitrojs/nitro

Bundler helper prelude is emitted after its call site in the SSR chunk, so the worker throws __exportAll is not a function

Open
#4,605 1 comment 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

pending triage
Dominant language
TypeScript
Stars
11.2k
Forks
899
Avg merge
2d 24m
Merged PRs (30d)
40

Description

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 for command === "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:

  1. bun add @clerk/tanstack-react-start (1.5.9)
  2. 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.
  3. rm -rf .output node_modules/.vite && bun run build
  4. wrangler dev --config .output/server/wrangler.json and 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, because ClerkProvider alone keeps Clerk in the SSR graph.
  • Not fixed by changing import order in the server entry.
  • Not fixed by build.rollupOptions.output.manualChunks isolating @clerk/* into its own chunk.
  • Not fixed by nitro 3.0.260610-beta. nitro 3.0.260903-beta fails earlier, during writeWranglerConfig in _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 Error

The 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
0
Additional 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

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with Nitro's cloudflare-module preset and Vite plugin path, then inspect .output/server/_ssr/server-DChvPAdW.mjs and compare it with the plain Vite output described in the issue. Trace why __exportAll and its helper prelude are emitted after their call site. Done means the generated worker loads helpers before use or imports them from a shared chunk, and the Wrangler request no longer returns 500.

Written by the indexing model from the issue text.

Assessment

Tech stack
javascript, typescript, vite
Domain
backend, build-system, cloud
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
48/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.