Benchmark
I benchmarked @ttsalpha/qrcode against the three most popular React QR libraries: qrcode.react, qr-code-styling, and react-qr-code — covering true cold start, SSR latency, throughput, sequential batch, bundle size, and feature completeness.
Environment: ubuntu-latest · Node.js v24.16.0 · ECL pinned to M · median / p95 / p99 · June 2026
Feature Comparison
Key capabilities across all four libraries — before we look at numbers.
| Feature | @ttsalpha/qrcode | qrcode.react | qr-code-styling | react-qr-code |
|---|---|---|---|---|
| Output formats | SVG · PNG | SVG · Canvas | SVG · Canvas · PNG | SVG only |
| Logo support | Image URL + React node | Image URL | Image URL | — |
| Auto ECL for logo | ✓ auto | manual only | manual only | — |
| Custom dot & corner styles | ✓ | — | ✓ | — |
| Standalone string API | ✓ | — | — | — |
| SSR / Edge runtime | ✓ | ✓ | ✕ browser only | ✓ |
| Error correction level | ✓ | ✓ | ✓ | ✓ |
| QR version control | ✓ | ✓ | ✓ | — |
| Zero dependencies | ✓ (0) | ✓ (0) | ✕ (1 dep) | ✕ (2 deps) |
| TypeScript built-in | ✓ | ✓ | ✓ | ✓ |
| ESM + CJS dual export | ✓ | ✓ | ✕ | ✓ |
| Accessibility (aria / title) | ✓ | ✓ | — | ✓ |
| Bundle size (gzip) | 8.7 KB | 6.1 KB | 13.8 KB | ∼45 KB (w/deps) |
Unique input per render, 3 s window
Each call receives a distinct URL — no lib can benefit from caching. Higher r/s is better.
toSVGString reaches 2,126 r/s — 1.2× faster than qrcode.react and 27× faster than qr-code-styling. Runs sync with no React or DOM overhead, ideal for server-side and batch workloads.
Fresh process per round via child_process.fork
10 rounds each. Measures real Lambda / edge cold-start: import + first render with zero JIT warmup.
| Library | Import (ms) | Import p95 | 1st render | 1st p95 | 2nd render |
|---|---|---|---|---|---|
| @ttsalpha/qrcode (toSVGString) | 30.25 ms | 32.8 ms | 7.026 ms | 7.244 ms | 1.369 ms |
| @ttsalpha/qrcode (React) | 29.99 ms | 30.95 ms | 15.41 ms | 16.228 ms | 2.795 ms |
| qrcode.react | 27.59 ms | 27.96 ms | 14.817 ms | 17.839 ms | 4.732 ms |
| react-qr-code | 32.34 ms | 35.32 ms | 14.921 ms | 17.573 ms | 8.274 ms |
| qr-code-styling | 4.55 ms | 4.85 ms | 58 ms | 60.281 ms | 37.103 ms |
toSVGString first-renders in 7.026 ms — 2× faster than qrcode.react (14.817 ms). qr-code-styling has the fastest import (4.55 ms) but the slowest first render (58 ms), and stays slow at 37 ms on the second render because its DOM-based async pipeline does not JIT-warm effectively.
Real-world payloads
12 varied payloads (short URL, long URL, vCard, numeric, WiFi, mailto, tel…), 10 rounds, p99 included.
| Library | Median (ms) | p95 (ms) | p99 (ms) |
|---|---|---|---|
| @ttsalpha/qrcode (toSVGString) | 1.256 | 1.272 | 1.272 |
| @ttsalpha/qrcode (React) | 2.013 | 2.286 | 2.286 |
| qrcode.react | 2.41 | 2.839 | 2.839 |
| react-qr-code | 3.288 | 4.129 | 4.129 |
| qr-code-styling | ✕ Not SSR-safe | — | — |
toSVGString is 1.9× faster than qrcode.react and 2.6× faster than react-qr-code across 12 mixed payloads. Tight p99 (1.272 ms) means latency stays predictable even with complex inputs like vCard or WiFi configs.
Burst of N renders, single thread
Node.js is single-threaded — React renders run sequentially. Batch=100 (qr-code-styling: 20). 10 rounds.
| Library | Batch | Median batch (ms) | p95 batch (ms) | Avg per render (ms) |
|---|---|---|---|---|
| @ttsalpha/qrcode (toSVGString) | 100 | 115.4 | 115.98 | 1.154 |
| qrcode.react | 100 | 142.91 | 146.42 | 1.429 |
| @ttsalpha/qrcode (React) | 100 | 187.7 | 191.3 | 1.877 |
| react-qr-code | 100 | 230.86 | 237.74 | 2.309 |
| qr-code-styling | 20 | 551.79 | 578.37 | 27.59 |
toSVGString completes 100 renders in 115.4 ms median (1.154 ms/render) — 1.24× faster than qrcode.react. qr-code-styling takes 552 ms for just 20 renders; at that rate, 100 renders would take ~2,760 ms.
Custom dot shapes + corner styles
ECL=H (logo-safe), size=512 px. Only @ttsalpha/qrcode and qr-code-styling support custom styling.
@ttsalpha/qrcode renders styled QR codes 20× faster than qr-code-styling — while remaining SSR-safe, sync, and DOM-free. qr-code-styling requires a browser environment (JSDOM polyfill for Node.js/Edge) with an async API that does not scale.
qrcode.react and react-qr-code have no styling API.
Per-type render time
500 samples each, p99 included. Lower is better.
| Data type | @ttsalpha util | @ttsalpha React | qrcode.react | react-qr-code |
|---|---|---|---|---|
| Short URL | 0.447 ms | 0.928 ms | 0.55 ms | 1.001 ms |
| Numeric (20 digits) | 0.264 ms | 0.724 ms | 0.425 ms | 1.003 ms |
| AlphaNumeric | 0.447 ms | 0.92 ms | 0.595 ms | 0.955 ms |
| Unicode (Japanese) | 0.665 ms | 0.834 ms | 0.797 ms | 1.354 ms |
| Long URL (120 chars) | 1.744 ms | 2.377 ms | 1.732 ms | 3.054 ms |
| vCard | 1.453 ms | 2.032 ms | 1.443 ms | 2.592 ms |
toSVGString wins on 4 of 6 data types. qrcode.react edges ahead on long URL (1.732 ms vs 1.744 ms) and vCard (1.443 ms vs 1.453 ms) — both within 1%, negligible in practice.
5,000 renders, unique input
Heap sampled at baseline, peak, and final. Near-zero drift across all libraries.
| Library | Baseline | Peak | Final | Drift |
|---|---|---|---|---|
| @ttsalpha/qrcode (toSVGString) | 44.54 MB | 44.55 MB | 44.56 MB | +0.02 MB |
| @ttsalpha/qrcode (React) | 44.53 MB | 44.61 MB | 44.55 MB | +0.02 MB |
| react-qr-code | 44.58 MB | 44.62 MB | 44.57 MB | −0.01 MB |
| qrcode.react | 44.63 MB | 44.72 MB | 44.56 MB | −0.07 MB |
All libraries show excellent memory behavior — peak stays within 0.18 MB of baseline across 5,000 renders with unique inputs. No signs of leaks in any library.
gzip + dependencies
| Library | Min (KB) | Gzip (KB) | Dependencies | Actual total |
|---|---|---|---|---|
| qrcode.react | 16.3 | 6.1 | 0 | 16.3 KB |
| @ttsalpha/qrcode | 28.5 | 8.7 | 0 | 28.5 KB |
| react-qr-code | 23.4 | 8.5 | 2 | ~45 KB ⚠️ |
| qr-code-styling | 46.9 | 13.8 | 1 | 46.9 KB |
react-qr-code ships 23 KB but silently pulls in qrcode-generator (~40 KB) and prop-types (~1 KB) — nearly double the apparent size.
Capability comparison
| Feature | @ttsalpha/qrcode | qrcode.react | qr-code-styling | react-qr-code |
|---|---|---|---|---|
| SVG output | ✓ | ✓ | ✓ | ✓ |
| Canvas output | — | ✓ | ✓ | — |
| PNG export | ✓ | — | ✓ | — |
| toSVGString() — sync, no DOM | ✓ | — | — | — |
| SSR / Edge runtime safe | ✓ | ✓ | ✕ | ✓ |
| Zero dependencies | ✓ | ✓ | — | — |
| Dot shape styles | ✓ | — | ✓ | — |
| Corner styles | ✓ | — | ✓ | — |
| Logo — image URL | ✓ | ✓ | ✓ | — |
| Logo — any React node | ✓ | — | — | — |
| Error correction level | ✓ | ✓ | ✓ | ✓ |
| QR version control | ✓ | ✓ | ✓ | — |
| TypeScript built-in | ✓ | ✓ | ✓ | ✓ |
| ESM + CJS dual export | ✓ | ✓ | — | ✓ |
| Accessibility (aria / title) | ✓ | ✓ | — | ✓ |
| React 18+ support | ✓ | ✓ | ✓ | ✓ |
| React 16 / 17 support | — | ✓ | ✓ | ✓ |
| Score | 15 / 17 | 12 / 17 | 11 / 17 | 8 / 17 |
Rankings
| Category | @ttsalpha/qrcode | qrcode.react | qr-code-styling | react-qr-code |
|---|---|---|---|---|
| Throughput | #1 | #2 | ✕ | #3 |
| True cold start | #1 | #2 | ✕ | #3 |
| SSR latency | #1 | #2 | ✕ | #3 |
| Sequential batch | #1 | #2 | ✕ | #3 |
| Styled QR | #1 | — | #2 | — |
| Bundle size | #2 | #1 | #3 | #4 |
| Feature score | #1 | #2 | #3 | #4 |
Reproduce
All benchmark scripts are open source. Clone and run locally.
pnpm bench # node --expose-gc benchmark.mjsnode benchmark-coldstart.mjsWhen to Choose
- Best overall for React 18+
- Need the fastest true cold start — 2× faster than qrcode.react in a fresh process
- Need
toSVGStringfor SSR, email templates, or batch generation - Need styled QR that works server-side
- Need logo as any React component
- Want zero dependencies
- Bundle size is the primary constraint (6.1 KB gzip)
- Targeting React 16/17 legacy projects
- Need Canvas output alongside SVG
- Simplest possible API is sufficient
- Browser-only, no SSR requirement
- Willing to accept ~20× slower styled render times
- No strong reason to choose over the others
- Fewest features, slowest renders
- Hidden bundle cost from dependencies