Skip to content

[Start + Rsbuild] ssr environment has no splitChunks config → every shared module is duplicated into every server route chunk (291 MB server dist / 4-6 GB dev memory at 300 routes) #8113

Description

@childrentime

Which project does this relate to?

Start

Describe the bug

With @tanstack/react-start/plugin/rsbuild, the server (ssr environment) build emits a separate copy of every shared module into every route chunk that imports it. Only the client environment gets a splitChunks config from the plugin (performance.chunkSplit: { strategy: 'custom', override: { chunks: 'async' } }); the ssr environment gets none, and Rsbuild turns optimization.splitChunks off for target: 'node' when no splitChunks config is present (rsbuild:split-chunks plugin: if (isServer) { if (splitChunks === false || Object.keys(splitChunks).length === 0) chain.optimization.splitChunks(false) }).

The RSC path already sets splitChunks: { preset: 'single-vendor' } for the server environment in createRsbuildEnvironmentPlan (start-plugin-core/src/rsbuild/planning.ts), but the non-RSC path sets nothing.

Effect on a real-sized app (same source, 300 file routes + 400 shared components, react-start 1.168.47, measured on an M5 MacBook, physical footprint):

Rsbuild default Rsbuild + environments.ssr.splitChunks: { chunks: 'all', minSize: 0 } Vite (same source)
server dist (dist/server) 291 MB (311 files, each route chunk ~1 MB) 4 MB 1.4 MB
prod: memory after visiting all 300 routes 463 MB (JS heap 348 MB) 130 MB 147 MB
prod: avg first hit per route 7.2 ms 1.4 ms 2.0 ms
rsbuild build cold / peak memory 5.5 s / 1.9 GB 4.9 s / 1.4 GB 3.1 s / 0.7 GB
rsbuild dev memory (whole-session mean / after 40 HMR edits) 4.4 GB / 5.9 GB 1.4 GB / 1.7 GB 1.0 GB / 0.8 GB
dev HMR of a component imported by many routes (p50) 7.1 s 0.8 s 44 ms
dev HMR root component (p50) 938 ms 742 ms 39 ms
prod QPS unchanged unchanged

Client output is unaffected (shared modules above minSize are deduped by the client splitChunks), and module singletons still work at runtime (the webpack runtime caches modules by id, so a shared module's state is not duplicated even though its source is) — the cost is disk, parse/load time and, in dev, memory and rebuild time, all scaling with the number of routes.

Since a server bundle pays no per-request cost for extra chunks, it would make sense for the plugin to default the ssr environment to splitChunks: { chunks: 'all', minSize: 0 } (or a single server file), while still letting users override it via environments.ssr.

Rsbuild-native fix that works today:

// rsbuild.config.ts
export default defineConfig({
  plugins: [pluginReact(), tanstackStart()],
  environments: {
    ssr: { splitChunks: { chunks: 'all', minSize: 0 } },
  },
})

(chunks: 'all' alone is not enough because of the default minSize: 20000 — small shared modules are still inlined into every route chunk; preset: 'single-vendor' only dedupes node_modules.)

Complete minimal reproducer

https://github.com/childrentime/tanstack-start-rsbuild-ssr-chunk-duplication

Steps to Reproduce the Bug

  1. git clone https://github.com/childrentime/tanstack-start-rsbuild-ssr-chunk-duplication && cd tanstack-start-rsbuild-ssr-chunk-duplication && pnpm install
  2. pnpm build && pnpm checkWidget7 source appears in 10 server chunk(s) <-- duplicated (60 routes import 6 of 40 shared components each; the marker is a comment in src/components/Widget7.tsx)
  3. pnpm build:fixed && pnpm check (same config + environments.ssr.splitChunks: { chunks: 'all', minSize: 0 }) → Widget7 source appears in 1 server chunk(s)
  4. Optional: ROUTES=300 COMPONENTS=400 pnpm gen && pnpm build && du -sh dist/server to see it grow with route count.

Expected behavior

As a user, I expected the server build to emit each shared module once (like the client build and like the Vite adapter do), instead of once per route chunk that imports it — or at least a documented default for the ssr environment's splitChunks.

Screenshots or Videos

No response

Platform

  • Router / Start Version: @tanstack/react-start 1.168.47 (@tanstack/start-plugin-core 1.171.37, @tanstack/react-router 1.170.30)
  • OS: macOS 26.5.1 (Apple M5), Node 22.12.0
  • Browser: n/a (build output)
  • Browser Version: n/a
  • Bundler: rsbuild
  • Bundler Version: @rsbuild/core 2.1.13 (@rspack/core 2.1.10), @rsbuild/plugin-react 2.1.0

Additional context

Found while benchmarking the Vite vs Rsbuild adapters on the same Start app; happy to share the full benchmark harness/results if useful. The RSC code path (splitChunks: { preset: 'single-vendor' } for the server env) suggests configuring server chunking is already considered in scope for the plugin.

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