[Fizz] Ignore error if content node is gone (#33068)
We normally expect the segment to exist whatever the client does while streaming. However, when hydration errors at the root of the shell for a whole document render, then we clear nodes from body which can include our segments. We don't need them anymore because we switched to client rendering. It triggers an error accessing parent node which can safely be ignored. This just helps avoid confusion in this scenario. This also covers up the error in #33067. Which doesn't actually cause any visible problems other than error logging. However, ideally we wouldn't emit completeBoundary instructions if the boundary is inside a cancelled fallback.
Sebastian Markbåge committed
Apr 30, 2025 at 17:51 UTC
71797c871b6bfa45988cba38f3388bac095b26cf
2 files changed
+7
-1
packages/react-dom-bindings/src/server/fizz-instruction-set/ReactDOMFizzInstructionSetInlineCodeStrings.js
+1
-1
@@ -4,7 +4,7 @@
4
export const clientRenderBoundary =
5
'$RX=function(b,c,d,e,f){var a=document.getElementById(b);a&&(b=a.previousSibling,b.data="$!",a=a.dataset,c&&(a.dgst=c),d&&(a.msg=d),e&&(a.stck=e),f&&(a.cstck=f),b._reactRetry&&b._reactRetry())};';
6
export const completeBoundary =
7
- '$RC=function(b,d,e){d=document.getElementById(d);d.parentNode.removeChild(d);var a=document.getElementById(b);if(a){b=a.previousSibling;if(e)b.data="$!",a.setAttribute("data-dgst",e);else{e=b.parentNode;a=b.nextSibling;var f=0;do{if(a&&8===a.nodeType){var c=a.data;if("/$"===c||"/&"===c)if(0===f)break;else f--;else"$"!==c&&"$?"!==c&&"$!"!==c&&"&"!==c||f++}c=a.nextSibling;e.removeChild(a);a=c}while(a);for(;d.firstChild;)e.insertBefore(d.firstChild,a);b.data="$"}b._reactRetry&&b._reactRetry()}};';
7
+ '$RC=function(b,d,e){if(d=document.getElementById(d)){d.parentNode.removeChild(d);var a=document.getElementById(b);if(a){b=a.previousSibling;if(e)b.data="$!",a.setAttribute("data-dgst",e);else{e=b.parentNode;a=b.nextSibling;var f=0;do{if(a&&8===a.nodeType){var c=a.data;if("/$"===c||"/&"===c)if(0===f)break;else f--;else"$"!==c&&"$?"!==c&&"$!"!==c&&"&"!==c||f++}c=a.nextSibling;e.removeChild(a);a=c}while(a);for(;d.firstChild;)e.insertBefore(d.firstChild,a);b.data="$"}b._reactRetry&&b._reactRetry()}}};';
8
export const completeBoundaryWithStyles =
9
'$RM=new Map;\n$RR=function(t,u,y){function v(n){this._p=null;n()}for(var w=$RC,p=$RM,q=new Map,r=document,g,b,h=r.querySelectorAll("link[data-precedence],style[data-precedence]"),x=[],k=0;b=h[k++];)"not all"===b.getAttribute("media")?x.push(b):("LINK"===b.tagName&&p.set(b.getAttribute("href"),b),q.set(b.dataset.precedence,g=b));b=0;h=[];var l,a;for(k=!0;;){if(k){var e=y[b++];if(!e){k=!1;b=0;continue}var c=!1,m=0;var d=e[m++];if(a=p.get(d)){var f=a._p;c=!0}else{a=r.createElement("link");a.href=\nd;a.rel="stylesheet";for(a.dataset.precedence=l=e[m++];f=e[m++];)a.setAttribute(f,e[m++]);f=a._p=new Promise(function(n,z){a.onload=v.bind(a,n);a.onerror=v.bind(a,z)});p.set(d,a)}d=a.getAttribute("media");!f||d&&!matchMedia(d).matches||h.push(f);if(c)continue}else{a=x[b++];if(!a)break;l=a.getAttribute("data-precedence");a.removeAttribute("media")}c=q.get(l)||g;c===g&&(g=a);q.set(l,a);c?c.parentNode.insertBefore(a,c.nextSibling):(c=r.head,c.insertBefore(a,c.firstChild))}Promise.all(h).then(w.bind(null,\nt,u,""),w.bind(null,t,u,"Resource failed to load"))};';
10
export const completeSegment =
packages/react-dom-bindings/src/server/fizz-instruction-set/ReactDOMFizzInstructionSetShared.js
+6
@@ -49,6 +49,12 @@ export function clientRenderBoundary(
49
50
export function completeBoundary(suspenseBoundaryID, contentID, errorDigest) {
51
const contentNode = document.getElementById(contentID);
52
+ if (!contentNode) {
53
+ // If the client has failed hydration we may have already deleted the streaming
54
+ // segments. The server may also have emitted a complete instruction but cancelled
55
+ // the segment. Regardless we can ignore this case.
56
+ return;
57
+ }
58
// We'll detach the content node so that regardless of what happens next we don't leave in the tree.
59
// This might also help by not causing recalcing each time we move a child from here to the target.
60
contentNode.parentNode.removeChild(contentNode);