Skip to content

[Fizz] Add "Queued" Status to SSR:ed Suspense Boundaries #33087

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
May 1, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 26 additions & 13 deletions packages/react-dom-bindings/src/client/ReactFiberConfigDOM.js
Original file line number Diff line number Diff line change
Expand Up @@ -238,14 +238,15 @@ const ACTIVITY_END_DATA = '/&';
const SUSPENSE_START_DATA = '$';
const SUSPENSE_END_DATA = '/$';
const SUSPENSE_PENDING_START_DATA = '$?';
const SUSPENSE_QUEUED_START_DATA = '$~';
const SUSPENSE_FALLBACK_START_DATA = '$!';
const PREAMBLE_CONTRIBUTION_HTML = 'html';
const PREAMBLE_CONTRIBUTION_BODY = 'body';
const PREAMBLE_CONTRIBUTION_HEAD = 'head';
const FORM_STATE_IS_MATCHING = 'F!';
const FORM_STATE_IS_NOT_MATCHING = 'F';

const DOCUMENT_READY_STATE_COMPLETE = 'complete';
const DOCUMENT_READY_STATE_LOADING = 'loading';

const STYLE = 'style';

Expand Down Expand Up @@ -1084,6 +1085,7 @@ function clearHydrationBoundary(
} else if (
data === SUSPENSE_START_DATA ||
data === SUSPENSE_PENDING_START_DATA ||
data === SUSPENSE_QUEUED_START_DATA ||
data === SUSPENSE_FALLBACK_START_DATA ||
data === ACTIVITY_START_DATA
) {
Expand Down Expand Up @@ -1205,6 +1207,7 @@ function hideOrUnhideDehydratedBoundary(
} else if (
data === SUSPENSE_START_DATA ||
data === SUSPENSE_PENDING_START_DATA ||
data === SUSPENSE_QUEUED_START_DATA ||
data === SUSPENSE_FALLBACK_START_DATA
) {
depth++;
Expand Down Expand Up @@ -3140,7 +3143,10 @@ export function canHydrateSuspenseInstance(
}

export function isSuspenseInstancePending(instance: SuspenseInstance): boolean {
return instance.data === SUSPENSE_PENDING_START_DATA;
return (
instance.data === SUSPENSE_PENDING_START_DATA ||
instance.data === SUSPENSE_QUEUED_START_DATA
);
}

export function isSuspenseInstanceFallback(
Expand All @@ -3149,7 +3155,7 @@ export function isSuspenseInstanceFallback(
return (
instance.data === SUSPENSE_FALLBACK_START_DATA ||
(instance.data === SUSPENSE_PENDING_START_DATA &&
instance.ownerDocument.readyState === DOCUMENT_READY_STATE_COMPLETE)
instance.ownerDocument.readyState !== DOCUMENT_READY_STATE_LOADING)
);
}

Expand Down Expand Up @@ -3192,15 +3198,19 @@ export function registerSuspenseInstanceRetry(
callback: () => void,
) {
const ownerDocument = instance.ownerDocument;
if (
if (instance.data === SUSPENSE_QUEUED_START_DATA) {
// The Fizz runtime has already queued this boundary for reveal. We wait for it
// to be revealed and then retries.
instance._reactRetry = callback;
} else if (
// The Fizz runtime must have put this boundary into client render or complete
// state after the render finished but before it committed. We need to call the
// callback now rather than wait
instance.data !== SUSPENSE_PENDING_START_DATA ||
// The boundary is still in pending status but the document has finished loading
// before we could register the event handler that would have scheduled the retry
// on load so we call teh callback now.
ownerDocument.readyState === DOCUMENT_READY_STATE_COMPLETE
ownerDocument.readyState !== DOCUMENT_READY_STATE_LOADING
) {
callback();
} else {
Expand Down Expand Up @@ -3255,18 +3265,19 @@ function getNextHydratable(node: ?Node) {
break;
}
if (nodeType === COMMENT_NODE) {
const nodeData = (node: any).data;
const data = (node: any).data;
if (
nodeData === SUSPENSE_START_DATA ||
nodeData === SUSPENSE_FALLBACK_START_DATA ||
nodeData === SUSPENSE_PENDING_START_DATA ||
nodeData === ACTIVITY_START_DATA ||
nodeData === FORM_STATE_IS_MATCHING ||
nodeData === FORM_STATE_IS_NOT_MATCHING
data === SUSPENSE_START_DATA ||
data === SUSPENSE_FALLBACK_START_DATA ||
data === SUSPENSE_PENDING_START_DATA ||
data === SUSPENSE_QUEUED_START_DATA ||
data === ACTIVITY_START_DATA ||
data === FORM_STATE_IS_MATCHING ||
data === FORM_STATE_IS_NOT_MATCHING
) {
break;
}
if (nodeData === SUSPENSE_END_DATA || nodeData === ACTIVITY_END_DATA) {
if (data === SUSPENSE_END_DATA || data === ACTIVITY_END_DATA) {
return null;
}
}
Expand Down Expand Up @@ -3494,6 +3505,7 @@ function getNextHydratableInstanceAfterHydrationBoundary(
data === SUSPENSE_START_DATA ||
data === SUSPENSE_FALLBACK_START_DATA ||
data === SUSPENSE_PENDING_START_DATA ||
data === SUSPENSE_QUEUED_START_DATA ||
data === ACTIVITY_START_DATA
) {
depth++;
Expand Down Expand Up @@ -3535,6 +3547,7 @@ export function getParentHydrationBoundary(
data === SUSPENSE_START_DATA ||
data === SUSPENSE_FALLBACK_START_DATA ||
data === SUSPENSE_PENDING_START_DATA ||
data === SUSPENSE_QUEUED_START_DATA ||
data === ACTIVITY_START_DATA
) {
if (depth === 0) {
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ const ACTIVITY_END_DATA = '/&';
const SUSPENSE_START_DATA = '$';
const SUSPENSE_END_DATA = '/$';
const SUSPENSE_PENDING_START_DATA = '$?';
const SUSPENSE_QUEUED_START_DATA = '$~';
const SUSPENSE_FALLBACK_START_DATA = '$!';

// TODO: Symbols that are referenced outside this module use dynamic accessor
Expand Down Expand Up @@ -106,6 +107,7 @@ export function completeBoundary(suspenseBoundaryID, contentID) {
} else if (
data === SUSPENSE_START_DATA ||
data === SUSPENSE_PENDING_START_DATA ||
data === SUSPENSE_QUEUED_START_DATA ||
data === SUSPENSE_FALLBACK_START_DATA ||
data === ACTIVITY_START_DATA
) {
Expand All @@ -132,6 +134,10 @@ export function completeBoundary(suspenseBoundaryID, contentID) {
}
}

// Mark this Suspense boundary as queued so we know not to client render it
// at the end of document load.
const suspenseNodeOuter = suspenseIdNodeOuter.previousSibling;
suspenseNodeOuter.data = SUSPENSE_QUEUED_START_DATA;
// Queue this boundary for the next batch
window['$RB'].push(suspenseIdNodeOuter, contentNodeOuter);

Expand Down Expand Up @@ -261,6 +267,14 @@ export function completeBoundaryWithStyles(
}
}

const suspenseIdNodeOuter = document.getElementById(suspenseBoundaryID);
if (suspenseIdNodeOuter) {
// Mark this Suspense boundary as queued so we know not to client render it
// at the end of document load.
const suspenseNodeOuter = suspenseIdNodeOuter.previousSibling;
suspenseNodeOuter.data = SUSPENSE_QUEUED_START_DATA;
}

Promise.all(dependencies).then(
window['$RC'].bind(null, suspenseBoundaryID, contentID),
window['$RX'].bind(null, suspenseBoundaryID, 'CSS failed to load'),
Expand Down
Loading