182
disableDefaultPropsExceptForClasses,
183
enableAsyncIterableChildren,
184
enableViewTransition,
185
+ enableFizzBlockingRender,
186
} from 'shared/ReactFeatureFlags';
187
188
import assign from 'shared/assign';
419
// 500 * 1024 / 8 * .8 * 0.5 / 2
420
const DEFAULT_PROGRESSIVE_CHUNK_SIZE = 12800;
421
422
+function getBlockingRenderMaxSize(request: Request): number {
423
+ // We want to make sure that we can block the reveal of a well designed complete
424
+ // shell but if you have constructed a too large shell (e.g. by not adding any
425
+ // Suspense boundaries) then that might take too long to render. We shouldn't
426
+ // punish users (or overzealous metrics tracking) in that scenario.
427
+ // There's a trade off here. If this limit is too low then you can't fit a
428
+ // reasonably well built UI within it without getting errors. If it's too high
429
+ // then things that accidentally fall below it might take too long to load.
430
+ // Web Vitals target 1.8 seconds for first paint and our goal to have the limit
431
+ // be fast enough to hit that. For this argument we assume that most external
432
+ // resources are already cached because it's a return visit, or inline styles.
433
+ // If it's not, then it's highly unlikely that any render blocking instructions
434
+ // we add has any impact what so ever on the paint.
435
+ // Assuming a first byte of about 600ms which is kind of bad but common with a
436
+ // decent static host. If it's longer e.g. due to dynamic rendering, then you
437
+ // are going to bound by dynamic production of the content and you're better off
438
+ // with Suspense boundaries anyway. This number doesn't matter much. Then you
439
+ // have about 1.2 seconds left for bandwidth. On 3G that gives you about 112.5kb
440
+ // worth of data. That's worth about 10x in terms of uncompressed bytes. Then we
441
+ // half that just to account for longer latency, slower bandwidth and CPU processing.
442
+ // Now we're down to about 500kb. In fact, looking at metrics we've collected with
443
+ // rel="expect" examples and other documents, the impact on documents smaller than
444
+ // that is within the noise. That's because there's enough happening within that
445
+ // start up to not make HTML streaming not significantly better.
446
+ // Content above the fold tends to be about 100-200kb tops. Therefore 500kb should
447
+ // be enough head room for a good loading state. After that you should use
448
+ // Suspense or SuspenseList to improve it.
449
+ // Since this is highly related to the reason you would adjust the
450
+ // progressiveChunkSize option, and always has to be higher, we define this limit
451
+ // in terms of it. So if you want to increase the limit because you have high
452
+ // bandwidth users, then you can adjust it up. If you are concerned about even
453
+ // slower bandwidth then you can adjust it down.
454
+ return request.progressiveChunkSize * 40; // 512kb by default.
455
+}
456
+
457
function isEligibleForOutlining(
458
request: Request,
459
boundary: SuspenseBoundary,
5512
destination: Destination,
5513
rootSegment: Segment,
5514
preambleSegments: Array<Array<Segment>>,
5515
+ skipBlockingShell: boolean,
5516
) {
5517
// The preamble is ready.
5481
- writePreambleStart(destination, request.resumableState, request.renderState);
5518
+ writePreambleStart(
5519
+ destination,
5520
+ request.resumableState,
5521
+ request.renderState,
5522
+ skipBlockingShell,
5523
+ );
5524
for (let i = 0; i < preambleSegments.length; i++) {
5525
const segments = preambleSegments[i];
5526
for (let j = 0; j < segments.length; j++) {
5930
5931
flushedByteSize = request.byteSize; // Start counting bytes
5932
// TODO: Count the size of the preamble chunks too.
5933
+ let skipBlockingShell = false;
5934
+ if (enableFizzBlockingRender) {
5935
+ const blockingRenderMaxSize = getBlockingRenderMaxSize(request);
5936
+ if (flushedByteSize > blockingRenderMaxSize) {
5937
+ skipBlockingShell = true;
5938
+ const maxSizeKb = Math.round(blockingRenderMaxSize / 1000);
5939
+ const error = new Error(
5940
+ 'This rendered a large document (>' +
5941
+ maxSizeKb +
5942
+ ') without any Suspense ' +
5943
+ 'boundaries around most of it. That can delay initial paint longer than ' +
5944
+ 'necessary. To improve load performance, add a <Suspense> or <SuspenseList> ' +
5945
+ 'around the content you expect to be below the header or below the fold. ' +
5946
+ 'In the meantime, the content will deopt to paint arbitrary incomplete ' +
5947
+ 'pieces of HTML.',
5948
+ );
5949
+ const errorInfo: ThrownInfo = {};
5950
+ logRecoverableError(request, error, errorInfo, null);
5951
+ }
5952
+ }
5953
flushPreamble(
5954
request,
5955
destination,
5956
completedRootSegment,
5957
completedPreambleSegments,
5958
+ skipBlockingShell,
5959
);
5960
flushSegment(request, destination, completedRootSegment, null);
5961
request.completedRootSegment = null;