Skip to content

Commit 0dba1c6

Browse files
authored
Allow SSR to finish microtasky work before flushing (#86311)
In a dynamic Fizz (SSR) render, rendering work is initially scheduled using microtasks, and a flush will be attempted when the stream gets pulled: https://github.com/facebook/react/blob/8ac5f4eb3601f7381462f8b74ecf24d47259cc20/packages/react-dom/src/server/ReactDOMFizzServerNode.js#L386-L389 This can cause streaming metadata to flake between appearing in `<head>` and `<body>` depending on if whether it had enough microtasks to render before the flush occurred. In general, if something is async, but available after a couple microtasks, then we shouldn't bother streaming it, it should just go into the initial HTML. We achieve this by waiting a task before allowing the stream to get pulled, which allows all the microtasky work to finish without delaying the response too much.
1 parent f0a1a69 commit 0dba1c6

File tree

2 files changed

+12
-3
lines changed

2 files changed

+12
-3
lines changed

packages/next/src/server/stream-utils/node-web-streams-helper.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,11 @@ import type { ReactDOMServerReadableStream } from 'react-dom/server'
22
import { getTracer } from '../lib/trace/tracer'
33
import { AppRenderSpan } from '../lib/trace/constants'
44
import { DetachedPromise } from '../../lib/detached-promise'
5-
import { scheduleImmediate, atLeastOneTask } from '../../lib/scheduler'
5+
import {
6+
scheduleImmediate,
7+
atLeastOneTask,
8+
waitAtLeastOneReactRenderTask,
9+
} from '../../lib/scheduler'
610
import { ENCODED_TAGS } from './encoded-tags'
711
import {
812
indexOfUint8Array,
@@ -797,9 +801,13 @@ export async function continueFizzStream(
797801
// Suffix itself might contain close tags at the end, so we need to split it.
798802
const suffixUnclosed = suffix ? suffix.split(CLOSE_TAG, 1)[0] : null
799803

800-
// If we're generating static HTML we need to wait for it to resolve before continuing.
801804
if (isStaticGeneration) {
805+
// If we're generating static HTML we need to wait for it to resolve before continuing.
802806
await renderStream.allReady
807+
} else {
808+
// Otherwise, we want to make sure Fizz is done with all microtasky work
809+
// before we start pulling the stream and cause a flush.
810+
await waitAtLeastOneReactRenderTask()
803811
}
804812

805813
return chainTransformers(renderStream, [

test/e2e/app-dir/metadata-icons/app/custom-icon/delay-icons/page.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,9 @@ export default async function Page() {
1818

1919
export async function generateMetadata() {
2020
await connection()
21+
// Delay until after the shell is flushed so that the tags end up in the body.
22+
await new Promise((resolve) => setTimeout(resolve, 10))
2123
return {
22-
// This long text description will lead to the metadata being inserted after the head tag.
2324
description: 'long text description'.repeat(1000),
2425
icons: {
2526
icon: `/heart.png`,

0 commit comments

Comments
 (0)