Skip to content

Commit 144ffc8

Browse files
committed
docs(perf): save lighthouse shim-vs-react baseline (2026-04-20)
30-run median comparison across home / docs / blog × desktop / mobile, served via `pnpm start:prod` from the same tree with `tanstackDom()` on vs off. Performance-score parity, consistent FCP win for shim, modest LCP regression on RSC-heavy pages, effectively zero CLS/TBT post- hydration fix. Saved as a durable reference so we can re-measure on a consistent baseline the next time we ship a shim perf change or want to flag drift against CrUX field data.
1 parent ce4d57a commit 144ffc8

File tree

1 file changed

+85
-0
lines changed

1 file changed

+85
-0
lines changed
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
# Lighthouse: @tanstack/dom-vite shim vs. real React
2+
3+
**Date:** 2026-04-20
4+
**tanstack.com commit at time of measurement:** `fb806bb` (shim build with `@tanstack/dom-vite@0.1.0-alpha.5`, pulling `@tanstack/react-dom@0.1.0-alpha.4` — includes the RSC deferred-hydration adoption fix landed the same day).
5+
**React baseline build:** same source tree with `tanstackDom()` plugin removed from `vite.config.ts` and `serverVariantAliases` dropped — i.e. stock `react@19.2.3` / `react-dom@19.2.3`.
6+
7+
## TL;DR
8+
9+
- **Performance score: parity** (±2 across pages / form factors, within run-to-run noise).
10+
- **FCP: consistent shim win** everywhere — ~4% on home, ~11–17% on docs / blog. Smaller main-thread parse cost lets first paint land sooner.
11+
- **LCP: shim regresses on RSC-heavy pages, desktop** — the LCP element on docs / blog pages lives in the Flight-streamed subtree, and the shim's `use(pendingPromise)` + deferred-resume adds latency vs. React's battle-tested RSC client. Mobile is mostly parity (network-bound anyway).
12+
- **TBT / CLS: effectively zero on both** after the same-day RSC hydration fix — no duplicate DOM, no layout shift from appending.
13+
- **Bundle (raw JS): −4.7%** on tanstack.com (-980 KB of 21 MB total client JS). Modest because router / store / app code dominates; shim only replaces React's share.
14+
15+
## Methodology
16+
17+
1. `pnpm build` for each variant.
18+
2. `PORT=4000 pnpm start:prod` to serve from `dist/server/server.js` on `http://localhost:4000`.
19+
3. **5 trials × 3 URLs × 2 form factors = 30 Lighthouse runs per variant** using `npx lighthouse` v13 with `--only-categories=performance` and headless Chrome.
20+
4. Mobile runs use Lighthouse's default emulation (slow 4G + 4× CPU slowdown). Desktop uses `--preset=desktop` (no throttling).
21+
5. Medians reported below.
22+
23+
## Medians
24+
25+
### Performance score
26+
27+
| URL | form | React | Shim | Δ |
28+
| --- | :-: | -: | -: | -: |
29+
| `/` | desktop | 99 | 99 | 0 |
30+
| `/` | mobile | 87 | 88 | +1 |
31+
| `/query/latest/docs/framework/react/guides/queries` | desktop | 96 | 96 | 0 |
32+
| `/query/latest/docs/framework/react/guides/queries` | mobile | 64 | 66 | +2 |
33+
| `/blog/react-server-components` | desktop | 98 | 96 | −2 |
34+
| `/blog/react-server-components` | mobile | 70 | 71 | +1 |
35+
36+
### Web Vitals
37+
38+
| URL | form | FCP (R → S) | LCP (R → S) | TBT (R → S) | CLS (R → S) | TTI (R → S) |
39+
| --- | :-: | :-: | :-: | :-: | :-: | :-: |
40+
| `/` | desktop | 0.61s → 0.59s (−4%) | 0.84s → 0.91s (+8%) | 0ms → 0ms | 0 → 0 | 0.84s → 0.92s |
41+
| `/` | mobile | 2.34s → 2.31s | 3.71s → 3.60s (−3%) | 19ms → 20ms | 0 → 0 | 5.54s → 5.55s |
42+
| `/query/.../queries` | desktop | 1.05s → 0.92s (−13%) | 1.05s → 1.24s (+18%) | 0ms → 0ms | 0 → 0 | 1.05s → 1.24s |
43+
| `/query/.../queries` | mobile | 4.66s → 4.13s (−11%) | 6.62s → 6.39s (−3%) | 17ms → 19ms | 0 → 0 | 8.36s → 8.41s |
44+
| `/blog/react-server-components` | desktop | 0.90s → 0.74s (−17%) | 0.90s → 1.29s (+43%) | 0ms → 0ms | 0 → 0 | 0.90s → 1.29s |
45+
| `/blog/react-server-components` | mobile | 3.73s → 3.21s (−14%) | 5.32s → 6.23s (+17%) | 34ms → 21ms (−37%) | 0 → 0 | 6.24s → 6.57s |
46+
47+
### Bundle size (uncompressed total client JS)
48+
49+
| Build | Total client JS | Notes |
50+
| --- | -: | --- |
51+
| Real React | 21,052 KB | Dedicated `react-*.js` chunk = 176 KB (`manualChunks` splits `node_modules/react{,-dom}/`) |
52+
| Shim | 20,072 KB | No dedicated react chunk; shim code inlines into `app-shell` (+16 KB there). Net **−980 KB (−4.7%)** |
53+
54+
## Caveats
55+
56+
- **Lab data only.** Chrome origin-level CWV (CrUX) needs ~28 days of real traffic before aggregates stabilize. Since the shim only went live on `2026-04-20`, field data won't be comparable for a month.
57+
- **`pnpm start:prod` serves from Node locally — no CDN.** Absolute TTFB numbers are dev-machine noise (5ms–1s range depending on cold-cache loader work); anchor on client-side metrics.
58+
- **Per-page LCP percentages can look dramatic when the absolute value is small.** Blog desktop LCP `0.90s → 1.29s` is +390 ms — real, but a sub-second LCP regression in both states is still a Core Web Vitals "Good" rating (<2.5s).
59+
- **Single-node prod server — no edge, no warm cache.** Mobile Lighthouse runs with 4× CPU throttling are inherently high-variance.
60+
61+
## Reproduce
62+
63+
```bash
64+
# React baseline
65+
# 1) temporarily remove tanstackDom() plugin + serverVariantAliases in vite.config.ts
66+
pnpm build
67+
PORT=4000 pnpm start:prod &
68+
# run 5 trials × 3 URLs × 2 form factors, save JSON to ./react/
69+
70+
# Shim
71+
# 2) restore tanstackDom() plugin + serverVariantAliases
72+
pnpm build
73+
PORT=4000 pnpm start:prod &
74+
# re-run, save JSON to ./shim/
75+
76+
# Aggregate medians + delta (parse JSON, compute median of numericValues per audit key)
77+
```
78+
79+
See the shim side for the runner + aggregator scripts used (`/tmp/lh-compare/run.sh`, `/tmp/lh-compare/aggregate.mjs` at measurement time).
80+
81+
## Related shim work shipped with this comparison
82+
83+
- `@tanstack/react-dom@0.1.0-alpha.4`: `renderFunction`'s deferred-hydration branch now mirrors `renderLazy`'s ancestor-Suspense guard (`_awaitingLazyHydration`). Fixes duplicate-markup on RSC pages. Regression test: `tests/rsc-hydration-adopt.test.tsx`.
84+
- `@tanstack/react-dom-server@0.1.0-alpha.4`: shell-chunk batching in `streamHtml` (reduces Node stream overhead ~3–4% on SSR bench).
85+
- `@tanstack/dom-vite@0.1.0-alpha.5`: dep bump to pick up react-dom@alpha.4.

0 commit comments

Comments
 (0)