[Fizz] Unify prerender only fields to save a field (#35067)
I need to regain a field because the SuspenseBoundary type is already at 16 fields in prod, after which it deopts v8. There are two fields that are only used in prerender to track postpones. These are ripe to be split into an optional object so that they only take up one field when they're not used.
Sebastian Markbåge committed
Nov 7, 2025 at 09:18 UTC
1e986f514f7fdf73d013bf6595a47dfcb0cf2da7
1 file changed
+30
-14
packages/react-server/src/ReactFizzServer.js
+30
-14
@@ -257,8 +257,10 @@ type SuspenseBoundary = {
257
fallbackState: HoistableState,
258
contentPreamble: null | Preamble,
259
fallbackPreamble: null | Preamble,
260
- trackedContentKeyPath: null | KeyNode, // used to track the path for replay nodes
261
- trackedFallbackNode: null | ReplayNode, // used to track the fallback for replay nodes
260
+ tracked: null | {
261
+ contentKeyPath: null | KeyNode, // used to track the path for replay nodes
262
+ fallbackNode: null | ReplayNode, // used to track the fallback for replay nodes
263
+ },
264
errorDigest: ?string, // the error hash if it errors
265
// DEV-only fields
266
errorMessage?: null | string, // the error string if it errors
@@ -803,8 +805,7 @@ function createSuspenseBoundary(
805
fallbackState: createHoistableState(),
806
contentPreamble,
807
fallbackPreamble,
806
- trackedContentKeyPath: null,
807
- trackedFallbackNode: null,
808
+ tracked: null,
809
};
810
if (__DEV__) {
811
// DEV-only fields for hidden class
@@ -1303,9 +1304,6 @@ function renderSuspenseBoundary(
1304
defer,
1305
);
1306
}
1306
- if (request.trackedPostpones !== null) {
1307
- newBoundary.trackedContentKeyPath = keyPath;
1308
- }
1307
1308
const insertionIndex = parentSegment.chunks.length;
1309
// The children of the boundary segment is actually the fallback.
@@ -1358,9 +1356,12 @@ function renderSuspenseBoundary(
1356
null,
1357
];
1358
trackedPostpones.workingMap.set(fallbackKeyPath, fallbackReplayNode);
1361
- // We are rendering the fallback before the boundary content so we keep track of
1362
- // the fallback replay node until we determine if the primary content suspends
1363
- newBoundary.trackedFallbackNode = fallbackReplayNode;
1359
+ newBoundary.tracked = {
1360
+ contentKeyPath: keyPath,
1361
+ // We are rendering the fallback before the boundary content so we keep track of
1362
+ // the fallback replay node until we determine if the primary content suspends
1363
+ fallbackNode: fallbackReplayNode,
1364
+ };
1365
}
1366
1367
task.blockedSegment = boundarySegment;
@@ -3793,14 +3794,21 @@ function trackPostponedBoundary(
3794
// it before flushing and we know that we can't inline it.
3795
boundary.rootSegmentID = request.nextSegmentId++;
3796
3796
- const boundaryKeyPath = boundary.trackedContentKeyPath;
3797
+ const tracked = boundary.tracked;
3798
+ if (tracked === null) {
3799
+ throw new Error(
3800
+ 'It should not be possible to postpone at the root. This is a bug in React.',
3801
+ );
3802
+ }
3803
+
3804
+ const boundaryKeyPath = tracked.contentKeyPath;
3805
if (boundaryKeyPath === null) {
3806
throw new Error(
3807
'It should not be possible to postpone at the root. This is a bug in React.',
3808
);
3809
}
3810
3803
- const fallbackReplayNode = boundary.trackedFallbackNode;
3811
+ const fallbackReplayNode = tracked.fallbackNode;
3812
3813
const children: Array<ReplayNode> = [];
3814
const boundaryNode: void | ReplayNode =
@@ -3853,7 +3861,11 @@ function trackPostpone(
3861
trackedPostpones,
3862
boundary,
3863
);
3856
- if (boundary.trackedContentKeyPath === keyPath && task.childIndex === -1) {
3864
+ if (
3865
+ boundary.tracked !== null &&
3866
+ boundary.tracked.contentKeyPath === keyPath &&
3867
+ task.childIndex === -1
3868
+ ) {
3869
// Assign ID
3870
if (segment.id === -1) {
3871
if (segment.parentFlushed) {
@@ -3950,7 +3962,11 @@ function untrackBoundary(request: Request, boundary: SuspenseBoundary) {
3962
if (trackedPostpones === null) {
3963
return;
3964
}
3953
- const boundaryKeyPath = boundary.trackedContentKeyPath;
3965
+ const tracked = boundary.tracked;
3966
+ if (tracked === null) {
3967
+ return;
3968
+ }
3969
+ const boundaryKeyPath = tracked.contentKeyPath;
3970
if (boundaryKeyPath === null) {
3971
return;
3972
}