DEV Community

Cover image for You Probably Don't Need to Think About UI Optimization: Honest Benchmarks
Matteo Antony Mistretta
Matteo Antony Mistretta

Posted on

You Probably Don't Need to Think About UI Optimization: Honest Benchmarks

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);
Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode

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.

Docs · Repo

Top comments (0)