why-hydration 0.1.1 → 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +81 -0
- package/README.md +56 -7
- package/dist/{chunk-CDNKC2LD.cjs → chunk-DSO337ME.cjs} +445 -91
- package/dist/chunk-DSO337ME.cjs.map +1 -0
- package/dist/{chunk-VUGHOMMU.cjs → chunk-MTAU6G26.cjs} +164 -24
- package/dist/chunk-MTAU6G26.cjs.map +1 -0
- package/dist/{chunk-ZMAUNJJR.js → chunk-NQX4TBV5.js} +159 -19
- package/dist/chunk-NQX4TBV5.js.map +1 -0
- package/dist/{chunk-WJ3ZOW4D.js → chunk-WQLUD25W.js} +445 -91
- package/dist/chunk-WQLUD25W.js.map +1 -0
- package/dist/index.cjs +17 -17
- package/dist/index.d.cts +5 -5
- package/dist/index.d.ts +5 -5
- package/dist/index.js +1 -1
- package/dist/next/index.cjs +4 -4
- package/dist/next/index.d.cts +1 -1
- package/dist/next/index.d.ts +1 -1
- package/dist/next/index.js +2 -2
- package/dist/react.cjs +4 -4
- package/dist/react.d.cts +2 -2
- package/dist/react.d.ts +2 -2
- package/dist/react.js +2 -2
- package/dist/{types-CDtDTiIa.d.cts → types-6wTih14e.d.cts} +1 -1
- package/dist/{types-CDtDTiIa.d.ts → types-6wTih14e.d.ts} +1 -1
- package/package.json +5 -4
- package/LICENSE +0 -21
- package/dist/chunk-CDNKC2LD.cjs.map +0 -1
- package/dist/chunk-VUGHOMMU.cjs.map +0 -1
- package/dist/chunk-WJ3ZOW4D.js.map +0 -1
- package/dist/chunk-ZMAUNJJR.js.map +0 -1
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.1.3
|
|
4
|
+
|
|
5
|
+
Stability + accuracy overhaul from running on a large production Next.js app.
|
|
6
|
+
Detection is now **deterministic** and reports **every** mismatch at once.
|
|
7
|
+
|
|
8
|
+
- **LCS child alignment.** Children are matched by an LCS (tag+id) instead of by
|
|
9
|
+
index, so a node the client injects mid-tree (react-toastify's
|
|
10
|
+
`<section class="Toastify">`, a portal, a modal, an ad) is treated as an
|
|
11
|
+
insertion — it no longer shifts every sibling and cascade into a different set
|
|
12
|
+
of false positives on each refresh. This makes results **consistent across
|
|
13
|
+
refreshes**.
|
|
14
|
+
- **Collect all mismatches.** The diff now returns every divergence on the page
|
|
15
|
+
(deduped by value), so the overlay shows them together instead of one-per-
|
|
16
|
+
refresh. The panel is scrollable and shows a "↓ N issues — scroll to see all"
|
|
17
|
+
hint (with a ✕ close button) when there are more than fit.
|
|
18
|
+
- **Skip client-injected containers** — toasts, modals, portals, overlays,
|
|
19
|
+
tooltips, consent banners, chat/analytics widgets (by class/role/`aria-live`)
|
|
20
|
+
are never reported and never mask a real mismatch.
|
|
21
|
+
- **Skip pending Suspense fallbacks** — content inside a streaming `<!--$?-->`
|
|
22
|
+
boundary (server `Loading…` vs client content) is expected, not a mismatch.
|
|
23
|
+
- **No duplicates** — value-based dedup means the same mismatch (e.g. two links
|
|
24
|
+
with the same conditional class) appears once.
|
|
25
|
+
- **All class/style mismatches captured.** React can emit several changes in one
|
|
26
|
+
hydration message; the parser now extracts all of them (not just the first).
|
|
27
|
+
- Adjacent delete+insert is coalesced into a single `structure` report.
|
|
28
|
+
- License: removed MIT (now `UNLICENSED`); added the author's LinkedIn.
|
|
29
|
+
|
|
30
|
+
## 0.1.2
|
|
31
|
+
|
|
32
|
+
Accuracy overhaul from running on a large production Next.js app — fixes real
|
|
33
|
+
false positives and misclassifications.
|
|
34
|
+
|
|
35
|
+
- **New `attribute-mismatch` category.** A `class`/`style` difference (e.g. a
|
|
36
|
+
conditional `forceHide` class) is now reported correctly with the exact
|
|
37
|
+
changed tokens — previously misclassified as "locale-format" because the class
|
|
38
|
+
list contained digits.
|
|
39
|
+
- **Component name + source file:line.** Reports now name the offending
|
|
40
|
+
component (e.g. `<PriceTag>`) and, where React exposes it, the source
|
|
41
|
+
`file:line`, read from React's fiber and its hydration diff.
|
|
42
|
+
- **Modern React message parsing.** React 18.3+/19 print a JSX diff tree
|
|
43
|
+
(`+`/`-` lines) instead of "Prop X did not match"; the parser now understands
|
|
44
|
+
it and extracts the attribute/value and component. This is required because
|
|
45
|
+
React does **not** patch mismatched attributes, so the DOM diff alone can't
|
|
46
|
+
see them.
|
|
47
|
+
- **Third-party noise is skipped.** Hidden ads/consent/analytics iframes (e.g.
|
|
48
|
+
Google Funding Choices `googlefcInactive`, `about:blank`) no longer produce
|
|
49
|
+
false "browser-only API" reports and no longer mask your real mismatch.
|
|
50
|
+
- **Stricter locale detection** — number-format matching now requires actual
|
|
51
|
+
numeric values, so class lists / ids with digits no longer false-match.
|
|
52
|
+
- **Reliable timing.** Detection re-checks across a short settling window
|
|
53
|
+
because React applies client values to mismatched subtrees a few hundred ms
|
|
54
|
+
after hydration; value-based dedup prevents double reports.
|
|
55
|
+
- Docs: detection scope (full load vs client navigation), `attribute-mismatch`
|
|
56
|
+
category, refreshed screenshots showing component names.
|
|
57
|
+
|
|
58
|
+
## 0.1.1
|
|
59
|
+
|
|
60
|
+
Fixes found by end-to-end testing in a real Next.js app (App Router + Pages
|
|
61
|
+
Router, React 18 & 19). **0.1.0 is broken in Next.js — use 0.1.1 or later.**
|
|
62
|
+
|
|
63
|
+
- **`'use client'` directive** is now injected into the `react` and `next`
|
|
64
|
+
bundles. Without it, `<HydrationInspector>` crashed in a Server Component
|
|
65
|
+
layout (`useRef only works in Client Components`). `why-hydration/next` is now
|
|
66
|
+
client-only; import `HydrationSnapshotScript` from `why-hydration/next/script`.
|
|
67
|
+
- **Diff skips framework comment markers** (React `<!--$-->`, RSC payload) that
|
|
68
|
+
were reported as false `viewport-branching` mismatches.
|
|
69
|
+
- **Inline `style` is normalized through the CSSOM**, so the server snapshot and
|
|
70
|
+
the browser-normalized live DOM (`#hex` → `rgb()`, spacing) no longer produce
|
|
71
|
+
a false `unknown` mismatch.
|
|
72
|
+
- **`typesVersions`** added so subpath types resolve under `moduleResolution:
|
|
73
|
+
"node"` (fixes `next build` type errors in apps using classic resolution).
|
|
74
|
+
|
|
75
|
+
Verified live: locale-format, non-deterministic-value, date-time, and
|
|
76
|
+
browser-only-api classify correctly in App Router and Pages Router; the tool is
|
|
77
|
+
a zero-code no-op in a production `next build`.
|
|
78
|
+
|
|
79
|
+
## 0.1.0
|
|
80
|
+
|
|
81
|
+
Initial release.
|
package/README.md
CHANGED
|
@@ -3,14 +3,18 @@
|
|
|
3
3
|
[](https://www.npmjs.com/package/why-hydration)
|
|
4
4
|
[](https://www.npmjs.com/package/why-hydration)
|
|
5
5
|
[](https://bundlephobia.com/package/why-hydration)
|
|
6
|
-
[](LICENSE)
|
|
7
6
|
|
|
8
7
|
📦 **npm:** https://www.npmjs.com/package/why-hydration
|
|
9
8
|
|
|
10
9
|
**Tells you which component broke hydration, what differed, and how to fix it — in dev, with zero production cost.**
|
|
11
10
|
|
|
11
|
+
A **React hydration error / hydration mismatch debugger** for **Next.js** (App
|
|
12
|
+
Router & Pages Router), **Remix**, **Vite**, and any React SSR app. It points at
|
|
13
|
+
the exact **component, file, and value** behind "Hydration failed because the
|
|
14
|
+
server rendered HTML didn't match the client" — and tells you how to fix it.
|
|
15
|
+
|
|
12
16
|
<p align="center">
|
|
13
|
-
<img src="docs/screenshots/overlay-hero.png" alt="why-hydration overlay showing
|
|
17
|
+
<img src="docs/screenshots/overlay-hero.png" alt="why-hydration overlay showing classified React hydration mismatches in a Next.js app with component names" width="440">
|
|
14
18
|
</p>
|
|
15
19
|
|
|
16
20
|
React's hydration warnings tell you _that_ something mismatched, rarely _which
|
|
@@ -28,10 +32,13 @@ the client DOM, classifies the root cause, and hands you a specific fix.
|
|
|
28
32
|
or format the value after mount.
|
|
29
33
|
```
|
|
30
34
|
|
|
31
|
-
- 🔍 **Where** — component (
|
|
35
|
+
- 🔍 **Where** — the **component name** (e.g. `<PriceTag>`) and **source file:line**,
|
|
36
|
+
read from React's own fiber/diff, plus the exact DOM node + selector path.
|
|
32
37
|
- 🔀 **What** — server value vs client value, side by side.
|
|
33
38
|
- 🧠 **Why** — the cause, classified into a known category with a confidence score.
|
|
34
39
|
- 🛠️ **Fix** — a specific, actionable suggestion with a docs link.
|
|
40
|
+
- 🧹 **Low noise** — skips third-party/extension injections (ads, consent, chat,
|
|
41
|
+
Grammarly) so you see _your_ bug, not their DOM.
|
|
35
42
|
- 🫧 **Zero prod cost** — everything is gated on `process.env.NODE_ENV` and
|
|
36
43
|
tree-shakes to a **no-op** (~0 B, size-budgeted in CI).
|
|
37
44
|
- 🌐 **Arabic-first** — digit-script mismatches (٠١٢ vs 012) are a first-class cause.
|
|
@@ -156,6 +163,25 @@ A clean page with no mismatches shows **nothing** — no overlay, no logs.
|
|
|
156
163
|
|
|
157
164
|
---
|
|
158
165
|
|
|
166
|
+
## When it detects (full load vs client navigation)
|
|
167
|
+
|
|
168
|
+
Hydration mismatches happen **only during the initial server render + hydration**
|
|
169
|
+
— i.e. a **full page load, refresh, or direct URL entry** to an SSR'd route.
|
|
170
|
+
`why-hydration` detects those.
|
|
171
|
+
|
|
172
|
+
**Client-side navigation** (Next.js `<Link>` / `router.push`, React Router) does
|
|
173
|
+
**not** re-hydrate — the destination is rendered entirely on the client, so there
|
|
174
|
+
is no server HTML to diverge from and **no hydration mismatch can occur**. This is
|
|
175
|
+
React's design, not a limitation. To reproduce a mismatch on a specific route,
|
|
176
|
+
**refresh that route** (or open its URL directly).
|
|
177
|
+
|
|
178
|
+
Detection covers both React's console warning path and a real server-vs-client
|
|
179
|
+
DOM diff, and re-checks across a short settling window (React applies client
|
|
180
|
+
values to mismatched subtrees a few hundred ms after hydration), so time- and
|
|
181
|
+
render-order-dependent mismatches are caught reliably.
|
|
182
|
+
|
|
183
|
+
---
|
|
184
|
+
|
|
159
185
|
## Options
|
|
160
186
|
|
|
161
187
|
```tsx
|
|
@@ -311,6 +337,19 @@ early third-party script before hydration.
|
|
|
311
337
|
third-party init to post-hydration.
|
|
312
338
|
**Reference:** [React `suppressHydrationWarning`](https://react.dev/reference/react-dom/components/common#suppressing-unavoidable-hydration-mismatch-errors).
|
|
313
339
|
|
|
340
|
+
<a id="cause-attribute-mismatch"></a>
|
|
341
|
+
|
|
342
|
+
### attribute-mismatch
|
|
343
|
+
|
|
344
|
+
A `class`, `style`, or other attribute differs between server and client — the
|
|
345
|
+
report lists the exact tokens (e.g. _added on client: `forceHide`_). Usually a
|
|
346
|
+
class/style applied by a client-only conditional (viewport, media query, theme,
|
|
347
|
+
or feature flag) during the first render. React does **not** patch mismatched
|
|
348
|
+
attributes, so `why-hydration` reads these from React's own diff.
|
|
349
|
+
**Fix:** render the same attribute on the server and the first client paint —
|
|
350
|
+
move the condition into `useEffect`/a mounted flag, or use CSS media queries.
|
|
351
|
+
**Reference:** [React — different client/server content](https://react.dev/reference/react-dom/client/hydrateRoot#handling-different-client-and-server-content).
|
|
352
|
+
|
|
314
353
|
<a id="cause-unknown"></a>
|
|
315
354
|
|
|
316
355
|
### unknown
|
|
@@ -388,14 +427,24 @@ field in `package.json` to match your URL.
|
|
|
388
427
|
**Can I send reports to my logging?** Yes — pass `onReport`; you receive the full
|
|
389
428
|
`HydrationReport`.
|
|
390
429
|
|
|
391
|
-
**
|
|
392
|
-
|
|
430
|
+
**Why do results now show all at once and stay the same on refresh?** The diff
|
|
431
|
+
aligns children with an LCS and collects every mismatch deterministically, so
|
|
432
|
+
injected nodes (toasts, portals, ads) can't shift the comparison and change the
|
|
433
|
+
results between refreshes.
|
|
434
|
+
|
|
435
|
+
**Does it slow my app down?** No. It's dev-only, diffs across a short settling
|
|
436
|
+
window right after hydration (React applies client values a few hundred ms
|
|
437
|
+
later), then stops — no standing observers.
|
|
393
438
|
|
|
394
439
|
## Contributing
|
|
395
440
|
|
|
396
441
|
Adding a cause category is a self-contained change — see
|
|
397
442
|
[CONTRIBUTING.md](CONTRIBUTING.md).
|
|
398
443
|
|
|
399
|
-
##
|
|
444
|
+
## Author
|
|
445
|
+
|
|
446
|
+
Built by **[Razan Aboushi](https://www.linkedin.com/in/razan-aboushi/)** ·
|
|
447
|
+
[GitHub](https://github.com/razan-aboushi) ·
|
|
448
|
+
[LinkedIn](https://www.linkedin.com/in/razan-aboushi/)
|
|
400
449
|
|
|
401
|
-
|
|
450
|
+
© 2026 Razan Aboushi. All rights reserved.
|