Skip to main content

The Problem with JavaScript Framework Benchmarks

·PkgPulse Team

TL;DR

"Framework X is 10x faster than React" is almost always comparing toy apps under artificial conditions. The classic JS framework benchmarks (js-framework-benchmark, TodoMVC) test operations like "create 1000 rows" — workloads that don't exist in real apps. Your app performance is 90% determined by: bundle size delivered to users, time-to-interactive, API latency, and how well you avoid unnecessary re-renders. Not which framework you chose. Here's how to benchmark what actually matters.

Key Takeaways

  • Synthetic benchmarks test toy apps — rarely relevant to production workload
  • The DOM is the bottleneck — your JavaScript is not the slow part
  • React is "slow" in benchmarks because it's general-purpose, not because it's bad
  • Solid/Svelte/Qwik are fast in benchmarks — also fast in apps, but not 10x in practice
  • What to actually measure: LCP, INP, bundle size, API latency, perceived performance

What the Famous Benchmarks Actually Test

The js-framework-benchmark (krausest/js-framework-benchmark):
→ The most cited benchmark for framework performance
→ Tests: create 1000 rows, update 10 rows, swap 2 rows, select a row, delete a row
→ Measures: operation time in milliseconds

Results (typical, approximate):
Vanilla JS:    1.0x  (baseline)
Solid:         1.1x  (very close to vanilla)
Svelte:        1.2x
Inferno:       1.1x
Preact:        1.3x
React:         1.8x
Vue 3:         1.7x
Angular:       2.2x

Implication claimed: "React is 80% slower than optimal!"
Actual implication: React adds ~1ms to a 1000-row table render.

The test creates a <table> with 1000 rows.
No real app looks like this.
No user interaction involves 1000 simultaneous DOM mutations.
The benchmark tests the edge case, not the common case.

