Dev TimeRun Timee18e.devBlog

Run Time Stats

Client Side Rendered Tests

First Paint (ms)

First Paint (ms) chart
FrameworkFirst PaintFCPINP
Astro87.6ms87.66ms4.22ms
Next.js348.6ms348.42ms16.34ms
Nuxt170.8ms170.54ms10.9ms
React Router146.6ms146.41ms14.59ms
SolidStart108.2ms108.18ms15.9ms
SvelteKit115.4ms115.56ms12.38ms
TanStack Start154.2ms154.26ms31.94ms

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 dynamic import with ssr: false to prevent build-time prerendering
  • TanStack Start, Nuxt, SvelteKit, and SolidStart disable SSR per-route
  • React Router uses route-level clientLoader functions with HydrateFallback so 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's ClientRouter is 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 Paint (ms) chart
FrameworkFirst PaintFCPINP
Astro67.6ms67.51ms0.46ms
Next.js151ms150.78ms22.09ms
Nuxt94.2ms94.23ms11.44ms
React Router90ms89.78ms20.4ms
SolidStart97.8ms97.62ms16.17ms
SvelteKit87.6ms87.46ms13.19ms
TanStack Start115ms114.84ms26.13ms

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

Ops/sec chart
FrameworkOps/secMedian LatencyBody SizeDuplication
Baseline HTML8481.199ms96.83kb1x
Astro5101.901ms99.82kb1x
Mastro5151.912ms181.95kb1x
Next.js2124.833ms199.57kb2x
Nuxt3492.769ms201.27kb2x
React Router3472.855ms211.63kb2x
SolidStart3842.546ms230.24kb2x
SvelteKit4332.239ms183.49kb2x
TanStack Start3143.144ms193.62kb2x

Methodology

  • Each framework renders the dedicated /ssr-throughput route with a table of 1000 rows and UUID id/name columns
  • This route intentionally does not render the exact same table as the browser SSR and load tests: it omits detail links and framework link components so router, prefetch, and navigation metadata do not dominate the request-handler throughput measurement
  • Mock HTTP requests bypass TCP overhead so this measures request-handler rendering throughput rather than full server throughput
  • 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
  • Frameworks are invoked through their production request handlers where possible. Web API handlers are called with Request objects; Node.js handlers are called with mock IncomingMessage and ServerResponse objects.
  • Next.js renders the throughput table as a client component, matching the setup from PR #94, so the benchmark compares traditional server-rendered React + hydration work instead of making Next.js render every table row as React Server Components
  • Inspired by eknkc/ssr-benchmark

Server Side Load Test

P99 Latency

P99 Latency at 25 Connections

P99 Latency at 25 Connections chart

P90 Latency

P90 Latency at 25 Connections

P90 Latency at 25 Connections chart
FrameworkPeak req/sPeak ConnectionsP99 @ 25P99 @ 50P99 @ 100Total Req.
Baseline HTML1,67710018ms39ms97ms49,383
Astro692.42557ms123ms1786ms20,902
Next.js3612181ms4678ms4748ms990
Nuxt64.8102169ms4174ms4286ms2,035
React Router16525351ms1743ms3779ms5,456
SolidStart62.2253345ms3802ms4034ms2,003
SvelteKit4942576ms464ms2831ms15,728
TanStack Start4253827ms4376ms4251ms1,298

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 Largest Contentful Paint chart
FrameworkLCP%CLS%FCP%TTFB%INP%
SolidStart91669183100
Astro9187918297
Nuxt.js6557675995
SvelteKit7977787194
Next.js7269766794
React Router6162667090

Methodology

  • All Core Web Vitals for Desktop are sourced from HTTP Archive
  • Metrics refresh monthly