fix: broaden captureExistingTree to find React root in Next.js App Router

Next.js 13+ App Router calls hydrateRoot on <html> or <body> directly
(not on a #__next div), so __reactFiber$ annotations land on
document.documentElement or document.body.

Changes:
- Add document.documentElement to the candidate list (covers App Router)
- Extract getFiber() helper to remove repeated key-scan loop
- Add DOM-wide fallback scan (querySelectorAll('*')) so any React app
  is found regardless of its root container convention
- Remove the early !tree bail-out — post FIBER_TREE_UPDATE even with a
  sparse tree, matching onCommitFiberRoot's behaviour

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
SinachPat
2026-05-07 10:52:27 +01:00
co-authored by Claude Sonnet 4.6
parent 8bbee987ff
commit f707ecd7b3
+24 -10
View File
@@ -923,28 +923,44 @@ export function buildProxyFiberHookScript(): string {
// Attempt 2 (2 s delay) hook ran before hydration or Suspense deferred. // Attempt 2 (2 s delay) hook ran before hydration or Suspense deferred.
function captureExistingTree() { function captureExistingTree() {
// Find any DOM element that React has annotated with a fiber reference. // Find any DOM element that React has annotated with a fiber reference.
// Priority order covers the most common React root containers:
// - Next.js Pages Router: #__next
// - CRA / Vite: #root
// - Generic: #app
// - Next.js App Router: <html> or <body> (hydrateRoot on document)
// - Fallback DOM scan: first annotated element anywhere in <body>
var candidates = [ var candidates = [
document.getElementById('__next'), document.getElementById('__next'),
document.getElementById('root'), document.getElementById('root'),
document.getElementById('app'), document.getElementById('app'),
document.documentElement, // <html> — App Router hydrateRoot target
document.body, document.body,
]; ];
var fiber = null; function getFiber(el) {
for (var ci = 0; ci < candidates.length; ci++) { if (!el) return null;
var el = candidates[ci];
if (!el) continue;
var keys = Object.keys(el); var keys = Object.keys(el);
for (var ki = 0; ki < keys.length; ki++) { for (var ki = 0; ki < keys.length; ki++) {
if (keys[ki].indexOf('__reactFiber$') === 0) { if (keys[ki].indexOf('__reactFiber$') === 0) return el[keys[ki]];
fiber = el[keys[ki]];
break;
} }
return null;
} }
var fiber = null;
for (var ci = 0; ci < candidates.length; ci++) {
fiber = getFiber(candidates[ci]);
if (fiber) break; if (fiber) break;
} }
if (!fiber) return; // React not yet mounted on any known container. // Last resort: walk the DOM looking for any annotated element.
if (!fiber) {
var allEls = document.body ? document.body.querySelectorAll('*') : [];
for (var di = 0; di < allEls.length && !fiber; di++) {
fiber = getFiber(allEls[di]);
}
}
if (!fiber) return; // React not yet mounted anywhere in the document.
// Walk up to the HostRoot (the sentinel fiber React builds the tree from). // Walk up to the HostRoot (the sentinel fiber React builds the tree from).
var f = fiber; var f = fiber;
@@ -954,8 +970,6 @@ export function buildProxyFiberHookScript(): string {
nodeMap = {}; nodeMap = {};
fiberMap = new WeakMap(); fiberMap = new WeakMap();
var tree = serializeFiber(f, ''); var tree = serializeFiber(f, '');
if (!tree) return; // Nothing serializable yet.
reapplyOverrides(); reapplyOverrides();
post({ type: 'FIBER_TREE_UPDATE', root: tree }); post({ type: 'FIBER_TREE_UPDATE', root: tree });
if (selectedNodeId) updateHighlight(); if (selectedNodeId) updateHighlight();