Run Time Stats
Client Side Rendered Tests
First Paint (ms)
First Contentful Paint (ms)
Interaction to Next Paint (ms)
| Framework | First Paint | FCP | INP |
|---|---|---|---|
| Astro | 92.4ms | 92.35ms | 3.29ms |
| Next.js | 349.4ms | 349.17ms | 21.18ms |
| Nuxt | 157.6ms | 157.64ms | 10.1ms |
| React Router | 141.6ms | 141.68ms | 17.06ms |
| SolidStart | 103ms | 102.86ms | 13.95ms |
| SvelteKit | 107.4ms | 107.49ms | 10.22ms |
| TanStack Start | 165.6ms | 165.46ms | 28.38ms |
Methodology
- Each framework renders a table of 1000 rows with two UUID columns
- Measured using Lighthouse flow with Chromium via Puppeteer for accurate browser metrics
- First Paint and First Contentful Paint are measured on initial navigation
- Interaction to Next Paint is measured by clicking the first row's detail link
- Benchmarks run 5 times and results are averaged
-
Next.js wraps the client-side rendered table in a
dynamicimport withssr: falseto prevent build-time prerendering - TanStack Start, Nuxt, SvelteKit, and SolidStart disable SSR per-route
-
React Router uses route-level
clientLoaderfunctions withHydrateFallbackso the client-rendered routes are not server-rendered - Astro uses client-only React islands for client-side rendered routes
- Client-side rendered tests use each framework's normal production build because SPA-only build modes are not supported consistently across the frameworks being compared
-
Astro uses React for its client-side rendered test: the benchmark table and
detail components are React islands rendered with
client:only="react", which prevents Astro from server-rendering those components and lets them render only in the browser. Astro'sClientRouteris not used for this CSR test because it enables client-side transitions and soft navigation behavior rather than client-only rendering.
Server Side Rendered Tests
First Paint (ms)
First Contentful Paint (ms)
Interaction to Next Paint (ms)
| Framework | First Paint | FCP | INP |
|---|---|---|---|
| Astro | 70.2ms | 70.15ms | 7.68ms |
| Next.js | 146.4ms | 146.37ms | 18.46ms |
| Nuxt | 94.2ms | 94.19ms | 11.37ms |
| React Router | 89.4ms | 89.21ms | 18.54ms |
| SolidStart | 105.4ms | 105.41ms | 13.47ms |
| SvelteKit | 79.4ms | 79.36ms | 16.55ms |
| TanStack Start | 117ms | 116.9ms | 30.27ms |
Methodology
- Each framework renders a table of 1000 rows with two UUID columns
- Measured using Lighthouse flow with Chromium via Puppeteer for accurate browser metrics
- First Paint and First Contentful Paint are measured on initial navigation
- Interaction to Next Paint is measured by clicking the first row's detail link
- Benchmarks run 5 times and results are averaged
-
The measured route is
/server-side-rendered, and detail navigation uses/server-side-rendered/:id.
Server Side Throughput Tests
Ops/sec
Body Size
Median Latency
| Framework | Ops/sec | Median Latency | Body Size | Duplication |
|---|---|---|---|---|
| Baseline HTML | 625 | 1.588ms | 184.7kb | 1.5x |
| Astro | 385 | 2.527ms | 190.65kb | 1.5x |
| Mastro | 515 | 1.912ms | 181.95kb | 1x |
| Next.js | 41 | 24.072ms | 579.39kb | 3.49x |
| Nuxt | 65 | 13.053ms | 298.03kb | 2.5x |
| React Router | 156 | 6.36ms | 320.07kb | 2.5x |
| SolidStart | 65 | 13.307ms | 381.88kb | 2.5x |
| SvelteKit | 356 | 2.686ms | 271.38kb | 2.5x |
| TanStack Start | 43 | 23.043ms | 281.55kb | 2.5x |
Methodology
- Each framework renders a table of 1000 rows with two UUID columns
- Mock HTTP requests bypass TCP overhead for accurate rendering measurement
- Data is loaded asynchronously to simulate real-world data fetching
- Duplication factor indicates how many times each UUID appears in the response (1x = optimal, 2x = includes hydration payload)
- Benchmarks run for 10 seconds using tinybench
-
Astro, Nuxt, and SvelteKit handle Node.js HTTP requests natively. React
Router, SolidStart, and TanStack Start use Web APIs internally, so
benchmarks include the cost of their Node.js adapter layers (
@react-router/node, h3, and srvx respectively) -
Next.js defaults to React Server Components (RSC), a different rendering
model than traditional server-rendered React. To keep the comparison fair,
Next.js uses
"use client"to opt out of RSC and use traditional server rendering + hydration like most of the other frameworks - Inspired by eknkc/ssr-benchmark
Server Side Load Test
P99 Latency
P99 Latency at 25 Connections
P99 Latency at 50 Connections
P99 Latency at 100 Connections
P90 Latency
P90 Latency at 25 Connections
P90 Latency at 50 Connections
P90 Latency at 100 Connections
| Framework | Peak req/s | Peak Connections | P99 @ 25 | P99 @ 50 | P99 @ 100 | Total Req. |
|---|---|---|---|---|---|---|
| Baseline HTML | 1,619.6 | 50 | 19ms | 41ms | 98ms | 48,121 |
| Astro | 686.6 | 25 | 44ms | 122ms | 1984ms | 20,376 |
| Next.js | 38 | 5 | 2169ms | 4680ms | 4751ms | 1,037 |
| Nuxt | 68.8 | 5 | 2052ms | 4121ms | 4365ms | 2,151 |
| React Router | 177.2 | 10 | 298ms | 1655ms | 3947ms | 5,823 |
| SolidStart | 63 | 10 | 3190ms | 3736ms | 4146ms | 2,031 |
| SvelteKit | 492 | 25 | 80ms | 407ms | 2452ms | 15,637 |
| TanStack Start | 44 | 5 | 2919ms | 4495ms | 4537ms | 1,345 |
Methodology
- Each framework serves the server-rendered table route over a real local HTTP server
-
The measured route is
/server-side-rendered, using the same 1000-row UUID table as the SSR request throughput and browser rendering tests - Load is applied in staged connection counts, from 1 through 200 concurrent connections, with each stage running for approximately 5 seconds
- Peak requests/sec is the highest successful stage throughput observed during the staged run
- P90 and P99 latency are compared at the 25-, 50-, and 100-connection stages for every framework, so latency is measured under the same concurrency pressure
- Total requests cover the full staged load run, not only the peak stage
Core Web Vitals Desktop
Good Largest Contentful Paint
Measures how fast a page's main content loads. To provide a good user experience, the LCP should be 2.5 seconds or less.
Good Cumulative Layout Shift
Measures how much and how far content unexpectedly moves on a page. To provide a good user experience, sites should maintain a CLS score of 0.1 or less for at least 75% of page visits.
Good First Contentful Paint
Measures initial loading speed. To provide a good user experience, the FCP should be 1.8 seconds or less.
Good Time To First Byte
Measures the time between the request for a resource and when the first byte of a response begins to arrive. A good TTFB is less than or equal to 800ms.
Good Interaction to Next Paint
Measures overall page responsiveness to user actions. To provide a good user experience, the INP should be 200ms or less.
| Framework | LCP% | CLS% | FCP% | TTFB% | INP% |
|---|---|---|---|---|---|
| SolidStart | 91 | 66 | 91 | 83 | 100 |
| Astro | 91 | 87 | 91 | 82 | 97 |
| Nuxt.js | 65 | 57 | 67 | 59 | 95 |
| SvelteKit | 79 | 77 | 78 | 71 | 94 |
| Next.js | 72 | 69 | 76 | 67 | 94 |
| React Router | 61 | 62 | 66 | 70 | 90 |