Benchmark
I benchmarked @ttsalpha/qrcode against the three QR libraries most React apps use: qrcode.react, react-qr-code, and qr-code-styling. I also included qrcode, the most-downloaded QR package on npm, as a headless baseline. The tests cover true cold start, SSR latency, throughput, repeated-value caching, sequential batch, bundle size, and feature completeness.
Environment: ubuntu-24.04 · Node.js v24.21.0 · ECL pinned to M · median of 3 runs · Oct 2026
Feature Comparison
Key capabilities across all five libraries, before we get to the numbers.
| Feature | @ttsalpha/qrcode | qrcode.react | qr-code-styling | react-qr-code | qrcode |
|---|---|---|---|---|---|
| Output formats | SVG · PNG | SVG · Canvas | SVG · Canvas · PNG | SVG only | SVG · Canvas · PNG |
| React component | ✓ | ✓ | — | ✓ | — |
| React Native / Expo | ✓ ./native | ✕ | ✕ | ✓ | ✓ string only |
| Logo support | URL + React node | URL | URL | — | — |
| Auto ECL for logo | ✓ auto | manual only | manual only | — | — |
| Custom dot & corner styles | ✓ | — | ✓ | — | — |
| Standalone string API | ✓ sync | — | — | — | ✓ async |
| SSR / Edge runtime | ✓ | ✓ | ✕ browser only | ✓ | ✓ |
| Error correction level | ✓ | ✓ | ✓ | ✓ | ✓ |
| QR version control | ✓ | ✓ | ✓ | — | ✓ |
| Zero dependencies | ✓ (0) | ✓ (0) | ✕ (1 dep) | ✕ (2 deps) | ✕ (3 deps) |
| TypeScript built-in | ✓ | ✓ | ✓ | ✓ | ✕ via @types |
| ESM + CJS dual export | ✓ | ✓ | ✕ | ✓ | ✕ CJS only |
| Accessibility (aria / title) | ✓ | ✓ | — | ✓ | — |
| Bundle size (gzip) | 10.8 KB | 5.9 KB | 13.5 KB | 8.3 KB | 8.5 KB |
Unique input per render, 3 s window
Each call receives a distinct URL, so no lib can benefit from caching. Higher r/s is better.
toSVGString reaches 7,817 r/s: 4.6× qrcode.react, 1.7× the headless qrcode baseline, 91× qr-code-styling. It runs synchronously with no React and no DOM, which suits server-side and batch work. The React component path, at 3,950 r/s, is 2.3× qrcode.react and 4.0× react-qr-code; against the headless baseline toSVGString is the like-for-like entry, since that path never pays for React.
Same input every render
Re-rendering one QR across requests or mounts, like receipts, kiosk screens, and shared links. @ttsalpha/qrcode ≥2.4 memoizes matrices in a 16-entry LRU, so this test is expected to favor it by design; it is kept separate from the cold-path tests above.
With both the matrix and the path cache hitting, toSVGString reaches 129,173 r/s, about 37× the headless qrcode baseline and 99× qrcode.react. No other library caches by value. This payload is also slightly longer than test 01's, so their numbers sit a notch below their cold-path throughput.
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) | 3.86 ms | 6.97 ms | 5.111 ms | 5.669 ms | 0.94 ms |
| qrcode (headless) | 22.77 ms | 25.18 ms | 8.698 ms | 9.622 ms | 1.459 ms |
| @ttsalpha/qrcode (React) | 31.31 ms | 32.07 ms | 12.355 ms | 13.338 ms | 1.75 ms |
| qrcode.react | 27.96 ms | 28.23 ms | 15.002 ms | 15.316 ms | 4.606 ms |
| react-qr-code | 32.99 ms | 35.74 ms | 17.165 ms | 18.025 ms | 9.303 ms |
| qr-code-styling | 4.54 ms | 4.68 ms | 58.171 ms | 62.904 ms | 36.346 ms |
toSVGString first-renders in 5.111 ms, 1.7× faster than the headless qrcode baseline (8.698 ms) and 2.9× faster than qrcode.react (15.002 ms). Its import is the lightest here at 3.86 ms, loaded from the ./core entry, which pulls in no React. The component path imports React and react-dom/server instead: 31.3 ms. qr-code-styling imports in 4.54 ms but first-renders in 58.2 ms, and is still at 36.3 ms on the second.
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) | 0.445 | 0.504 | 0.504 |
| @ttsalpha/qrcode (React) | 1.153 | 1.285 | 1.285 |
| qrcode (headless) | 1.8 | 1.963 | 1.963 |
| qrcode.react | 2.604 | 2.849 | 2.849 |
| react-qr-code | 3.257 | 4.239 | 4.239 |
| qr-code-styling | ✕ Not SSR-safe | — | — |
toSVGString is 4.0× faster than the headless qrcode baseline and 5.9× faster than qrcode.react across 12 mixed payloads. Tight p99 (0.504 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, so React renders run sequentially. Batch=100 (qr-code-styling: 20). 20 rounds (qr-code-styling: 10).
| Library | Batch | Median batch (ms) | p95 batch (ms) | Avg per render (ms) |
|---|---|---|---|---|
| @ttsalpha/qrcode (toSVGString) | 100 | 32.65 | 32.93 | 0.326 |
| @ttsalpha/qrcode (React) | 100 | 50.25 | 99.07 | 0.502 |
| qrcode (headless) | 100 | 69.82 | 73.77 | 0.698 |
| qrcode.react | 100 | 147.69 | 151.89 | 1.477 |
| react-qr-code | 100 | 240.15 | 253.51 | 2.401 |
| qr-code-styling | 20 | 548.67 | 572.22 | 27.433 |
toSVGString completes 100 renders in 32.65 ms median, or 0.326 ms per render. That is 2.1× faster than the headless qrcode baseline and 4.5× faster than qrcode.react. qr-code-styling takes 549 ms for just 20 renders; at that rate, 100 renders would take about 2,743 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 45× faster than qr-code-styling, and it stays SSR-safe, sync, and DOM-free while doing it. qr-code-styling needs a browser environment (a JSDOM polyfill on Node.js/Edge) with an async API that does not scale.
qrcode.react, react-qr-code, and qrcode have no styling API.
Per-type render time
500 samples each, unique input. Median per render, lower is better.
| Data type | @ttsalpha util | @ttsalpha React | qrcode.react | react-qr-code | qrcode |
|---|---|---|---|---|---|
| Short URL | 0.122 ms | 0.207 ms | 0.624 ms | 1.013 ms | 0.195 ms |
| Numeric (20 digits) | 0.081 ms | 0.191 ms | 0.455 ms | 0.942 ms | 0.123 ms |
| AlphaNumeric | 0.124 ms | 0.727 ms | 0.63 ms | 1.015 ms | 0.19 ms |
| Unicode (Japanese) | 0.172 ms | 0.268 ms | 0.799 ms | 1.346 ms | 0.321 ms |
| Long URL (120 chars) | 0.443 ms | 0.541 ms | 1.725 ms | 3.044 ms | 0.785 ms |
| vCard | 0.371 ms | 0.469 ms | 1.472 ms | 2.555 ms | 0.601 ms |
toSVGString wins on all 6 data types, and beats headless qrcode on each of them. The @ttsalpha React path is ahead of both React libraries on five of the six; alphanumeric is the exception, in both runs measured on this image. Headless qrcode is the fastest of the rest, ahead of qrcode.react and react-qr-code everywhere.
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) | 42.15 MB | 42.15 MB | 42.1 MB | 0 MB |
| @ttsalpha/qrcode (React) | 42.18 MB | 42.25 MB | 42.2 MB | −0.01 MB |
| react-qr-code | 42.16 MB | 42.24 MB | 42.14 MB | −0.01 MB |
| qrcode.react | 42.11 MB | 42.34 MB | 42.15 MB | +0.02 MB |
| qrcode (headless) | 42.18 MB | 42.33 MB | 42.2 MB | +0.02 MB |
Peak stays within 0.25 MB of baseline across 5,000 renders with unique inputs, and nothing drifts in a way that compounds. @ttsalpha/qrcode's 16-entry matrix cache holds steady: the toSVGString path drifts 0 MB, the React path −0.01 MB.
Minified + gzip, dependencies bundled
Source: bundlephobia.com. Minified + gzip, react/react-dom external, each lib's own dependencies bundled.
| Library | Min (KB) | Gzip (KB) | Runtime deps |
|---|---|---|---|
| qrcode.react | 15.9 | 5.9 | 0 |
| react-qr-code | 22.8 | 8.3 | 2 (bundled) |
| qrcode | 22.9 | 8.5 | 3 (bundled) |
| @ttsalpha/qrcode | 25.1 | 10.8 | 0 |
| qr-code-styling | 45.8 | 13.5 | 1 (bundled) |
qrcode.react stays the smallest at 5.9 KB gzip; @ttsalpha/qrcode is the largest of the four with a plain SVG pipeline, at 10.8 KB. It carries zero runtime dependencies and is tree-shakeable (sideEffects: false), so importing only the component, or only toSVGString, costs less than the figure above.
Capability comparison
| Feature | @ttsalpha/qrcode | qrcode.react | qr-code-styling | react-qr-code | qrcode |
|---|---|---|---|---|---|
| SVG output | ✓ | ✓ | ✓ | ✓ | ✓ |
| Canvas output | — | ✓ | ✓ | — | ✓ |
| PNG export | ✓ | — | ✓ | — | ✓ |
| toSVGString() — sync, no DOM | ✓ | — | — | — | — |
| React component | ✓ | ✓ | — | ✓ | — |
| React Native / Expo | ✓ | — | — | ✓ | ✓ |
| 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 | 17 / 19 | 13 / 19 | 11 / 19 | 10 / 19 | 9 / 19 |
Rankings
| Category | @ttsalpha/qrcode | qrcode.react | qr-code-styling | react-qr-code | qrcode |
|---|---|---|---|---|---|
| Throughput | #1 | #3 | ✕ | #4 | #2 |
| Repeated value | #1 | #3 | ✕ | #4 | #2 |
| True cold start | #1 | #3 | ✕ | #4 | #2 |
| SSR latency | #1 | #3 | ✕ | #4 | #2 |
| Sequential batch | #1 | #3 | ✕ | #4 | #2 |
| Styled QR | #1 | — | #2 | — | — |
| Bundle size | #4 | #1 | #5 | #2 | #3 |
| Feature score | #1 | #2 | #3 | #4 | #5 |
Reproduce
All benchmark scripts are open source. Clone and run locally.
pnpm bench # node --expose-gc benchmark.mjsnode benchmark-coldstart.mjsWhen to Choose
- Styled QR that has to survive SSR, the only library here with both
- Fastest cold start, 2.9× qrcode.react, and 4.6× its throughput
toSVGStringfor email or batch jobs, with no React and no DOM- Need the same component on React Native and Expo
- Logo as any React node
- The same QR re-rendered often, where the cache pays off
- Bundle size is the primary constraint (5.9 KB gzip)
- Targeting React 16/17 legacy projects
- Need Canvas output alongside SVG
- Simplest possible API is sufficient
- Need Canvas output or PNG export in the browser
- Browser-only, no SSR requirement
- Willing to accept ~45× slower styled render times
- Plain QR codes only, with no styling or logo
- Fewest features and slowest renders among the SSR-safe React libraries
- Headless Node.js pipelines with no React at all
- Need terminal / PNG-file output on the server
- Async-only API and no styling, so plain QR codes only