Dev Time Run Time e18e.dev Blog

Run Time Stats

SSR Performance

Measured on GitHub Actions (ubuntu-latest, Node 24) using custom SSR benchmark apps.

Framework Ops/sec Avg Latency Body Size Duplication
Baseline HTML 766 1.315ms 96.81kb 1x
Astro 385 2.628ms 99.86kb 1x
Mastro 339 2.983ms 181.95kb 1x
Next.js 125 8.117ms 199.11kb 2x
Nuxt 250 4.076ms 201.26kb 2x
React Router 64 15.528ms 211.14kb 2x
SolidStart 263 4.066ms 227.79kb 2x
SvelteKit 276 3.725ms 183.55kb 2x
TanStack Start 179 5.665ms 193.53kb 2x

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 SSR. To keep the comparison fair, Next.js uses "use client" to opt out of RSC and use traditional SSR + hydration like most of the other frameworks
  • Inspired by eknkc/ssr-benchmark

SPA Performance

First Paint (ms)

Default
  • Measured on GitHub Actions (ubuntu-latest, Node 24) using Lighthouse flow with Chromium.

    Framework First Paint FCP INP
    Astro 136.6ms 136.39ms 11.19ms
    Next.js 384.6ms 384.44ms 21.35ms
    Nuxt 148.4ms 148.24ms 14.42ms
    React Router 178.8ms 178.92ms 16.54ms
    SolidStart 127.6ms 127.58ms 20.09ms
    SvelteKit 156.8ms 156.99ms 12.88ms
    TanStack Start 804.2ms 804.32ms 199.32ms

    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, TanStack Start, and React Router default to SSR with no per-route opt-out. Next.js wraps the SPA table in a dynamic import with ssr: false to prevent build-time prerendering. TanStack Start uses its built-in spa mode. React Router disables SSR entirely via ssr: false in its config. All other frameworks (Nuxt, SvelteKit, SolidStart, Astro) disable SSR per-route without a separate build.

    MPA Performance

    First Paint (ms)

    Default
  • Measured on GitHub Actions (ubuntu-latest, Node 24) using Lighthouse flow with Chromium.

    Framework First Paint FCP INP
    Astro 100.8ms 100.78ms 1.03ms
    Next.js 171.2ms 171.37ms 19.13ms
    Nuxt 111.2ms 111.34ms 3.66ms
    React Router 207.6ms 207.4ms 2.44ms
    SolidStart 126.8ms 126.7ms 17.68ms
    SvelteKit 98.8ms 98.69ms 3.27ms
    TanStack Start 99.8ms 99.92ms 2ms

    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, TanStack Start, and React Router default to SSR with no per-route opt-out. Next.js wraps the SPA table in a dynamic import with ssr: false to prevent build-time prerendering. TanStack Start uses its built-in spa mode. React Router disables SSR entirely via ssr: false in its config. All other frameworks (Nuxt, SvelteKit, SolidStart, Astro) disable SSR per-route without a separate build.