|
| 1 | +--- |
| 2 | +title: "We Benchmarked Our SSR Framework Against Next.js — Here's What We Found" |
| 3 | +published: true |
| 4 | +tags: react, nextjs, performance, ssr |
| 5 | +series: |
| 6 | +canonical_url: https://paretojs.tech/blog/benchmarks/ |
| 7 | +cover_image: |
| 8 | +--- |
| 9 | + |
| 10 | +We built [Pareto](https://github.com/childrentime/pareto), a lightweight streaming-first React SSR framework on Vite. Claims are cheap — so we built an automated benchmark suite that runs in CI on every PR, comparing Pareto against **Next.js**, **React Router (Remix)**, and **TanStack Start** on identical hardware. |
| 11 | + |
| 12 | +## What We Tested |
| 13 | + |
| 14 | +Four scenarios covering the most common SSR workloads: |
| 15 | + |
| 16 | +- **Static SSR** — Page with inline data, no async loader. Pure SSR throughput. |
| 17 | +- **Data Loading** — Loader with simulated 10ms DB query. SSR + data fetching overhead. |
| 18 | +- **Streaming SSR** — `defer()` + Suspense with 200ms delayed data. Streaming pipeline efficiency. |
| 19 | +- **API / JSON** — Pure JSON endpoint. Routing + serialization overhead. |
| 20 | + |
| 21 | +All benchmarks on GitHub Actions (Ubuntu, Node 22, 4 CPUs), using [autocannon](https://github.com/mcollina/autocannon) with 100 connections for 30 seconds. |
| 22 | + |
| 23 | +## Throughput: Requests Per Second |
| 24 | + |
| 25 | +| Scenario | Pareto | Next.js | React Router | TanStack Start | |
| 26 | +|---|---:|---:|---:|---:| |
| 27 | +| Static SSR | 2,224/s | **3,328/s** | 997/s | 2,009/s | |
| 28 | +| Data Loading | **2,733/s** | 293/s | 955/s | 1,386/s | |
| 29 | +| Streaming SSR | **247/s** | 236/s | 247/s | 247/s | |
| 30 | +| API / JSON | **3,675/s** | 2,212/s | 1,950/s | — | |
| 31 | + |
| 32 | +Next.js wins on static SSR. But the moment a loader is involved, Pareto handles **9.3x more requests than Next.js** and **2.9x more than React Router**. |
| 33 | + |
| 34 | +## Load Capacity: Max Sustainable QPS |
| 35 | + |
| 36 | +We ran a ramp-up test from 1 to 1,000 concurrent connections, measuring the max QPS each framework sustains while keeping p99 latency under 500ms. |
| 37 | + |
| 38 | +| Scenario | Pareto | Next.js | React Router | TanStack Start | |
| 39 | +|---|---:|---:|---:|---:| |
| 40 | +| Static SSR | **2,281/s** | 2,203/s | 1,098/s | 1,515/s | |
| 41 | +| Data Loading | **2,735/s** | 331/s | 1,044/s | 1,458/s | |
| 42 | +| Streaming SSR | **2,022/s** | 310/s | 807/s | 960/s | |
| 43 | +| API / JSON | **3,556/s** | 1,419/s | 1,912/s | — | |
| 44 | + |
| 45 | +Under streaming SSR load, Pareto sustains **2,022 req/s** — that's **6.5x Next.js** and **2.5x React Router**. |
| 46 | + |
| 47 | +**What this looks like in practice:** Say your product page needs to serve 2,000 req/s at peak. With Pareto, that's a single server. With Next.js at 331/s, you'd need **6 servers** behind a load balancer. For streaming SSR dashboards, it's **1 Pareto instance** vs **7 Next.js instances**. |
| 48 | + |
| 49 | +## Latency |
| 50 | + |
| 51 | +| Scenario | Pareto p50/p99 | Next.js p50/p99 | React Router p50/p99 | |
| 52 | +|---|---:|---:|---:| |
| 53 | +| Static SSR | 431ms / 1.35s | **244ms / 326ms** | 704ms / 7.16s | |
| 54 | +| Data Loading | **350ms / 702ms** | 1.42s / 7.82s | 760ms / 7.41s | |
| 55 | +| API / JSON | **266ms / 320ms** | 283ms / 321ms | 486ms / 2.12s | |
| 56 | + |
| 57 | +Under 100 concurrent connections, Pareto's data loading p99 is **702ms** while Next.js spikes to **7.82s**. 99% of users get their page in under 700ms with Pareto. With Next.js, 1 in 100 users waits nearly 8 seconds. |
| 58 | + |
| 59 | +## Bundle Size |
| 60 | + |
| 61 | +| Framework | Client JS (gzip) | Total (gzip) | |
| 62 | +|---|---:|---:| |
| 63 | +| **Pareto** | **62 KB** | **72 KB** | |
| 64 | +| Next.js | 233 KB | 409 KB | |
| 65 | +| React Router | 100 KB | 102 KB | |
| 66 | +| TanStack Start | 101 KB | 272 KB | |
| 67 | + |
| 68 | +62 KB of client JavaScript — roughly 1/4 of Next.js. On 4G mobile (~5 Mbps), that's **100ms** to download vs **370ms**. On 3G, it's **330ms vs 1.2 seconds** before any rendering begins. |
| 69 | + |
| 70 | +## The Cost Difference |
| 71 | + |
| 72 | +Here's a concrete scenario — a SaaS dashboard at 10,000 data-loading req/s peak: |
| 73 | + |
| 74 | +| Framework | Servers needed (4 CPU) | Monthly cost (est.) | |
| 75 | +|---|---:|---:| |
| 76 | +| **Pareto** | **4** | ~$160 | |
| 77 | +| TanStack Start | 7 | ~$280 | |
| 78 | +| React Router | 10 | ~$400 | |
| 79 | +| Next.js | 31 | ~$1,240 | |
| 80 | + |
| 81 | +## How We Keep Benchmarks Honest |
| 82 | + |
| 83 | +- **CI automated** — runs on every PR touching core code |
| 84 | +- **System tuning** — ASLR disabled, CPU governor `performance` |
| 85 | +- **Median aggregation** — eliminates outlier noise, CV% for stability |
| 86 | +- **Sequential isolation** — one framework at a time, cooldown between runs |
| 87 | +- **Same hardware** — all frameworks on the same GitHub Actions runner |
| 88 | + |
| 89 | +The full suite is open source: [github.com/childrentime/pareto/tree/main/benchmarks](https://github.com/childrentime/pareto/tree/main/benchmarks) |
| 90 | + |
| 91 | +```bash |
| 92 | +npx create-pareto my-app |
| 93 | +cd my-app && npm install && npm run dev |
| 94 | +``` |
| 95 | + |
| 96 | +--- |
| 97 | + |
| 98 | +*Pareto is a lightweight, streaming-first React SSR framework built on Vite. [GitHub](https://github.com/childrentime/pareto) · [Docs](https://paretojs.tech)* |
0 commit comments