A whole shop page400 product tilesn = 400low cardinality
A real product listing: responsive grid, a sale badge (color by discount), a wishlist toggle, a truncated title, a dynamic rating bar, optional struck-through price and an out-of-stock add-to-cart — plus (hover:hover)-guarded hover, :focus-visible rings, WCAG ::before tap targets, a @container query per tile, reduced-motion handling and a11y semantics. Tailwind fires ~8 cn() per tile.
Source · generated HTML · generated CSS · rendered preview
SSR render throughput — renders / sec · higher is betteriHow many times per second this lane renders the whole workload to an HTML string in Node (renderToString), timing the production render only — any build-time CSS collection (a Tailwind JIT, a Panda sheet slice) is excluded. Higher is better.
SSR throughput under load — requests / sec · higher is betteriRequests/sec the lane sustains under concurrent HTTP load (autocannon) serving the SSR render end-to-end — a more realistic server measure than the in-process microbench. Higher is better.
Where the SSR render time goes — CPU self-time · median ms / renderiThe median server renderToString(), split by CPU self-time from a sampled V8 profile mapped through source maps: react-dom (the floor every lane shares), the styling library's runtime, and your component. other is GC / unattributed native work.
Where the client hydration time goes — CPU self-time · median msiTime for React to hydrate the server HTML in the browser — attach event handlers and build the fiber tree over the existing DOM (it does not re-create markup). Measured as the single hydration commit, split by CPU self-time into react-dom, the styling library's client runtime, and your component. other is browser-native / GC work V8 can't attribute to a package. Lower is better.
Where the interaction time goes — in-place re-render · median msiA state change triggers a synchronous re-render (flushSync) of the whole mounted workload, then we wait for the next paint — click→paint latency. Split by CPU self-time per package. This is where runtime CSS-in-JS libraries re-run their per-element styling on every update; build-time lanes (next-yak / Panda / Tailwind / vanilla) do almost none. other is browser-native / GC. Lower is better.
Where the cold-mount time goes — blank screen → first render · median msiStarting from a blank root (no SSR markup), a "click" renders the whole workload from scratch (createRoot().render()), then we wait for the first paint. Unlike hydration — which attaches to existing server HTML — this is a cold client mount, so the first paint includes each runtimelibrary's first style injection into the document. Split by CPU self-time into react-dom, thestyling library, and your component; other is browser-native / GC. Lower is better.
Page bytes shipped — JS + CSS + HTML, gzipped · lower is betteriGzipped bytes the browser downloads for this page: the client JS runtime the lane ships (over the bare React floor), the CSS, and the SSR HTML. Lower is better.
Scaling — SSR render time (ms) vs instance countiSSR render time as the workload grows from a handful to thousands of instances — shows how each lane's per-element cost compounds. A flatter line scales better.
A real componentthe DenseButtonn = 1,000low cardinality
A real-project button with pseudo-states (:hover/:focus-visible/:active/:disabled), a 992px responsive flip, a ::before WCAG target-size, composed style fragments and an icon child — rendered 1,000×. Not a toy 4-class button: this is what real buttons cost.
Source · generated HTML · generated CSS · rendered preview
SSR render throughput — renders / sec · higher is betteriHow many times per second this lane renders the whole workload to an HTML string in Node (renderToString), timing the production render only — any build-time CSS collection (a Tailwind JIT, a Panda sheet slice) is excluded. Higher is better.
SSR throughput under load — requests / sec · higher is betteriRequests/sec the lane sustains under concurrent HTTP load (autocannon) serving the SSR render end-to-end — a more realistic server measure than the in-process microbench. Higher is better.
Where the SSR render time goes — CPU self-time · median ms / renderiThe median server renderToString(), split by CPU self-time from a sampled V8 profile mapped through source maps: react-dom (the floor every lane shares), the styling library's runtime, and your component. other is GC / unattributed native work.
Where the client hydration time goes — CPU self-time · median msiTime for React to hydrate the server HTML in the browser — attach event handlers and build the fiber tree over the existing DOM (it does not re-create markup). Measured as the single hydration commit, split by CPU self-time into react-dom, the styling library's client runtime, and your component. other is browser-native / GC work V8 can't attribute to a package. Lower is better.
Where the interaction time goes — in-place re-render · median msiA state change triggers a synchronous re-render (flushSync) of the whole mounted workload, then we wait for the next paint — click→paint latency. Split by CPU self-time per package. This is where runtime CSS-in-JS libraries re-run their per-element styling on every update; build-time lanes (next-yak / Panda / Tailwind / vanilla) do almost none. other is browser-native / GC. Lower is better.
Where the cold-mount time goes — blank screen → first render · median msiStarting from a blank root (no SSR markup), a "click" renders the whole workload from scratch (createRoot().render()), then we wait for the first paint. Unlike hydration — which attaches to existing server HTML — this is a cold client mount, so the first paint includes each runtimelibrary's first style injection into the document. Split by CPU self-time into react-dom, thestyling library, and your component; other is browser-native / GC. Lower is better.
Page bytes shipped — JS + CSS + HTML, gzipped · lower is betteriGzipped bytes the browser downloads for this page: the client JS runtime the lane ships (over the bare React floor), the CSS, and the SSR HTML. Lower is better.
Scaling — SSR render time (ms) vs instance countiSSR render time as the workload grows from a handful to thousands of instances — shows how each lane's per-element cost compounds. A flatter line scales better.
A real Tabs component150 groupsn = 150low cardinality
A real design-system Tabs: responsive typography, the full active/hover/focus-visible/disabled state matrix, an animated active underline via CSS anchor positioning (with a per-tab ::after fallback gated on @supports), a ::before WCAG tap target, hidden-scrollbar overflow and a composed FullWidthTabs wrapper. Tailwind needs a ~40-token list per tab; next-yak compiles it all at build time.
Source · generated HTML · generated CSS · rendered preview
SSR render throughput — renders / sec · higher is betteriHow many times per second this lane renders the whole workload to an HTML string in Node (renderToString), timing the production render only — any build-time CSS collection (a Tailwind JIT, a Panda sheet slice) is excluded. Higher is better.
SSR throughput under load — requests / sec · higher is betteriRequests/sec the lane sustains under concurrent HTTP load (autocannon) serving the SSR render end-to-end — a more realistic server measure than the in-process microbench. Higher is better.
Where the SSR render time goes — CPU self-time · median ms / renderiThe median server renderToString(), split by CPU self-time from a sampled V8 profile mapped through source maps: react-dom (the floor every lane shares), the styling library's runtime, and your component. other is GC / unattributed native work.
Where the client hydration time goes — CPU self-time · median msiTime for React to hydrate the server HTML in the browser — attach event handlers and build the fiber tree over the existing DOM (it does not re-create markup). Measured as the single hydration commit, split by CPU self-time into react-dom, the styling library's client runtime, and your component. other is browser-native / GC work V8 can't attribute to a package. Lower is better.
Where the interaction time goes — in-place re-render · median msiA state change triggers a synchronous re-render (flushSync) of the whole mounted workload, then we wait for the next paint — click→paint latency. Split by CPU self-time per package. This is where runtime CSS-in-JS libraries re-run their per-element styling on every update; build-time lanes (next-yak / Panda / Tailwind / vanilla) do almost none. other is browser-native / GC. Lower is better.
Where the cold-mount time goes — blank screen → first render · median msiStarting from a blank root (no SSR markup), a "click" renders the whole workload from scratch (createRoot().render()), then we wait for the first paint. Unlike hydration — which attaches to existing server HTML — this is a cold client mount, so the first paint includes each runtimelibrary's first style injection into the document. Split by CPU self-time into react-dom, thestyling library, and your component; other is browser-native / GC. Lower is better.
Page bytes shipped — JS + CSS + HTML, gzipped · lower is betteriGzipped bytes the browser downloads for this page: the client JS runtime the lane ships (over the bare React floor), the CSS, and the SSR HTML. Lower is better.
Scaling — SSR render time (ms) vs instance countiSSR render time as the workload grows from a handful to thousands of instances — shows how each lane's per-element cost compounds. A flatter line scales better.
Variant / state buttonn = 1,000low cardinality
A button rendered 1,000× cycling ~12 distinct class strings (variant × active × fullWidth). With so few repeated strings almost every cn() is a cache hit (nearly free), while wrapper-component libraries still run their machinery per instance — the case where a cached merger is hard to beat.
Source · generated HTML · generated CSS · rendered preview
SSR render throughput — renders / sec · higher is betteriHow many times per second this lane renders the whole workload to an HTML string in Node (renderToString), timing the production render only — any build-time CSS collection (a Tailwind JIT, a Panda sheet slice) is excluded. Higher is better.
SSR throughput under load — requests / sec · higher is betteriRequests/sec the lane sustains under concurrent HTTP load (autocannon) serving the SSR render end-to-end — a more realistic server measure than the in-process microbench. Higher is better.
Where the SSR render time goes — CPU self-time · median ms / renderiThe median server renderToString(), split by CPU self-time from a sampled V8 profile mapped through source maps: react-dom (the floor every lane shares), the styling library's runtime, and your component. other is GC / unattributed native work.
Where the client hydration time goes — CPU self-time · median msiTime for React to hydrate the server HTML in the browser — attach event handlers and build the fiber tree over the existing DOM (it does not re-create markup). Measured as the single hydration commit, split by CPU self-time into react-dom, the styling library's client runtime, and your component. other is browser-native / GC work V8 can't attribute to a package. Lower is better.
Where the interaction time goes — in-place re-render · median msiA state change triggers a synchronous re-render (flushSync) of the whole mounted workload, then we wait for the next paint — click→paint latency. Split by CPU self-time per package. This is where runtime CSS-in-JS libraries re-run their per-element styling on every update; build-time lanes (next-yak / Panda / Tailwind / vanilla) do almost none. other is browser-native / GC. Lower is better.
Where the cold-mount time goes — blank screen → first render · median msiStarting from a blank root (no SSR markup), a "click" renders the whole workload from scratch (createRoot().render()), then we wait for the first paint. Unlike hydration — which attaches to existing server HTML — this is a cold client mount, so the first paint includes each runtimelibrary's first style injection into the document. Split by CPU self-time into react-dom, thestyling library, and your component; other is browser-native / GC. Lower is better.
Page bytes shipped — JS + CSS + HTML, gzipped · lower is betteriGzipped bytes the browser downloads for this page: the client JS runtime the lane ships (over the bare React floor), the CSS, and the SSR HTML. Lower is better.
Scaling — SSR render time (ms) vs instance countiSSR render time as the workload grows from a handful to thousands of instances — shows how each lane's per-element cost compounds. A flatter line scales better.
Composed components (3 levels)n = 1,000low cardinality
A Button wrapped by two more components, each adding styles and threading className down. next-yak flattens the chain at build time (depth ≈ free); the Tailwind lanes pay one merge per level and styled-components/Emotion run a wrapper component at each.
Source · generated HTML · generated CSS · rendered preview
SSR render throughput — renders / sec · higher is betteriHow many times per second this lane renders the whole workload to an HTML string in Node (renderToString), timing the production render only — any build-time CSS collection (a Tailwind JIT, a Panda sheet slice) is excluded. Higher is better.
SSR throughput under load — requests / sec · higher is betteriRequests/sec the lane sustains under concurrent HTTP load (autocannon) serving the SSR render end-to-end — a more realistic server measure than the in-process microbench. Higher is better.
Where the SSR render time goes — CPU self-time · median ms / renderiThe median server renderToString(), split by CPU self-time from a sampled V8 profile mapped through source maps: react-dom (the floor every lane shares), the styling library's runtime, and your component. other is GC / unattributed native work.
Where the client hydration time goes — CPU self-time · median msiTime for React to hydrate the server HTML in the browser — attach event handlers and build the fiber tree over the existing DOM (it does not re-create markup). Measured as the single hydration commit, split by CPU self-time into react-dom, the styling library's client runtime, and your component. other is browser-native / GC work V8 can't attribute to a package. Lower is better.
Where the interaction time goes — in-place re-render · median msiA state change triggers a synchronous re-render (flushSync) of the whole mounted workload, then we wait for the next paint — click→paint latency. Split by CPU self-time per package. This is where runtime CSS-in-JS libraries re-run their per-element styling on every update; build-time lanes (next-yak / Panda / Tailwind / vanilla) do almost none. other is browser-native / GC. Lower is better.
Where the cold-mount time goes — blank screen → first render · median msiStarting from a blank root (no SSR markup), a "click" renders the whole workload from scratch (createRoot().render()), then we wait for the first paint. Unlike hydration — which attaches to existing server HTML — this is a cold client mount, so the first paint includes each runtimelibrary's first style injection into the document. Split by CPU self-time into react-dom, thestyling library, and your component; other is browser-native / GC. Lower is better.
Page bytes shipped — JS + CSS + HTML, gzipped · lower is betteriGzipped bytes the browser downloads for this page: the client JS runtime the lane ships (over the bare React floor), the CSS, and the SSR HTML. Lower is better.
Scaling — SSR render time (ms) vs instance countiSSR render time as the workload grows from a handful to thousands of instances — shows how each lane's per-element cost compounds. A flatter line scales better.
Dynamic valuetranslateX (the naive way)n = 1,000high cardinality
1,000 elements each with a unique translateX. Baking the value into the class name produces a brand-new class string every render that a merger can't cache, and styled-components emits a CSS rule per value — while next-yak/Panda turn it into a CSS variable, so their per-instance work stays constant.