0.3.5 is a performance and housekeeping release. @devorajs/core and @devorajs/cli are 0.3.5 and create-devora is 0.3.3 (new projects pin ^0.3.5). The three adapters are unchanged. The complete list is the 0.3.5 section of the CHANGELOG; this post is the short version, with the numbers and their limits.
What an existing app has to do
Nothing in a typical app. The CHANGELOG lists four behavior changes; each is small:
- A missing session store, or (in production) a missing
DEVORA_SESSION_SECRET, stopsdevora startwith one clear line and exit status 1 instead of a stack trace. It refused to start before and still does. ctx.urlandctx.headersare built the first time a handler reads them. Reading them is unchanged. They are now getters, so a handler that assigns to them throws in strict mode; nothing in the framework does.- A rebuilt island or client-route manifest is picked up within a second.
- The header's brand link has the accessible name
devora.js home(it wasHome, which did not contain its visible text). A test that selects it byaria-label="Home"needs the new name.
The five performance changes
Each came from a CPU profile of a running devora start (node --cpu-prof), not from a guess, and each has a test. In short, work that never changes was being redone on every request:
ctx.urlandctx.headerswere built even when no handler read them.- Build keys, module lookups and the paths of pre-rendered pages were recomputed per request, and the request path went through the URL parser even when it was plain. A plain path is now read straight from the request line; anything unusual still goes through the parser, and a test compares both on thousands of generated paths.
- The store key for a session ID (an HMAC) is remembered per secret, cookie name and ID, in a memory bounded to 5,000 entries. The value is the same, so existing sessions keep working and an isolated app still cannot read a shared app's sessions.
- The island and client-route manifests were read from disk on every server-rendered request; they are now read at most once a second. On a form POST this was about a third of the profile (file system calls were 33.7% of samples in 0.3.4 and 0.1% after).
- The absolute request URL behind
ctx.urlis parsed when first read.
No security default was changed. The tests that cover the paths these changes touch: a remembered session key never outlives a session (revoked, deleted, expired, after the memory is flooded, in another app's scope); the remembered page paths stay bounded and keep refusing traversal; and a page form POST gets a 403 without, with a missing or with a wrong CSRF token and a 303 with a valid one, with the 0.3.4 values for the security headers and cookie flags.
What it measured
The benchmark harness (the same pages and the same JSON API in several frameworks, one machine, the load tool on that machine, 5 runs of 10 s per scenario) was run on the published 0.3.4 packages and again on the published 0.3.5 packages. Medians, with the lowest and highest run in brackets:
| Scenario | 0.3.4, req/s | 0.3.5, req/s |
|---|---|---|
| Pre-rendered page | 71,479 (68,049–73,376) | 101,891 (97,155–105,289) |
| Server-rendered page | 9,475 (9,159–9,484) | 11,736 (11,611–11,921) |
| Form POST | 31,988 (28,631–32,094) | 79,136 (78,271–81,871) |
| JSON list (API route) | 30,524 (26,001–37,802) | 49,529 (49,258–51,050) |
| Authenticated GET (API route) | 43,169 (42,768–43,347) | 77,228 (76,431–77,635) |
The 0.3.4 API runs were noisy (the JSON list ranged from 26,001 to 37,802), so the gap there is overstated. A second measurement is steadier: an interleaved A/B of the 0.3.4 packages against the same code before it was published, alternating builds on the same machine over 3 rounds, gave about +15% on the JSON list, +32% on the authenticated GET, +21% on a valid POST, +41% on the pre-rendered page and +23% on the server-rendered page (benchmarks/results/ab-published-vs-final.txt). Take the A/B figures as the better estimate of the change, and both as one machine's numbers.
Where it is still behind
The performance page and the repository's reports call a difference smaller than a framework's own run-to-run spread a tie. By that rule, on 0.3.5:
- Ahead of the other four frameworks on the pre-rendered page, the server-rendered page and the form POST (Next.js, Astro, SvelteKit, React Router).
- Behind Fastify, Hono and NestJS on its Fastify platform on every JSON API scenario; on the authenticated GET also behind Express and NestJS on its Express platform. On the JSON list, Fastify reached 66,678 req/s against 49,529; on the authenticated GET, 134,383 against 77,228.
- Level on the scenario with one awaited 2 ms step, where five results overlap: the timer, not the framework, sets the rate.
Part of that gap is work the bare frameworks do not do: Devora.js sends CSP, HSTS and X-Frame-Options on every response and runs a real session layer for the authenticated scenario, and these numbers keep both. Memory is higher too: 285 MB after the API scenarios against 167 to 258 MB for Express, Fastify, Hono and both NestJS platforms (and lower than Next.js, SvelteKit, React Router and Astro). A faster server also keeps more heap, so that is not proof of a leak; a 10-minute mixed-load run on the code that became 0.3.5 held steady at about 314 MB.
Upgrade
npm install @devorajs/core@^0.3.5 @devorajs/cli@^0.3.5
Upgrade core and cli together, in the root and every app's package.json. The steps and the earlier tables are on Upgrading to 0.3.5; the numbers are on Performance.