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
git clone https://github.com/childrentime/tanstack-start-rsbuild-ssr-chunk-duplication && cd tanstack-start-rsbuild-ssr-chunk-duplication && pnpm install
pnpm build && pnpm check → Widget7 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)
pnpm build:fixed && pnpm check (same config + environments.ssr.splitChunks: { chunks: 'all', minSize: 0 }) → Widget7 source appears in 1 server chunk(s)
- 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.
Which project does this relate to?
Start
Describe the bug
With
@tanstack/react-start/plugin/rsbuild, the server (ssrenvironment) build emits a separate copy of every shared module into every route chunk that imports it. Only the client environment gets asplitChunksconfig from the plugin (performance.chunkSplit: { strategy: 'custom', override: { chunks: 'async' } }); thessrenvironment gets none, and Rsbuild turnsoptimization.splitChunksoff fortarget: 'node'when nosplitChunksconfig is present (rsbuild:split-chunksplugin: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 increateRsbuildEnvironmentPlan(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):
environments.ssr.splitChunks: { chunks: 'all', minSize: 0 }dist/server)rsbuild buildcold / peak memoryrsbuild devmemory (whole-session mean / after 40 HMR edits)Client output is unaffected (shared modules above
minSizeare deduped by the clientsplitChunks), 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
ssrenvironment tosplitChunks: { chunks: 'all', minSize: 0 }(or a single server file), while still letting users override it viaenvironments.ssr.Rsbuild-native fix that works today:
(
chunks: 'all'alone is not enough because of the defaultminSize: 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
git clone https://github.com/childrentime/tanstack-start-rsbuild-ssr-chunk-duplication && cd tanstack-start-rsbuild-ssr-chunk-duplication && pnpm installpnpm build && pnpm check→Widget7 source appears in 10 server chunk(s) <-- duplicated(60 routes import 6 of 40 shared components each; the marker is a comment insrc/components/Widget7.tsx)pnpm build:fixed && pnpm check(same config +environments.ssr.splitChunks: { chunks: 'all', minSize: 0 }) →Widget7 source appears in 1 server chunk(s)ROUTES=300 COMPONENTS=400 pnpm gen && pnpm build && du -sh dist/serverto 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
ssrenvironment'ssplitChunks.Screenshots or Videos
No response
Platform
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.