This is the third post in a series. Start with the first post if you haven't already, or read the architecture post for the full mental model.
Framework posts love benchmarks. They also love cherry-picking them. I want to be upfront about what these benchmarks measure, what they don't, and what the numbers actually mean for your daily work.
The short version: Inglorious Web matches the fastest alternatives without any optimization work, in a bundle that's 4–5x smaller than React. Vue and Svelte are equally fast — that's not a surprise, and it's not a problem. The real differentiator isn't raw speed. It's that Inglorious Web's optimization surface area is dramatically smaller. You write naive code and get optimal performance. But there's a scenario where full-tree rendering has a measurable cost, and I'll show you that too — along with why it almost certainly doesn't matter for your users.
All benchmarks are in the public repo. Run them yourself on your hardware.
Benchmark 1: The Dashboard
What It Measures
A live-updating dashboard with 1,000 rows of data updating in real time, 10 random rows updated every 10ms (100 updates/second), 4 live charts, filtering and sorting, and an FPS counter. This simulates factory monitoring dashboards, stock tickers, and logistics tracking systems — the kind of UI where high-frequency bulk updates are the norm.
Fairness Rules
All implementations share the same dataset and update frequency, the same chart model and data slicing, the same business logic shape (event-driven handlers responding to the same event names), the same top-level component structure, and the same CSS. This is a genuine apples-to-apples comparison.
The Full Results
| Variant | FPS (dev) | FPS (prod) | Bundle (kB) |
|---|---|---|---|
| React | 52 | 113 | 62.39 |
| React Memoized | 112 | 120 | 62.58 |
| React + RTK | 32 | 92 | 72.21 |
| React + RTK Memoized | 87 | 118 | 72.30 |
| React + RTK + Inglorious adapters | 29 | 74 | 79.18 |
| React + RTK + Inglorious adapters Memoized | 69 | 93 | 79.29 |
| React + Inglorious Store | 33 | 95 | 71.98 |
| React + Inglorious Store Memoized | 87 | 120 | 72.05 |
| Vue | 116 | 117 | 26.80 |
| Vue + Pinia | 117 | 117 | 28.56 |
| Svelte | 112 | 119 | 16.02 |
| Svelte + Store | 110 | 119 | 16.04 |
| Svelte + Runes | 102 | 118 | 14.13 |
| Inglorious Web | 118 | 120 | 16.29 |
| Inglorious Web Memoized | 115 | 120 | 16.35 |
Measured on MacBook Pro 16" 2023, Chrome 144, macOS Tahoe.
What the Numbers Mean
Vue and Svelte are fast. That's expected and honest. Both reach comparable FPS to Inglorious Web in development and match it in production. Raw rendering speed is not where Inglorious Web differentiates — and this post won't pretend otherwise.
The React dev/prod gap is the compiler story. These benchmarks use React 19.2, which ships with an automatic compiler. The naive React variant drops to 52 FPS in development but recovers to 113 FPS in production — because the compiler adds memoizations automatically at build time. Manual React.memo, useMemo, and useCallback are no longer required for production performance. The compiler recovers performance that would previously require manual optimization work. The underlying rendering model remains the same, though — components can still re-render broadly, and the compiler only optimizes patterns it can safely recognize. When it encounters a pattern it can't, you're back to reasoning about the component tree manually.
The Inglorious adapter layer is the real overhead in RTK variants. React + RTK on its own (32 FPS dev, 92 FPS prod) isn't slow because of RTK's middleware — it's slow because of how RTK interacts with React's rendering model in this workload. The variants using Inglorious adapters on top of RTK perform worse still (29 FPS dev, 74 FPS prod), which tells you the cost is in the adapter layer bridging two state models, not in RTK's middleware itself.
Inglorious Web is fast by default and gains little from memoization. The baseline variant (105 FPS dev, 120 prod) already matches the best alternatives. Adding compute() for derived state brings it to 110 FPS dev — a modest gain, confirming that the baseline update model is already efficient. You don't need to think about optimization, and there's no compiler pass silently doing it for you.
The bundle size gap is structural. React core alone is 62KB. React + RTK reaches 72KB. Inglorious Web is 16KB total — framework, state manager, and renderer. Vue is 27KB, Svelte is 16KB. For global audiences on slower connections, this matters.
Benchmark 2: The Charts
What It Measures
Four live-updating charts — line, area, bar, pie — with 100 data points each, updated at 10 updates/second. Compared against Recharts, the most widely used React chart library.
The Results
| Implementation | FPS (120Hz monitor) | vs Recharts |
|---|---|---|
| 📊 Recharts | 85–95 FPS | baseline |
| 🚀 Inglorious Charts (Config) | 115–120 FPS | +35% faster |
| 🚀 Inglorious Charts (Composition) | 115–120 FPS | +35% faster |
On 60Hz monitors: Inglorious ~60 FPS, Recharts ~51 FPS.
This one Inglorious Web wins clearly. Recharts carries React's rendering overhead. @inglorious/charts is built on the same entity-centric model — charts are entities, lit-html handles surgical DOM updates, no virtual DOM reconciliation. For data-dense dashboards with multiple live charts, the gap is meaningful.
Benchmark 3: The Deep Tree
What It Measures and Why It Matters
This benchmark is specifically designed to challenge full-tree rendering: a tree of depth 8 with a branching factor of 3, where one random leaf updates every 300ms.
React, Vue, and Svelte handle this with fine-grained reactivity — only the affected leaf rerenders. Inglorious Web walks the whole tree. This is the worst case for the full-tree model, chosen deliberately.
The Results
| Variant | FPS (dev) | FPS (prod) | Bundle (kB) |
|---|---|---|---|
| React | 120 | 120 | 62.16 |
| Vue | 120 | 120 | 25.84 |
| Svelte | 120 | 120 | 14.68 |
| Inglorious Web | 120 | 120 | 15.00 |
All four hit the monitor cap in both dev and production. FPS parity is complete.
Where the Difference Shows Up
FPS isn't the whole story. Here's what the profiler shows in production over a 10-second window:
| Variant | Main Thread (ms) | Scripting (ms) | Rendering (ms) | JS Heap (MB) |
|---|---|---|---|---|
| React | 251 | 37 | 124 | 23.9–24.5 |
| Vue | 196 | 38 | 89 | 28.8–29.6 |
| Svelte | 206 | 34 | 74 | 49.3–49.8 |
| Inglorious Web | 339 | 135 | 122 | 11.4–28.1 |
Inglorious Web uses 135ms of scripting time versus ~35ms for the others. That's about 100ms more — over 10 seconds. That's 1% of wall-clock time, on a workload specifically engineered to be the worst case for full-tree rendering, with no perceptible effect on the user and all variants locked at 120 FPS throughout.
It's a real cost. The profiler can measure it. But it's not a practical concern for any real application — and it comes with a tradeoff in the other direction: Inglorious Web's JS heap (11.4–28.1 MB) is substantially lower than Svelte's (49.3–49.8 MB). Full-tree rendering trades a small amount of CPU time for lower memory pressure. Whether that's a good trade depends on your constraints.
When It Would Matter
If you're building a deeply nested UI where individual nodes update rarely and in isolation, and CPU efficiency is a hard requirement, fine-grained reactive frameworks have a genuine edge. Inglorious Web's primary workload is the opposite — high-frequency bulk updates across many entities simultaneously. Workloads that fit this model well include real-time dashboards, financial tickers, IoT monitoring panels, collaborative editing tools, and multiplayer state synchronization. The last two are worth noting specifically: @inglorious/server uses the same event system over WebSockets, so adding real-time collaboration to an existing app is a single middleware line rather than an architectural change. The deep tree benchmark is a useful stress test of the tradeoff, not a representative scenario for any of these.
Testing: The Developer Performance Benchmark
Runtime benchmarks measure one dimension. Developer performance — how fast you can write, change, and verify code — matters at least as much over the life of a project. Testing is where architectural choices become visceral.
React Hooks
import { renderHook, act } from "@testing-library/react";
const { result } = renderHook(() => useTaskList());
act(() => {
result.current.addTask('Write documentation');
});
expect(result.current.tasks).toHaveLength(1);
You need @testing-library/react, renderHook, and act() wrappers. The React runtime is required. The ceremony is high enough that many teams skip unit-testing hooks entirely.
React + Redux
import { tasksReducer, taskAdded } from './taskSlice';
const state = tasksReducer([], taskAdded('Write documentation'));
expect(state).toHaveLength(1);
Redux reducers are pure functions and test in complete isolation — no store setup, no runtime needed. This is genuinely good, and it's the same instinct behind Inglorious Web's handler design. Where it gets harder is async: testing a thunk requires mocking dispatch, getState, and any side effects, and the setup grows quickly with complexity.
Inglorious Web
import { trigger } from "@inglorious/web/test";
const { entity } = trigger(
{ type: 'taskList', id: 'list', tasks: [] },
taskList.taskAdded,
'Write documentation',
);
expect(entity.tasks).toHaveLength(1);
No runtime. No store setup. No action creators. Just call the handler, assert the result. And because handlers can be async and can notify further events, the same pattern works for async too — trigger returns a promise when the handler is async, and the events array captures any notifications fired during the handler. There's nothing else to learn.
The Honest Summary
| Aspect | React | Vue | Svelte | Inglorious Web |
|---|---|---|---|---|
| Dev FPS (bulk updates) | 32–112 (optimization required) | 116–117 | 102–112 | 105–110 |
| Prod FPS (bulk updates) | 92–120 (optimization required) | 117 | 118–119 | 120 |
| FPS (sparse deep tree) | 120 | 120 | 120 | 120 |
| Scripting overhead (sparse updates) | Low | Low | Low | ~1% higher |
| Bundle size | 62–79KB | 27–29KB | 14–16KB | 16KB |
| Optimization required | Not in prod (compiler); yes in dev | No | No | No |
| Testing complexity | High (hooks) / Low (reducers) / Hard (async thunks) | Medium | Medium | Low |
The right framework depends on your workload and priorities. For high-frequency bulk updates, data-dense dashboards, and event-driven UIs, Inglorious Web performs optimally without any optimization work. For deeply nested UIs with sparse, localized updates and strict CPU budgets, fine-grained reactive frameworks have a measurable efficiency edge. For teams coming from React and Redux, the mental model transfer is natural and the Redux DevTools compatibility makes migration incremental.
What Comes Next
Performance is one side of the picture. The other is server state — how you fetch data, handle loading and error states, and keep your UI in sync with the backend.
The next post looks at how the entity-centric model interacts with async data fetching, why handleAsync covers most of what TanStack Query provides, and when you'd still want a dedicated data-fetching library. The short version: if you have a centralized store, you may not have the problem TanStack Query was built to solve.
Top comments (0)