diff --git a/packages/next/src/server/stream-utils/node-web-streams-helper.ts b/packages/next/src/server/stream-utils/node-web-streams-helper.ts index b69f522336775..0df3d16cd26b8 100644 --- a/packages/next/src/server/stream-utils/node-web-streams-helper.ts +++ b/packages/next/src/server/stream-utils/node-web-streams-helper.ts @@ -2,7 +2,11 @@ import type { ReactDOMServerReadableStream } from 'react-dom/server' import { getTracer } from '../lib/trace/tracer' import { AppRenderSpan } from '../lib/trace/constants' import { DetachedPromise } from '../../lib/detached-promise' -import { scheduleImmediate, atLeastOneTask } from '../../lib/scheduler' +import { + scheduleImmediate, + atLeastOneTask, + waitAtLeastOneReactRenderTask, +} from '../../lib/scheduler' import { ENCODED_TAGS } from './encoded-tags' import { indexOfUint8Array, @@ -797,9 +801,13 @@ export async function continueFizzStream( // Suffix itself might contain close tags at the end, so we need to split it. const suffixUnclosed = suffix ? suffix.split(CLOSE_TAG, 1)[0] : null - // If we're generating static HTML we need to wait for it to resolve before continuing. if (isStaticGeneration) { + // If we're generating static HTML we need to wait for it to resolve before continuing. await renderStream.allReady + } else { + // Otherwise, we want to make sure Fizz is done with all microtasky work + // before we start pulling the stream and cause a flush. + await waitAtLeastOneReactRenderTask() } return chainTransformers(renderStream, [ diff --git a/test/e2e/app-dir/metadata-icons/app/custom-icon/delay-icons/page.tsx b/test/e2e/app-dir/metadata-icons/app/custom-icon/delay-icons/page.tsx index 932d9062e8149..0f7ae983249d9 100644 --- a/test/e2e/app-dir/metadata-icons/app/custom-icon/delay-icons/page.tsx +++ b/test/e2e/app-dir/metadata-icons/app/custom-icon/delay-icons/page.tsx @@ -18,8 +18,9 @@ export default async function Page() { export async function generateMetadata() { await connection() + // Delay until after the shell is flushed so that the tags end up in the body. + await new Promise((resolve) => setTimeout(resolve, 10)) return { - // This long text description will lead to the metadata being inserted after the head tag. description: 'long text description'.repeat(1000), icons: { icon: `/heart.png`,