Performance
The same three pages — a static page, a server-rendered page with 100 items and one interactive counter, and a form — built in Devora.js and four other frameworks, each with its own documented production setup. Measured 2026-10-16; every number on this page comes from that run's JSON.
Benchmarks
In this run Devora.js was ahead of every other framework on server-rendered page, pre-rendered page and form post; level, within the run-to-run spread, on build time (with Astro, SvelteKit and React Router); behind on memory (SvelteKit) and JavaScript (SvelteKit).
Requests/second, server-rendered pageHigher is better
| Framework | Result |
|---|---|
| Devora.js 0.3.5 | 11,736 req/s |
| Astro 7.3.8 | 9,456 req/s |
| SvelteKit 3.0.1 | 7,122 req/s |
| React Router 8.4.0 | 3,944 req/s |
| Next.js 16.4.1 | 1,826 req/s |
A page rendered per request: 100 items from a loader and one interactive counter. The scenario closest to a real application.
Requests/second, pre-rendered pageHigher is better
| Framework | Result |
|---|---|
| Devora.js 0.3.5 | 101,891 req/s |
| Astro 7.3.8 | 38,096 req/s |
| React Router 8.4.0 | 36,138 req/s |
| SvelteKit 3.0.1 | 36,036 req/s |
| Next.js 16.4.1 | 13,989 req/s |
A tiny page with no data, built ahead of time. Each framework sends its own output (sizes under "Response size"), so this mostly measures the HTTP layer.
Requests/second, form POSTHigher is better
| Framework | Result |
|---|---|
| Devora.js 0.3.5 | 79,136 req/s |
| SvelteKit 3.0.1 | 36,976 req/s |
| React Router 8.4.0 | 23,567 req/s |
| Astro 7.3.8 | 18,791 req/s |
| Next.js 16.4.1 | 8,585 req/s |
A valid form submission answered with a 303 redirect.
Cold production buildLower is better
| Framework | Result |
|---|---|
| React Router 8.4.0 | 409 ms |
| Devora.js 0.3.5 | 434 ms |
| Astro 7.3.8 | 563 ms |
| SvelteKit 3.0.1 | 971 ms |
| Next.js 16.4.1 | 2.17 s |
npm run build with build output and caches deleted.
Idle server memoryLower is better
| Framework | Result |
|---|---|
| SvelteKit 3.0.1 | 141 MB |
| React Router 8.4.0 | 149 MB |
| Devora.js 0.3.5 | 157 MB |
| Astro 7.3.8 | 161 MB |
| Next.js 16.4.1 | 175 MB |
Resident memory 2 s after start.
JavaScript sent to the browser, SSR pageLower is better
| Framework | Result |
|---|---|
| SvelteKit 3.0.1 | 33.4 kB |
| Devora.js 0.3.5 | 45.6 kB |
| Astro 7.3.8 | 67.4 kB |
| React Router 8.4.0 | 108.6 kB |
| Next.js 16.4.1 | 132.0 kB |
Every script the page loads, gzip.
Response size and encoding
What each framework actually sent for one request after the run, read as raw bytes. The load tool sends no Accept-Encoding, so nothing was compressed during the timed runs; the browser column adds what a browser sends.
| Framework | Pre-rendered page | Server-rendered page | Same, with browser Accept-Encoding |
|---|---|---|---|
| Devora.js | 568 B | 4,902 B | 568 B / 4,902 B |
| Next.js | 5,559 B | 17,105 B | 1,811 B (gzip) / 3,563 B (gzip) |
| Astro | 398 B | 8,391 B | 398 B / 8,391 B |
| SvelteKit | 1,868 B | 7,824 B | 628 B (br) / 7,824 B |
| React Router | 4,601 B | 15,153 B | 1,754 B (br) / 3,463 B (br) |
Apple M5, 10 cores, 16 GB, Darwin 27.0.0, Node 24.19.0. 50 connections, median of 5 runs (builds: 3), 2026-10-16. Client and server on one machine, tiny pages. Reproduce
Read this first
- Devora.js runs on Node and Vite like most of the others. Requests/second on one machine mostly measures Node's HTTP server and the rendering work per request; it isn't how many users an app can serve.
- Devora.js sends the same security headers (CSP, HSTS, X-Frame-Options) on every response by default; those are included in its numbers.
- One machine, client and server together. Run it on your own hardware before relying on it.
- The load tool sends no
Accept-Encoding, so no framework compressed during the timed runs; the pages differ in size because each framework emits different HTML (see "Response size and encoding" in the panel). The pre-rendered page is tiny, and a smaller page is part of why a framework is fast on it. - On the pre-rendered page this run measured Devora.js 101,891, Astro 38,096, React Router 36,138, SvelteKit 36,036, Next.js 13,989 requests/s (medians of 5 runs). The page is tiny, so this mostly compares the HTTP layer, and a result is only called a lead when it is outside the other's run-to-run spread.
Where Devora.js is behind, level, or ahead
A difference smaller than the spread between a framework's own runs is called a tie.
Behind
- Build, warm: 446 ms, 2nd of 5. React Router is ahead with 404 ms.
- Server start: 194 ms, 3rd of 5. SvelteKit is ahead with 140 ms.
- Idle memory: 157 MB, 3rd of 5. SvelteKit is ahead with 141 MB.
- JS, SSR page (gzip): 45.6 kB, 2nd of 5. SvelteKit is ahead with 33.4 kB.
Level, within the run-to-run spread
- Build, cold: 434 ms, level with Astro, SvelteKit and React Router.
- JS, static page (gzip): 0.0 kB, level with Astro.
- Counter responds after: 20 ms, level with Astro and SvelteKit.
Ahead of every other framework
Static page, req/s; SSR page, req/s; Form POST, req/s.
All results
Medians of the runs. Spreads (min–max of every run) are in the raw data.
| Metric | Devora.js 0.3.5 | Next.js 16.4.1 | Astro 7.3.8 | SvelteKit 3.0.1 | React Router 8.4.0 |
|---|---|---|---|---|---|
| Build, cold lower is better | 434 ms | 2.17 s | 563 ms | 971 ms | 409 ms |
| Build, warm lower is better | 446 ms | 1.24 s | 517 ms | 978 ms | 404 ms |
| Server start lower is better | 194 ms | 242 ms | 165 ms | 140 ms | 170 ms |
| Idle memory lower is better | 157 MB | 175 MB | 161 MB | 141 MB | 149 MB |
| Static page, req/s higher is better | 101,891 (p99 1 ms) | 13,989 (p99 6 ms) | 38,096 (p99 1 ms) | 36,036 (p99 3 ms) | 36,138 (p99 2 ms) |
| SSR page, req/s higher is better | 11,736 (p99 8 ms) | 1,826 (p99 52 ms) | 9,456 (p99 9 ms) | 7,122 (p99 11 ms) | 3,944 (p99 20 ms) |
| Form POST, req/s higher is better | 79,136 (p99 1 ms) | 8,585 (p99 11 ms) | 18,791 (p99 6 ms) | 36,976 (p99 4 ms) | 23,567 (p99 4 ms) |
| JS, SSR page (gzip) lower is better | 45.6 kB | 132.0 kB | 67.4 kB | 33.4 kB | 108.6 kB |
| JS, static page (gzip) lower is better | 0.0 kB | 131.8 kB | 0.0 kB | 33.4 kB | 108.6 kB |
| Counter responds after lower is better | 20 ms | 34 ms | 23 ms | 22 ms | 35 ms |
API routes
The same JSON API (identical payloads, status codes and validation, checked before timing) in Devora.js, Express, Fastify, Hono, NestJS on its Express and on its Fastify platform, and the API routes of Next.js, SvelteKit, React Router and Astro. Devora.js sends CSP, HSTS and X-Frame-Options on every response and runs a real session layer for the authenticated scenario; the bare backend frameworks do neither, so part of any gap to them is that work. Memory and start time for these apps are in the repository's backend report.
D. JSON list
GET /api/items: a JSON list of 100 items. Devora.js is behind Fastify 5 and others beyond the spread.
| Framework | Requests/s, median (min–max) | p99 |
|---|---|---|
| Fastify 5 5.12.7 | 66,678 (62,941–68,180) | 1 ms |
| Hono 4.13.13 | 66,215 (65,210–66,644) | 1 ms |
| NestJS (Fastify platform) 12.1.3 | 61,927 (61,377–62,305) | 1 ms |
| Devora.js 0.3.5 | 49,529 (49,258–51,050) | 2 ms |
| Express 5 5.3.0 | 48,388 (46,477–48,499) | 1 ms |
| NestJS (Express platform) 12.1.3 | 44,700 (43,626–44,979) | 2 ms |
| SvelteKit 3.0.1 | 32,976 (30,394–33,430) | 2 ms |
| Astro 7.3.8 | 19,844 (19,746–19,854) | 5 ms |
| React Router 8.4.0 | 17,985 (17,109–18,087) | 4 ms |
| Next.js 16.4.1 | 12,069 (11,565–12,195) | 7 ms |
E. Authenticated GET
GET /api/me with a Bearer token: a session lookup, then JSON. Devora.js is behind Fastify 5 and others beyond the spread.
| Framework | Requests/s, median (min–max) | p99 |
|---|---|---|
| Fastify 5 5.12.7 | 134,383 (129,958–134,988) | 0 ms |
| Hono 4.13.13 | 128,375 (123,418–129,440) | 0 ms |
| NestJS (Fastify platform) 12.1.3 | 119,165 (118,816–119,212) | 0 ms |
| Express 5 5.3.0 | 85,420 (84,529–88,282) | 1 ms |
| NestJS (Express platform) 12.1.3 | 79,404 (79,159–80,480) | 1 ms |
| Devora.js 0.3.5 | 77,228 (76,431–77,635) | 2 ms |
| SvelteKit 3.0.1 | 50,291 (47,666–53,852) | 2 ms |
| Astro 7.3.8 | 22,319 (21,877–22,690) | 5 ms |
| React Router 8.4.0 | 21,080 (20,251–21,202) | 3 ms |
| Next.js 16.4.1 | 13,216 (12,418–13,656) | 7 ms |
F1. POST, valid body
POST /api/items with a valid JSON body (201). Devora.js is behind Fastify 5 and others beyond the spread.
| Framework | Requests/s, median (min–max) | p99 |
|---|---|---|
| Fastify 5 5.12.7 | 116,186 (113,568–116,617) | 0 ms |
| Hono 4.13.13 | 106,772 (100,681–107,773) | 0 ms |
| NestJS (Fastify platform) 12.1.3 | 100,425 (100,192–100,797) | 0 ms |
| Express 5 5.3.0 | 75,395 (73,104–75,645) | 1 ms |
| Devora.js 0.3.5 | 71,450 (69,767–74,167) | 2 ms |
| NestJS (Express platform) 12.1.3 | 65,789 (65,293–66,128) | 1 ms |
| SvelteKit 3.0.1 | 40,831 (38,706–42,029) | 3 ms |
| React Router 8.4.0 | 22,318 (22,033–23,106) | 5 ms |
| Astro 7.3.8 | 19,669 (19,282–20,072) | 5 ms |
| Next.js 16.4.1 | 11,911 (11,389–12,377) | 8 ms |
F2. POST, invalid body
POST /api/items with an invalid body (422). Devora.js is behind Fastify 5 and others beyond the spread.
| Framework | Requests/s, median (min–max) | p99 |
|---|---|---|
| Fastify 5 5.12.7 | 115,901 (111,124–116,337) | 0 ms |
| Hono 4.13.13 | 109,402 (108,931–109,699) | 0 ms |
| NestJS (Fastify platform) 12.1.3 | 77,152 (76,132–78,048) | 1 ms |
| Devora.js 0.3.5 | 74,592 (70,423–75,302) | 1 ms |
| Express 5 5.3.0 | 73,545 (71,334–75,197) | 1 ms |
| NestJS (Express platform) 12.1.3 | 53,860 (53,750–54,009) | 1 ms |
| SvelteKit 3.0.1 | 42,003 (41,540–42,852) | 2 ms |
| React Router 8.4.0 | 23,307 (23,028–23,576) | 4 ms |
| Astro 7.3.8 | 20,059 (19,404–20,173) | 5 ms |
| Next.js 16.4.1 | 11,929 (11,783–12,422) | 8 ms |
G. One async step
GET /api/slow: one awaited 2 ms timer, then JSON. Devora.js is level, within the run-to-run spread, with Fastify 5, Hono, NestJS (Fastify platform) and SvelteKit.
| Framework | Requests/s, median (min–max) | p99 |
|---|---|---|
| Devora.js 0.3.5 | 15,967 (15,348–17,436) | 5 ms |
| Fastify 5 5.12.7 | 15,455 (15,279–15,699) | 4 ms |
| Hono 4.13.13 | 15,412 (15,332–15,512) | 4 ms |
| NestJS (Fastify platform) 12.1.3 | 15,226 (15,106–15,541) | 4 ms |
| SvelteKit 3.0.1 | 15,200 (15,056–15,947) | 5 ms |
| NestJS (Express platform) 12.1.3 | 15,132 (15,035–15,331) | 5 ms |
| Express 5 5.3.0 | 14,972 (14,519–15,240) | 5 ms |
| Astro 7.3.8 | 14,435 (14,230–14,685) | 6 ms |
| React Router 8.4.0 | 13,317 (13,032–13,383) | 6 ms |
| Next.js 16.4.1 | 13,293 (12,536–13,496) | 6 ms |
How it was measured
Each framework's app renders the same content with the same status codes: /static pre-rendered at build time, /items rendered per request with a loader returning 100 items and one counter, /form answering a valid POST with a 303 and an empty one with the error. (Next.js answers an invalid Server Action submission with 200 and the error, its documented behavior.) Builds and servers are each framework's own: Devora.js devora build/devora start, Next.js next build/next start, Astro on @astrojs/node, SvelteKit on adapter-node, React Router with react-router-serve. Defaults only.
- Load: autocannon 8.0.0, 50 connections, 5 s warm-up, then 5 runs of 10 s each, with a browser's
AcceptandOriginheaders. - Builds: 3 cold builds (output and caches deleted), each followed by a warm one.
- Browser: Chromium, 5 runs per page; JavaScript is every script the page loads, compressed with gzip -9 and brotli; "counter responds" is the time from navigation start until a click changes it.
- Machine: Apple M5, 10 cores, 16 GB, Darwin 27.0.0 (darwin arm64), Node v24.19.0.
- Versions: Devora.js 0.3.5, Next.js 16.4.1, Astro 7.3.8, SvelteKit 3.0.1, React Router 8.4.0.
Scripts, the five apps and the raw JSON: benchmarks/ in the devora.js repo. To reproduce: cd benchmarks && npm ci && npm run bench && npm run report.
Caveats
- Localhost has no network latency; real deployments add it to every request.
- The pages are small on purpose. Real apps spend most of their time in their own data access, which these numbers don't include.
- Idle memory includes the
npm startwrapper process, the same for every framework. - Hydration timing clicks every 5 ms on an unthrottled machine; a phone on a slow network would see larger differences in JavaScript size.
- Results are from one run of the suite on one day; they will change as every framework changes.