Benchmarks
Local oha runs on the same machine, same window, same runtime version. Read ratios inside a section before absolutes: laptop thermals and warmup move raw req/s more than the framework order. Reproduce with bun run bench:ssr and bun run bench:http.
Full-stack SSR - Nifra vs the meta-frameworks
One section per UI framework, each with two explicitly-labelled tables: A - dynamic SSR (the page rendered on every request, no caching) and B - cacheable (the same page from each framework's SSG/ISR mode). Meta-frameworks run on Node through their own production server. Nifra shows Bun and Node rows (@nifrajs/node) so the Node rows compare apples-to-apples. The headline multipliers below are Table A only.
25×Nifra + React vs Next.js (Node)
4×Nifra + Solid vs SolidStart (Node)
7×Nifra + Vue vs Nuxt (Node)
4×Nifra + Svelte vs SvelteKit (Node)
React
A - Dynamic SSR. The page is rendered on every request, no caching: the per-request cost, the number that matters for pages that can't be cached.
| Target | req/s | p50 | p99 | client JS (gz) |
|---|
| Nifra + React (Bun) | 33,834 | 1.40 ms | 2.77 ms | 59.8 KB |
| Nifra + React (Node) | 28,043 | 1.73 ms | 3.43 ms | 59.8 KB |
| Next.js (Node) | 1,115 | 44.36 ms | 56.98 ms | 182.4 KB |
| Remix (Node) | 2,103 | 23.72 ms | 31.53 ms | 95.4 KB |
B - Cacheable. The same page served from each framework's cached mode - SSG, and ISR where supported (cache warmed before measuring). A cached row is not an SSR row: compare within this table only.
| Target | req/s | p50 | p99 | client JS (gz) |
|---|
| Nifra + React SSG (Bun) | 36,766 | 1.29 ms | 2.58 ms | 70.7 KB |
| Next.js static (Node) | 6,709 | 7.21 ms | 12.07 ms | 182.4 KB |
| Nifra + React ISR (Bun) | 104,291 | 0.43 ms | 0.96 ms | 70.7 KB |
| Next.js ISR (Node) | 6,612 | 7.34 ms | 12.33 ms | 182.4 KB |
Solid
A - Dynamic SSR. The page is rendered on every request, no caching: the per-request cost, the number that matters for pages that can't be cached.
| Target | req/s | p50 | p99 | client JS (gz) |
|---|
| Nifra + Solid (Bun) | 34,965 | 1.35 ms | 2.64 ms | 6.3 KB |
| Nifra + Solid (Node) | 29,611 | 1.60 ms | 3.21 ms | 6.3 KB |
| SolidStart (Node) | 7,372 | 5.62 ms | 16.26 ms | 18.8 KB |
B - Cacheable. The same page served from each framework's cached mode - SSG, and ISR where supported (cache warmed before measuring). A cached row is not an SSR row: compare within this table only.
| Target | req/s | p50 | p99 | client JS (gz) |
|---|
| Nifra + Solid SSG (Bun) | 36,749 | 1.29 ms | 2.58 ms | 12 KB |
| SolidStart static (Node) | 19,218 | 2.45 ms | 4.32 ms | 18.3 KB |
Vue
A - Dynamic SSR. The page is rendered on every request, no caching: the per-request cost, the number that matters for pages that can't be cached.
| Target | req/s | p50 | p99 | client JS (gz) |
|---|
| Nifra + Vue (Bun) | 27,792 | 1.70 ms | 3.38 ms | 27 KB |
| Nifra + Vue (Node) | 19,660 | 2.35 ms | 4.69 ms | 27 KB |
| Nuxt (Node) | 2,828 | 15.88 ms | 43.60 ms | 72.2 KB |
B - Cacheable. The same page served from each framework's cached mode - SSG, and ISR where supported (cache warmed before measuring). A cached row is not an SSR row: compare within this table only.
| Target | req/s | p50 | p99 | client JS (gz) |
|---|
| Nifra + Vue SSG (Bun) | 37,081 | 1.28 ms | 2.55 ms | 12 KB |
| Nuxt static (Node) | 20,288 | 2.38 ms | 3.42 ms | 65.5 KB |
Svelte
A - Dynamic SSR. The page is rendered on every request, no caching: the per-request cost, the number that matters for pages that can't be cached.
| Target | req/s | p50 | p99 | client JS (gz) |
|---|
| Nifra + Svelte (Bun) | 45,685 | 1.04 ms | 2.07 ms | 21.2 KB |
| Nifra + Svelte (Node) | 30,261 | 1.48 ms | 3.32 ms | 21.2 KB |
| SvelteKit (Node) | 7,643 | 6.09 ms | 12.57 ms | n/a |
B - Cacheable. The same page served from each framework's cached mode - SSG, and ISR where supported (cache warmed before measuring). A cached row is not an SSR row: compare within this table only.
| Target | req/s | p50 | p99 | client JS (gz) |
|---|
| Nifra + Svelte SSG (Bun) | 36,993 | 1.28 ms | 2.56 ms | 19.5 KB |
| SvelteKit static (Node) | 24,871 | 1.76 ms | 4.09 ms | 30.1 KB |
Preact
A - Dynamic SSR. The page is rendered on every request, no caching: the per-request cost, the number that matters for pages that can't be cached.
| Target | req/s | p50 | p99 | client JS (gz) |
|---|
| Nifra + Preact (Bun) | 34,140 | 1.40 ms | 2.77 ms | 7.7 KB |
| Nifra + Preact (Node) | 29,503 | 1.64 ms | 3.27 ms | 7.7 KB |
| Raw Preact render (Node, ceiling) | 35,717 | 1.36 ms | 2.69 ms | 4.6 KB |
B - Cacheable. The same page served from each framework's cached mode - SSG, and ISR where supported (cache warmed before measuring). A cached row is not an SSR row: compare within this table only.
| Target | req/s | p50 | p99 | client JS (gz) |
|---|
| Nifra + Preact SSG (Bun) | 36,780 | 1.29 ms | 2.57 ms | 15.4 KB |
Meta-frameworks are Node-only in this matrix; compare them against Nifra's Node row (the headline multipliers above do exactly that). Preact has no maintained meta-framework, so its section is Nifra-only - Bun vs Node on the same app. A client-JS of n/a means the run couldn't account that framework's payload from the SSR HTML.
Backend - HTTP throughput across runtimes
Nifra is also a standalone API framework. Two core workloads - a path-param GET and a validated POST - each runtime through Nifra's real adapter, next to that runtime's raw handler and the popular libraries. Rows are ordered by the geometric mean of the workloads, so no single column decides the ranking.
Bun
| Framework | GET /users/:id | POST /users |
|---|
| bun-native | 149,462 | 130,810 |
| Nifra | 150,956 | 119,363 |
| Elysia | 151,931 | 117,652 |
| Hono | 127,009 | 93,525 |
Node
| Framework | GET /users/:id | POST /users |
|---|
| Nifra | 92,120 | 72,661 |
| node-raw | 86,796 | 74,085 |
| Fastify | 71,589 | 56,685 |
| Elysia | 77,654 | 49,764 |
| Express | 51,796 | 43,320 |
| Hono | 52,255 | 35,424 |
Deno
| Framework | GET /users/:id | POST /users |
|---|
| deno-raw | 147,618 | 140,889 |
| Nifra | 141,713 | 117,773 |
| Elysia | 147,895 | 98,689 |
| Hono | 131,425 | 102,609 |
Backend - the same routes with a real route's work
The tables above are the shape every framework publishes: a bare route doing nothing but routing. This is the same GET and POST after the route gains what an actual API route has - security headers, CORS, a request-id hook, bearer auth through a derive, and a cookie read. That combination disqualifies the route from every one of Nifra's fused fast lanes, so these are the pessimistic numbers, not the flattering ones. The third column adds one body-observing middleware to the same GET.
Bun
| Framework | GET /api/orders | POST /api/orders | GET + body hash |
|---|
| bun-native | 89,643 | 96,167 | 68,540 |
| Nifra | 85,401 | 82,451 | 65,129 |
| Elysia | 85,270 | 82,989 | 57,081 |
| Hono | 49,617 | 51,280 | 33,503 |
Node
| Framework | GET /api/orders | POST /api/orders | GET + body hash |
|---|
| node-raw | 61,182 | 63,018 | 54,882 |
| Nifra | 62,123 | 56,203 | 56,066 |
| Fastify | 62,556 | 51,061 | 57,950 |
| Elysia | 50,075 | 39,762 | 35,161 |
| Express | 39,541 | 37,276 | 38,366 |
| Hono | 26,610 | 22,874 | 19,304 |
Deno
| Framework | GET /api/orders | POST /api/orders | GET + body hash |
|---|
| deno-raw | 67,597 | 85,969 | 58,846 |
| Nifra | 62,634 | 72,876 | 55,200 |
| Elysia | 56,376 | 61,106 | 41,913 |
| Hono | 31,761 | 35,039 | 13,384 |
Reproduce locally with bun run bench:http:update, bun run bench:http:realworld:update, and bun run bench:ssr. Same-run ratios matter more than absolute req/s.