Why React "Loses" Benchmarks (And Why It Doesn't Matter)

React's design choices that cost benchmark performance:

1. Virtual DOM diffing
   → React builds a JS representation of the DOM, then diffs it
   → Solid/Svelte compile to direct DOM updates (no diffing)
   → Diffing adds overhead for large updates
   → For small updates (typical UI): difference is unmeasurable

2. Generality over specificity
   → React's reconciler handles any component tree update
   → Solid uses fine-grained reactivity (subscribes to specific values)
   → React's approach is more flexible but has more overhead per operation
   → Solid's approach is faster but requires more discipline from the developer

3. Fiber architecture (deliberate trade-off)
   → React's scheduler enables interrupting and resuming rendering
   → This is what enables Suspense, Transitions, useDeferredValue
   → The overhead from React's scheduler is the cost of these features
   → The benchmark doesn't use these features — so it looks "slow"

What the benchmark misses:
→ Bundle size (React's virtual DOM is small; what matters is your app code)
→ Streaming (React Server Components, Suspense)
→ Developer productivity (slower to build = slower to ship)
→ Error handling and edge cases
→ Real user interaction patterns

In practice:
A Solid app and a React app rendering a typical dashboard:
→ Both render in <16ms (60fps threshold)
→ User cannot perceive the difference
→ The benchmark difference is real but imperceptible

What Actually Makes Apps Slow

If your app is slow, it's almost certainly one of these:

1. JavaScript bundle size (most common)
   → 1MB of JS = 10 seconds parse time on a low-end phone
   → The framework is 20-100KB. Your app code is 500KB-2MB.
   → Fix: code splitting, lazy loading, tree shaking
   → Framework choice matters here: Svelte (0KB runtime) < React (45KB) < Angular (150KB)

2. Blocking API calls
   → User clicks "Load data", waits 800ms for API response
   → User perception: "app is slow"
   → Fix: streaming, optimistic updates, server-side rendering
   → Framework choice doesn't affect API latency

3. Render blocking resources
   → CSS in <head> that hasn't loaded yet
   → Fonts that aren't preloaded
   → Third-party scripts that block parsing
   → Fix: <link rel="preload">, lazy script loading, CSP

4. Layout thrashing (JavaScript)
   → Reading layout properties (getBoundingClientRect) then writing
   → Causes multiple reflows
   → Fix: separate reads and writes, use requestAnimationFrame
   → Framework doesn't matter here — it's your component code

5. Unoptimized images
   → 2MB uncompressed PNG where 50KB WebP would do
   → Fix: next/image, astro:image, sharp processing
   → Zero relationship to framework benchmarks

The framework benchmark measures:
→ How fast the framework updates DOM
→ This matters when: you're updating thousands of DOM nodes per second
→ Real apps that do this: stock tickers, data grids, real-time dashboards
→ Real apps that don't do this: ~95% of all apps

How to Actually Benchmark Your App

# Real performance metrics to track:

# 1. Core Web Vitals (what Google measures)
# LCP (Largest Contentful Paint): < 2.5 seconds
# INP (Interaction to Next Paint): < 200ms
# CLS (Cumulative Layout Shift): < 0.1

# Measure with:
npx lighthouse https://your-app.com --view
# Shows all Core Web Vitals + opportunities

# Real device testing (most important):
# Use Chrome DevTools → CPU throttling 4x + Network slow 3G
# Test on actual low-end Android device
# This reveals what your framework benchmark score never shows

# 2. JavaScript bundle size (biggest lever)
# Build your app, analyze the bundle:

# Vite:
ANALYZE=true npx vite build
# Or add rollup-plugin-visualizer:
npm install -D rollup-plugin-visualizer
# Add to vite.config.ts plugins, run build, open stats.html

# Next.js:
ANALYZE=true npx next build
# (install @next/bundle-analyzer first)

# What you'll find:
# → Your "framework" is probably 20-50KB
# → Your date library (if not tree-shaken) is 300KB
# → A chart library you use on one page loads on all pages
# → This is where to focus, not framework choice

# 3. Measure actual frame rate during interaction
# Chrome DevTools → Performance → Record while interacting
# Look for: long tasks (>50ms), layout recalculations
# A "slow" React component that runs in 2ms is not your problem

The Benchmark That Actually Matters for Framework Choice

// The real benchmark: developer productivity

// Question: How long does it take to build a feature?

// React — feature development time: ~1x (baseline, large ecosystem)
// → Lots of libraries, lots of examples, lots of Stack Overflow answers
// → Type system with TypeScript is excellent
// → Server Components reduce client JS significantly

// Svelte — feature development time: ~0.9x (slightly faster)
// → Less boilerplate: no useState, no useEffect boilerplate
// → But: fewer examples, smaller ecosystem, need to learn more yourself

// Vue — feature development time: ~0.9x
// → Options API is beginner-friendly
// → Composition API is excellent TypeScript
// → Strong in Asia (huge community), growing globally

// Solid — feature development time: ~1.2x
// → Fine-grained reactivity requires understanding how it works
// → Fewer libraries, smaller community
// → Worth it? Only if you're hitting React's performance limits

// The honest comparison:
// If you're hitting the js-framework-benchmark's use case in production,
// Solid/Svelte are genuinely better choices.
// If you're not (95% of apps), React's ecosystem advantage > performance gap.

// "But React is 1.8x slower in the benchmark!"
// At 60fps, you have 16ms per frame.
// React processes typical UI in 1-3ms.
// Solid processes the same UI in 0.5-1.5ms.
// Users cannot perceive the difference.

The One Benchmark Worth Paying Attention To

Bundle size is the benchmark that maps directly to user experience:

Initial JS bundle size → Time to Interactive on real devices
(Because JS must be downloaded, parsed, AND executed before the app is interactive)

Gzipped JS bundle (framework only):
Svelte: ~2KB runtime (compiles to vanilla JS)
Solid: ~7KB
Preact: ~4KB
Vue 3: ~33KB
React + ReactDOM: ~45KB
Angular: ~150KB

In a real app: these numbers are dominated by your application code.
A medium React app is typically:
→ React: 45KB
→ Your application code: 200-500KB
→ Libraries: 50-200KB

Total: 300-750KB
Framework: ~6% of total bundle

Switching from React to Svelte saves ~45KB.
Cutting lodash saves ~70KB.
Lazy-loading one heavy route saves ~100KB.

The framework's bundle contribution is real but not the biggest lever.
Code splitting your routes has 2-5x more impact than switching frameworks.

The benchmark worth running: npm run build, check dist/ sizes,
look at route-level code splitting, look at what libraries you're bundling.
That's where to optimize. Not "did I pick Solid over React?"

Compare React, Solid, Svelte, and other frameworks on bundle size and health at PkgPulse.

Comments

Stay Updated

Get the latest package insights, npm trends, and tooling tips delivered to your inbox.