I saw this benchmark/tweet and tried benchmarking vinext. There are very clear improvement opportunities for us here.
What This Tests
The benchmark mostly tests dynamic page SSR under load.
For vinext/Next.js, that means:
route match -> build RSC tree -> render Flight -> SSR consumes Flight -> HTML
It does not meaningfully test API routes, middleware, static assets, hydration, or client navigation.
Local Docker Harness
I added a local Docker Compose harness as a closer middle ground to the Platformatic setup:
- 6 app containers behind nginx
- k6 in a separate container
- repeated runs with randomized framework order
- warmup before each measured run
- median summary across runs
- reports completed app iterations/sec, not raw HTTP requests/sec
- same benchmark route mix
This is still not apples-to-apples with Platformatic EKS. Docker Desktop shares CPU/networking with the load generator. Treat this as a local saturation signal, not publishable framework ranking.
Versions:
- Next.js
16.2.4, React 19.2.5
- React Router
7.14.2, React 19.2.5, Vite 8.0.10
- TanStack Start
1.167.50, TanStack Router 1.168.25, React 19.2.5, Vite 8.0.10
- vinext latest local build from
/Users/nathan/Projects/vinext, React 19.2.5, Vite 8.0.10
Result: 1000 rps Baseline
One corrected baseline run at 1000 rps, 6 replicas, 120s measured duration:
| Framework |
Success Rate |
Achieved rps |
Avg Latency |
Median |
p99 |
Dropped |
| TanStack Start |
100.0% |
999.9 |
9ms |
9ms |
20ms |
0 |
| React Router |
100.0% |
999.9 |
10ms |
9ms |
20ms |
0 |
| Next.js |
100.0% |
999.9 |
13ms |
11ms |
47ms |
0 |
| vinext |
100.0% |
993.7 |
351ms |
45ms |
11.58s |
705 |
Interpretation: 1000 rps is too easy for React Router, TanStack, and Next in this local harness. It already stresses vinext: completed requests return 200, but tail latency spikes and k6 drops scheduled iterations.
Result: 2000 rps Saturation
Three corrected randomized-order runs at 2000 rps, 6 replicas, 120s measured duration:
| Framework |
Runs |
Success Rate |
Achieved rps |
Avg Latency |
Median |
p99 |
Dropped |
| React Router |
3 |
100.0% |
1999.7 |
13ms |
11ms |
43ms |
0 |
| TanStack Start |
3 |
100.0% |
1999.6 |
16ms |
12ms |
78ms |
0 |
| Next.js |
3 |
100.0% |
1163.7 |
3.71s |
648ms |
33.76s |
84,525 |
| vinext |
3 |
100.0% |
975.5 |
4.52s |
884ms |
37.26s |
109,679 |
Randomized order:
Round 1: react-router next tanstack vinext
Round 2: next tanstack vinext react-router
Round 3: react-router vinext next tanstack
Interpretation:
- React Router and TanStack remain clean at
2000 rps.
- Next.js saturates around ~
1.16k rps in this local harness.
- vinext saturates lower, around ~
975 rps, with worse tail latency and more dropped iterations.
- The useful signal is not the absolute numbers. The useful signal is the shape: vinext behaves like the expensive dynamic App Router SSR path and falls over well before loader-style frameworks.
Harness files live in .docker/ and docker-compose.local-bench.yml in the local benchmark checkout.
Likely vinext Bottleneck
The first suspicious vinext-specific cost is duplicate work in the dynamic App Router page path:
probe page/layouts
-> await async page work when there is no loading.tsx
-> real RSC render executes the page again
-> SSR consumes the Flight stream
-> embed Flight back into HTML
Files to inspect:
packages/vinext/src/server/app-page-render.ts - always calls probeAppPageBeforeRender() before render
packages/vinext/src/server/app-page-probe.ts - probes layouts and page before render
packages/vinext/src/server/app-page-execution.ts - awaits async page probe results when no loading boundary exists
packages/vinext/src/server/app-ssr-entry.ts - tees Flight and feeds SSR/html embedding
Done When
- Add timing breakdowns for route match, probe, RSC render, SSR render, and streaming.
- Confirm whether benchmark page data work executes once or twice per request.
- Remove or bypass avoidable probe work for normal
force-dynamic page SSR if compatible.
- Re-run the benchmark port locally and then in an apples-to-apples EKS setup.
I saw this benchmark/tweet and tried benchmarking vinext. There are very clear improvement opportunities for us here.
What This Tests
The benchmark mostly tests dynamic page SSR under load.
For vinext/Next.js, that means:
It does not meaningfully test API routes, middleware, static assets, hydration, or client navigation.
Local Docker Harness
I added a local Docker Compose harness as a closer middle ground to the Platformatic setup:
This is still not apples-to-apples with Platformatic EKS. Docker Desktop shares CPU/networking with the load generator. Treat this as a local saturation signal, not publishable framework ranking.
Versions:
16.2.4, React19.2.57.14.2, React19.2.5, Vite8.0.101.167.50, TanStack Router1.168.25, React19.2.5, Vite8.0.10/Users/nathan/Projects/vinext, React19.2.5, Vite8.0.10Result: 1000 rps Baseline
One corrected baseline run at
1000 rps, 6 replicas, 120s measured duration:Interpretation:
1000 rpsis too easy for React Router, TanStack, and Next in this local harness. It already stresses vinext: completed requests return 200, but tail latency spikes and k6 drops scheduled iterations.Result: 2000 rps Saturation
Three corrected randomized-order runs at
2000 rps, 6 replicas, 120s measured duration:Randomized order:
Interpretation:
2000 rps.1.16k rpsin this local harness.975 rps, with worse tail latency and more dropped iterations.Harness files live in
.docker/anddocker-compose.local-bench.ymlin the local benchmark checkout.Likely vinext Bottleneck
The first suspicious vinext-specific cost is duplicate work in the dynamic App Router page path:
Files to inspect:
packages/vinext/src/server/app-page-render.ts- always callsprobeAppPageBeforeRender()before renderpackages/vinext/src/server/app-page-probe.ts- probes layouts and page before renderpackages/vinext/src/server/app-page-execution.ts- awaits async page probe results when no loading boundary existspackages/vinext/src/server/app-ssr-entry.ts- tees Flight and feeds SSR/html embeddingDone When
force-dynamicpage SSR if compatible.