diff --git a/packages/react-dom/src/__tests__/ReactDOMFizzServer-test.js b/packages/react-dom/src/__tests__/ReactDOMFizzServer-test.js
index 06e83ba128be3..3ced96af29564 100644
--- a/packages/react-dom/src/__tests__/ReactDOMFizzServer-test.js
+++ b/packages/react-dom/src/__tests__/ReactDOMFizzServer-test.js
@@ -495,6 +495,10 @@ describe('ReactDOMFizzServer', () => {
pipe(writable);
});
expect(getVisibleChildren(container)).toEqual(
Loading...
);
+ // Because there is no content inside the Suspense boundary that could've
+ // been written, we expect to not see any additional partial data flushed
+ // yet.
+ expect(container.firstChild.nextSibling).toBe(null);
await act(async () => {
resolveElement({default: });
});
diff --git a/packages/react-server/src/ReactFizzServer.js b/packages/react-server/src/ReactFizzServer.js
index 7a238391493a3..c09d21cb55cf5 100644
--- a/packages/react-server/src/ReactFizzServer.js
+++ b/packages/react-server/src/ReactFizzServer.js
@@ -488,7 +488,7 @@ function renderSuspenseBoundary(
// We use the safe form because we don't handle suspending here. Only error handling.
renderNode(request, task, content);
contentRootSegment.status = COMPLETED;
- newBoundary.completedSegments.push(contentRootSegment);
+ queueCompletedSegment(newBoundary, contentRootSegment);
if (newBoundary.pendingTasks === 0) {
// This must have been the last segment we were waiting on. This boundary is now complete.
// Therefore we won't need the fallback. We early return so that we don't have to create
@@ -1430,6 +1430,29 @@ function abortTask(task: Task): void {
}
}
+function queueCompletedSegment(
+ boundary: SuspenseBoundary,
+ segment: Segment,
+): void {
+ if (
+ segment.chunks.length === 0 &&
+ segment.children.length === 1 &&
+ segment.children[0].boundary === null
+ ) {
+ // This is an empty segment. There's nothing to write, so we can instead transfer the ID
+ // to the child. That way any existing references point to the child.
+ const childSegment = segment.children[0];
+ childSegment.id = segment.id;
+ childSegment.parentFlushed = true;
+ if (childSegment.status === COMPLETED) {
+ queueCompletedSegment(boundary, childSegment);
+ }
+ } else {
+ const completedSegments = boundary.completedSegments;
+ completedSegments.push(segment);
+ }
+}
+
function finishedTask(
request: Request,
boundary: Root | SuspenseBoundary,
@@ -1463,7 +1486,7 @@ function finishedTask(
// If it is a segment that was aborted, we'll write other content instead so we don't need
// to emit it.
if (segment.status === COMPLETED) {
- boundary.completedSegments.push(segment);
+ queueCompletedSegment(boundary, segment);
}
}
if (boundary.parentFlushed) {
@@ -1482,8 +1505,8 @@ function finishedTask(
// If it is a segment that was aborted, we'll write other content instead so we don't need
// to emit it.
if (segment.status === COMPLETED) {
+ queueCompletedSegment(boundary, segment);
const completedSegments = boundary.completedSegments;
- completedSegments.push(segment);
if (completedSegments.length === 1) {
// This is the first time since we last flushed that we completed anything.
// We can schedule this boundary to emit its partially completed segments early