@samitouri / QOS-React-2 / commits / fa50caf5f8

[Fizz] Unify preamble only fields to save a field (#35068)

Stacked on #35067. Same idea of saving a field on the SuspenseBoundary in the common case. The case where they can have a preamble is rare.

Sebastian Markbåge committed Nov 7, 2025 at 09:19 UTC fa50caf5f84bb8976aa1dbff7f15a821e44e7af7
1 file changed +31 -29
packages/react-server/src/ReactFizzServer.js
+31 -29
@@ -255,8 +255,7 @@ type SuspenseBoundary = {
255 fallbackAbortableTasks: Set<Task>, // used to cancel task on the fallback if the boundary completes or gets canceled.
256 contentState: HoistableState,
257 fallbackState: HoistableState,
258 - contentPreamble: null | Preamble,
259 - fallbackPreamble: null | Preamble,
258 + preamble: null | Preamble,
259 tracked: null | {
260 contentKeyPath: null | KeyNode, // used to track the path for replay nodes
261 fallbackNode: null | ReplayNode, // used to track the fallback for replay nodes
@@ -275,7 +274,7 @@ type RenderTask = {
274 ping: () => void,
275 blockedBoundary: Root | SuspenseBoundary,
276 blockedSegment: Segment, // the segment we'll write to
278 - blockedPreamble: null | Preamble,
277 + blockedPreamble: null | PreambleState,
278 hoistableState: null | HoistableState, // Boundary state we'll mutate while rendering. This may not equal the state of the blockedBoundary
279 abortSet: Set<Task>, // the abortable set that this task belongs to
280 keyPath: Root | KeyNode, // the path of all parent keys currently rendering
@@ -398,7 +397,17 @@ export opaque type Request = {
397 didWarnForKey?: null | WeakSet<ComponentStackNode>,
398 };
399
401 -type Preamble = PreambleState;
400 +type Preamble = {
401 + content: PreambleState,
402 + fallback: PreambleState,
403 +};
404 +
405 +function createPreamble(): Preamble {
406 + return {
407 + content: createPreambleState(),
408 + fallback: createPreambleState(),
409 + };
410 +}
411
412 // This is a default heuristic for how to split up the HTML content into progressive
413 // loading. Our goal is to be able to display additional new content about every 500ms.
@@ -466,7 +475,7 @@ function isEligibleForOutlining(
475 // For boundaries that can possibly contribute to the preamble we don't want to outline
476 // them regardless of their size since the fallbacks should only be emitted if we've
477 // errored the boundary.
469 - boundary.contentPreamble === null
478 + boundary.preamble === null
479 );
480 }
481
@@ -786,8 +795,7 @@ function createSuspenseBoundary(
795 request: Request,
796 row: null | SuspenseListRow,
797 fallbackAbortableTasks: Set<Task>,
789 - contentPreamble: null | Preamble,
790 - fallbackPreamble: null | Preamble,
798 + preamble: null | Preamble,
799 defer: boolean,
800 ): SuspenseBoundary {
801 const boundary: SuspenseBoundary = {
@@ -803,8 +811,7 @@ function createSuspenseBoundary(
811 errorDigest: null,
812 contentState: createHoistableState(),
813 fallbackState: createHoistableState(),
806 - contentPreamble,
807 - fallbackPreamble,
814 + preamble,
815 tracked: null,
816 };
817 if (__DEV__) {
@@ -838,7 +845,7 @@ function createRenderTask(
845 childIndex: number,
846 blockedBoundary: Root | SuspenseBoundary,
847 blockedSegment: Segment,
841 - blockedPreamble: null | Preamble,
848 + blockedPreamble: null | PreambleState,
849 hoistableState: null | HoistableState,
850 abortSet: Set<Task>,
851 keyPath: Root | KeyNode,
@@ -1290,8 +1297,7 @@ function renderSuspenseBoundary(
1297 request,
1298 task.row,
1299 fallbackAbortSet,
1293 - createPreambleState(),
1294 - createPreambleState(),
1300 + createPreamble(),
1301 defer,
1302 );
1303 } else {
@@ -1300,7 +1306,6 @@ function renderSuspenseBoundary(
1306 task.row,
1307 fallbackAbortSet,
1308 null,
1303 - null,
1309 defer,
1310 );
1311 }
@@ -1365,7 +1370,8 @@ function renderSuspenseBoundary(
1370 }
1371
1372 task.blockedSegment = boundarySegment;
1368 - task.blockedPreamble = newBoundary.fallbackPreamble;
1373 + task.blockedPreamble =
1374 + newBoundary.preamble === null ? null : newBoundary.preamble.fallback;
1375 task.keyPath = fallbackKeyPath;
1376 task.formatContext = getSuspenseFallbackFormatContext(
1377 request.resumableState,
@@ -1409,7 +1415,7 @@ function renderSuspenseBoundary(
1415 -1,
1416 newBoundary,
1417 contentRootSegment,
1412 - newBoundary.contentPreamble,
1418 + newBoundary.preamble === null ? null : newBoundary.preamble.content,
1419 newBoundary.contentState,
1420 task.abortSet,
1421 keyPath,
@@ -1440,7 +1446,8 @@ function renderSuspenseBoundary(
1446 // context switching. We just need to temporarily switch which boundary and which segment
1447 // we're writing to. If something suspends, it'll spawn new suspended task with that context.
1448 task.blockedBoundary = newBoundary;
1443 - task.blockedPreamble = newBoundary.contentPreamble;
1449 + task.blockedPreamble =
1450 + newBoundary.preamble === null ? null : newBoundary.preamble.content;
1451 task.hoistableState = newBoundary.contentState;
1452 task.blockedSegment = contentRootSegment;
1453 task.keyPath = keyPath;
@@ -1548,7 +1555,7 @@ function renderSuspenseBoundary(
1555 -1,
1556 parentBoundary,
1557 boundarySegment,
1551 - newBoundary.fallbackPreamble,
1558 + newBoundary.preamble === null ? null : newBoundary.preamble.fallback,
1559 newBoundary.fallbackState,
1560 fallbackAbortSet,
1561 fallbackKeyPath,
@@ -1602,8 +1609,7 @@ function replaySuspenseBoundary(
1609 request,
1610 task.row,
1611 fallbackAbortSet,
1605 - createPreambleState(),
1606 - createPreambleState(),
1612 + createPreamble(),
1613 defer,
1614 );
1615 } else {
@@ -1612,7 +1618,6 @@ function replaySuspenseBoundary(
1618 task.row,
1619 fallbackAbortSet,
1620 null,
1615 - null,
1621 defer,
1622 );
1623 }
@@ -4372,7 +4377,7 @@ function erroredTask(
4377 if (
4378 request.pendingRootTasks === 0 &&
4379 request.trackedPostpones === null &&
4375 - boundary.contentPreamble !== null
4380 + boundary.preamble !== null
4381 ) {
4382 // The root is complete and this boundary may contribute part of the preamble.
4383 // We eagerly attempt to prepare the preamble here because we expect most requests
@@ -4414,7 +4419,6 @@ function abortRemainingSuspenseBoundary(
4419 null,
4420 new Set(),
4421 null,
4417 - null,
4422 false,
4423 );
4424 resumedBoundary.parentFlushed = true;
@@ -4894,7 +4898,7 @@ function finishedTask(
4898 if (
4899 request.pendingRootTasks === 0 &&
4900 request.trackedPostpones === null &&
4897 - boundary.contentPreamble !== null
4901 + boundary.preamble !== null
4902 ) {
4903 // The root is complete and this boundary may contribute part of the preamble.
4904 // We eagerly attempt to prepare the preamble here because we expect most requests
@@ -5271,10 +5275,8 @@ function preparePreambleFromSegment(
5275 );
5276 }
5277
5274 - const preamble = boundary.contentPreamble;
5275 - const fallbackPreamble = boundary.fallbackPreamble;
5276 -
5277 - if (preamble === null || fallbackPreamble === null) {
5278 + const preamble = boundary.preamble;
5279 + if (preamble === null) {
5280 // This boundary cannot have a preamble so it can't block the flushing of
5281 // the preamble.
5282 return false;
@@ -5286,7 +5288,7 @@ function preparePreambleFromSegment(
5288 case COMPLETED: {
5289 // This boundary is complete. It might have inner boundaries which are pending
5290 // and able to provide a preamble so we have to check it's children
5289 - hoistPreambleState(request.renderState, preamble);
5291 + hoistPreambleState(request.renderState, preamble.content);
5292 // We track this boundary's byteSize on the request since it will always flush with
5293 // the request since it may contribute to the preamble
5294 request.byteSize += boundary.byteSize;
@@ -5315,7 +5317,7 @@ function preparePreambleFromSegment(
5317 case CLIENT_RENDERED: {
5318 if (segment.status === COMPLETED) {
5319 // This boundary is errored so if it contains a preamble we should include it
5318 - hoistPreambleState(request.renderState, fallbackPreamble);
5320 + hoistPreambleState(request.renderState, preamble.fallback);
5321 return preparePreambleFromSubtree(
5322 request,
5323 segment,