Which project does this relate to?
Start
Describe the bug
prerender.concurrency suggests pages are rendered in parallel, but prerendering sends every page to one preview server (startPreviewServer in start-plugin-core/src/vite/prerender.ts). That server runs SSR in a single JavaScript thread, so concurrency only overlaps I/O waits; the rendering work of each page still happens one page at a time. For sites with many CPU-bound pages, build time grows linearly with page count, and raising concurrency changes nothing.
This isn't adapter-specific. The reproducer uses plain Start with the default Node preview server. With @cloudflare/vite-plugin the preview is one workerd process running one isolate, so the effect is the same.
Real-world impact: in a production app with ~19k prerendered pages (CPU-bound renders from bundled static data, no network I/O), prerendering takes ~110–150s locally at concurrency 1, 2 or 4 alike. In CI (Cloudflare Workers Builds, 4 vCPU) the full build went from ~1 min to ~5 min once all pages were prerendered.
As an experiment I split the same pages across 4 separate preview processes (N workerd instances started against the built dist/server). The output matched the built-in prerenderer's, except for the dehydration timestamp, and prerendering dropped to ~45s, scaling with process count (1 → 2 → 4) up to the machine's performance cores. I'd rather not maintain that workaround, since it depends on internals and unstable APIs, so an official option would be great.
Complete minimal reproducer
https://github.com/R4ULtv/tanstack-start-prerender-concurrency
Steps to Reproduce the Bug
git clone https://github.com/R4ULtv/tanstack-start-prerender-concurrency && cd tanstack-start-prerender-concurrency
npm install
npm run bench. This runs vite build with prerender.concurrency set to 1, 4 and the machine's core count, and prints the time of each build. The app has one route, /page/$id, whose loader does ~15 ms of synchronous CPU work, with 1,000 pages prerendered (PAGE_COUNT to change).
Result on an Apple M-series Mac (10 logical cores, Node 24):
| concurrency |
build time |
| 1 |
21.3s |
| 4 |
20.4s |
| 10 |
19.6s |
Expected behavior
With concurrency > 1 on a multi-core machine, prerendering CPU-bound pages should get faster. For example:
- an option such as
prerender.workers: number that starts N preview servers (worker threads or child processes) and spreads the page queue across them; or, at least,
- documentation stating that
concurrency only overlaps I/O and doesn't parallelize rendering.
Screenshots or Videos
No response
Platform
- Router / Start Version: @tanstack/react-start 1.168.57, @tanstack/react-router 1.170.38
- OS: macOS 15 (Darwin 24.6.0), Apple M-series (4 performance + 6 efficiency cores)
- Browser: N/A (build-time issue)
- Browser Version: N/A
- Bundler: vite
- Bundler Version: 8.3.0
Additional context
Which project does this relate to?
Start
Describe the bug
prerender.concurrencysuggests pages are rendered in parallel, but prerendering sends every page to one preview server (startPreviewServerinstart-plugin-core/src/vite/prerender.ts). That server runs SSR in a single JavaScript thread, soconcurrencyonly overlaps I/O waits; the rendering work of each page still happens one page at a time. For sites with many CPU-bound pages, build time grows linearly with page count, and raisingconcurrencychanges nothing.This isn't adapter-specific. The reproducer uses plain Start with the default Node preview server. With
@cloudflare/vite-pluginthe preview is one workerd process running one isolate, so the effect is the same.Real-world impact: in a production app with ~19k prerendered pages (CPU-bound renders from bundled static data, no network I/O), prerendering takes ~110–150s locally at
concurrency1, 2 or 4 alike. In CI (Cloudflare Workers Builds, 4 vCPU) the full build went from ~1 min to ~5 min once all pages were prerendered.As an experiment I split the same pages across 4 separate preview processes (N workerd instances started against the built
dist/server). The output matched the built-in prerenderer's, except for the dehydration timestamp, and prerendering dropped to ~45s, scaling with process count (1 → 2 → 4) up to the machine's performance cores. I'd rather not maintain that workaround, since it depends on internals and unstable APIs, so an official option would be great.Complete minimal reproducer
https://github.com/R4ULtv/tanstack-start-prerender-concurrency
Steps to Reproduce the Bug
git clone https://github.com/R4ULtv/tanstack-start-prerender-concurrency && cd tanstack-start-prerender-concurrencynpm installnpm run bench. This runsvite buildwithprerender.concurrencyset to 1, 4 and the machine's core count, and prints the time of each build. The app has one route,/page/$id, whose loader does ~15 ms of synchronous CPU work, with 1,000 pages prerendered (PAGE_COUNTto change).Result on an Apple M-series Mac (10 logical cores, Node 24):
Expected behavior
With
concurrency> 1 on a multi-core machine, prerendering CPU-bound pages should get faster. For example:prerender.workers: numberthat starts N preview servers (worker threads or child processes) and spreads the page queue across them; or, at least,concurrencyonly overlaps I/O and doesn't parallelize rendering.Screenshots or Videos
No response
Platform
Additional context
/page/X/→ 307 →/page/X), which doubles the number of requests during prerendering. Possibly related to fix(start): strip trailing slash from prerenderable paths #7070 / Static prerendering: Duplicate crawled/sitemap entries for nested index pages #6978.@cloudflare/vite-plugin, several local workerd instances started side by side hitSQLITE_BUSYon the shared.wrangler/stateuntil persistence was disabled per instance. That may need coordinating with the Cloudflare plugin if several preview servers are started.