Performance

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

Source: github.com/ttsalpha/qrcode-benchmark

Feature Comparison

Key capabilities across all five libraries, before we get to the numbers.

Feature@ttsalpha/qrcodeqrcode.reactqr-code-stylingreact-qr-codeqrcode
Output formatsSVG · PNGSVG · CanvasSVG · Canvas · PNGSVG onlySVG · Canvas · PNG
React component✓✓—✓—
React Native / Expo✓ ./native✕✕✓✓ string only
Logo supportURL + React nodeURLURL——
Auto ECL for logo✓ automanual onlymanual 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 KB5.9 KB13.5 KB8.3 KB8.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.

@ttsalpha/qrcode (toSVGString)
7,817 r/s
qrcode (headless)
4,527 r/s
@ttsalpha/qrcode (React)
3,950 r/s
qrcode.react (SVG)
1,688 r/s
react-qr-code
991 r/s
qr-code-styling (async)
86 r/s

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.

@ttsalpha/qrcode (toSVGString)
129,173 r/s
@ttsalpha/qrcode (React)
12,570 r/s
qrcode (headless)
3,508 r/s
qrcode.react (SVG)
1,311 r/s
react-qr-code
745 r/s
qr-code-styling (async)
63 r/s

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.

LibraryImport (ms)Import p951st render1st p952nd render
@ttsalpha/qrcode (toSVGString)3.86 ms6.97 ms5.111 ms5.669 ms0.94 ms
qrcode (headless)22.77 ms25.18 ms8.698 ms9.622 ms1.459 ms
@ttsalpha/qrcode (React)31.31 ms32.07 ms12.355 ms13.338 ms1.75 ms
qrcode.react27.96 ms28.23 ms15.002 ms15.316 ms4.606 ms
react-qr-code32.99 ms35.74 ms17.165 ms18.025 ms9.303 ms
qr-code-styling4.54 ms4.68 ms58.171 ms62.904 ms36.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.

LibraryMedian (ms)p95 (ms)p99 (ms)
@ttsalpha/qrcode (toSVGString)0.4450.5040.504
@ttsalpha/qrcode (React)1.1531.2851.285
qrcode (headless)1.81.9631.963
qrcode.react2.6042.8492.849
react-qr-code3.2574.2394.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).

LibraryBatchMedian batch (ms)p95 batch (ms)Avg per render (ms)
@ttsalpha/qrcode (toSVGString)10032.6532.930.326
@ttsalpha/qrcode (React)10050.2599.070.502
qrcode (headless)10069.8273.770.698
qrcode.react100147.69151.891.477
react-qr-code100240.15253.512.401
qr-code-styling20548.67572.2227.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 (toSVGString)
0.251 ms
@ttsalpha/qrcode (React)
0.35 ms
qr-code-styling (async DOM)
11.314 ms

@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 Reactqrcode.reactreact-qr-codeqrcode
Short URL0.122 ms0.207 ms0.624 ms1.013 ms0.195 ms
Numeric (20 digits)0.081 ms0.191 ms0.455 ms0.942 ms0.123 ms
AlphaNumeric0.124 ms0.727 ms0.63 ms1.015 ms0.19 ms
Unicode (Japanese)0.172 ms0.268 ms0.799 ms1.346 ms0.321 ms
Long URL (120 chars)0.443 ms0.541 ms1.725 ms3.044 ms0.785 ms
vCard0.371 ms0.469 ms1.472 ms2.555 ms0.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.

LibraryBaselinePeakFinalDrift
@ttsalpha/qrcode (toSVGString)42.15 MB42.15 MB42.1 MB0 MB
@ttsalpha/qrcode (React)42.18 MB42.25 MB42.2 MB−0.01 MB
react-qr-code42.16 MB42.24 MB42.14 MB−0.01 MB
qrcode.react42.11 MB42.34 MB42.15 MB+0.02 MB
qrcode (headless)42.18 MB42.33 MB42.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.

LibraryMin (KB)Gzip (KB)Runtime deps
qrcode.react15.95.90
react-qr-code22.88.32 (bundled)
qrcode22.98.53 (bundled)
@ttsalpha/qrcode25.110.80
qr-code-styling45.813.51 (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/qrcodeqrcode.reactqr-code-stylingreact-qr-codeqrcode
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—✓✓✓✓
Score17 / 1913 / 1911 / 1910 / 199 / 19

Rankings

Category@ttsalpha/qrcodeqrcode.reactqr-code-stylingreact-qr-codeqrcode
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.

Main benchmark
pnpm bench # node --expose-gc benchmark.mjs
Cold start benchmark
node benchmark-coldstart.mjs

When 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
  • toSVGString for 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