Skip to content

Start + Nitro on Vite 8.2: SSR service chunk re-exports an undeclared namespace (ssr_exports) → every request 500s while vite build exits 0 #8031

Description

@VSydorenko

Summary

On Vite 8.2.x (which pulls Rolldown 1.2.2+), the Nitro pass emits an SSR
service chunk that re-exports a namespace binding it never declares. Node
throws while compiling the module, so every single request 500s — including
the login page — with no failure at build time.

There is a second, opposite failure on Rolldown 1.1.2 … 1.2.1, where the SPA
prerender step dies with TypeError: createServerFn is not a function. The two
are mutually exclusive across versions, so no single Rolldown version is green
for both an SSR app and a prerendered SPA app in the same monorepo.

I believe the root cause is in Rolldown chunking, not in Start — filing here
because the failure is only reachable through the Start + Nitro pipeline, and
because the second symptom is one of your own exports. Happy to move this to
rolldown/rolldown if you prefer.

Symptom 1 — ssr_exports is not defined (Vite 8.2.x / Rolldown 1.2.2+)

Runtime error on every request:

SyntaxError: Export 'ssr_exports' is not defined in module
    at compileSourceTextModule (node:internal/modules/esm/utils:318:16)
    at ModuleLoader.moduleStrategy (node:internal/modules/esm/translators:89:18)
    …
  status: 500,
  unhandled: true

The offending chunk, .output/server/_ssr/ssr2.mjs, ends with:

export { ssr_exports as a, getRequestHeader as c, createServerEntry,
         requestHandler as d, server_default as default, /* … */ };

ssr_exports is neither declared in this module nor imported by it — the
only other __exportAll namespace in the same file (server_exports) is
declared, two lines above:

var server_exports = /* @__PURE__ */ __exportAll({ getResponseHeaders: () => getResponseHeaders });
//#endregion
export { ssr_exports as a, /* … */ };   // ← ssr_exports never declared

This is not a dead export. It is the namespace Nitro loads as the SSR service:

// .output/server/_chunks/ssr-renderer.mjs
var viteServices = {
  ["ssr"]: lazyService(() => import("../_ssr/ssr2.mjs").then((n) => n.a))
};

n.a is exactly ssr_exports, so the SSR renderer can never load.

Two more details that seem relevant to diagnosing it:

  1. The intermediate Vite output is clean. I grepped
    node_modules/.nitro/vite/services/ssr/ (the index.js entry plus its
    assets/): zero occurrences of ssr_exports. Other namespaces
    (server_exports, request_helpers_exports) are declared normally there.
    The undeclared binding is introduced by the Nitro re-bundle pass.

  2. The service entry gets split into two mutually-importing chunks.
    A working app emits a single _ssr/ssr.mjs that declares ssr_exports; the
    broken one emits _ssr/ssr.mjs and _ssr/ssr2.mjs which import from each
    other:

    // ssr.mjs
    import { d as requestHandler, u as getResponse } from "./ssr2.mjs";
    // ssr2.mjs
    import { a as getServerFnById, i as createServerFn, /* … */ } from "./ssr.mjs";

Rolldown 1.2.2's release notes list "support inlining of shared deps in
dynamic entries"
— and the SSR service is loaded through exactly such a dynamic
entry, which is my best guess at the trigger.

Symptom 2 — createServerFn is not a function (Rolldown 1.1.2 … 1.2.1)

Pinning Rolldown down to escape symptom 1 hits the known chunking regression
instead. Build exits 0, but prerender produces nothing:

[prerender] Prerendered 0 pages:
[unhandledRejection] Error: Failed to fetch /: Internal Server Error
  cause: TypeError: createServerFn is not a function
      at .output/server/_ssr/ssr.mjs:232:28

This matches rolldown#9993.
Note that issue is closed (2026-07-05) but a commenter confirmed five days
later that it still reproduces on rolldown@1.1.5 — "same failure class, but a
graph shape the cycle check in #10101 still misses". Both symptoms sit under the
rolldown#10008 chunking
umbrella (category correctness/cross-chunk-init-binding).

Measured version matrix

One SSR app (prerender: { enabled: false }, per-route SSR) and two prerendered
SPA apps, same monorepo, full vite build per row:

rolldown SSR app prerendered SPA app
1.1.2 / 1.1.5 / 1.2.0 / 1.2.1 createServerFn is not a function
1.2.2 / 1.2.3 ssr_exports
1.1.0 + vite 8.1.5

Things that did not help, in case they save someone time:

  • Aligning versions so Vite and Nitro share one Rolldown (overrides: { rolldown: 1.2.3 }).
    Note both are present by default: Vite 8.2.1 pulls rolldown@1.2.3 while
    nitro@3.0.260610-beta depends on rolldown@^1.1.0.
  • Downgrading Nitro to 3.0.260522-beta.
  • Bumping @tanstack/start-plugin-core to 1.171.33.
  • Disabling the React Compiler babel pass.

Reproduction

I do not have a minimal isolated repro — the failure only appears at real
module-graph size (the app is ~300 server modules; a toy Start app does not
trigger it). Same caveat as rolldown#10228, where the reporter also could not
get below ~500 modules. What reproduces reliably in our app:

pnpm --filter <ssr-app> build     # exits 0
node --check .output/server/_ssr/ssr2.mjs
# SyntaxError: Export 'ssr_exports' is not defined in module

I'm glad to run experiments against a patched build or dump any intermediate
artifact if that helps narrow it.

The part that worries me most

vite build exits 0 on a bundle that cannot serve a single request. This is
not a syntax error in the file, so nothing surfaces until Node compiles the
module at request time. Our CI was green, the deploy succeeded, and production
returned 500 on every route until a human noticed.

We now gate builds with a node --check sweep over every emitted server module
(~1.7s for 988 modules), which catches this class immediately:

ESM output check passed: 183 server modules link cleanly.

Something equivalent inside Start's build (or a documented recommendation) would
have turned a production outage into a failed build.

Environment

@tanstack/react-start        1.168.40
@tanstack/start-plugin-core  1.171.31 (also tested 1.171.33)
@tanstack/react-router       1.170.23
nitro                        3.0.260610-beta (also tested 3.0.260522-beta)
vite                         8.2.1 (broken) / 8.1.5 (works)
rolldown                     see matrix above
react / react-dom            19.2.8
node                         24.15.0
pnpm                         11.4.0
preset                       node-server (same failure on the Vercel preset)

Ask

  1. Is pinning vite@8.1.5 + rolldown@1.1.0 the right stopgap for Start users
    today, or is there a supported combination on Vite 8.2 that I missed?
  2. Would you consider carrying a known-good Rolldown range for Start, given that
    neither side of the matrix is green for mixed SSR + prerender monorepos?
  3. Is a link-validity check on emitted server chunks something Start would take?
    Happy to open a PR.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions