Skip to content

Commit

Permalink
Ensure the app shell is rendered before rendering the document (#35732)
Browse files Browse the repository at this point in the history
  • Loading branch information
shuding committed Mar 30, 2022
1 parent 5a5b617 commit 9fc1904
Show file tree
Hide file tree
Showing 2 changed files with 64 additions and 4 deletions.
50 changes: 50 additions & 0 deletions packages/next/server/node-web-streams-helper.ts
Expand Up @@ -233,6 +233,56 @@ export function createFlushEffectStream(
})
}

export async function renderToInitialStream({
ReactDOMServer,
element,
}: {
ReactDOMServer: typeof import('react-dom/server')
element: React.ReactElement
}): Promise<
ReadableStream<Uint8Array> & {
allReady?: Promise<void>
}
> {
return await (ReactDOMServer as any).renderToReadableStream(element)
}

export async function continueFromInitialStream({
suffix,
dataStream,
generateStaticHTML,
flushEffectHandler,
renderStream,
}: {
suffix?: string
dataStream?: ReadableStream<Uint8Array>
generateStaticHTML: boolean
flushEffectHandler?: () => Promise<string>
renderStream: ReadableStream<Uint8Array> & {
allReady?: Promise<void>
}
}): Promise<ReadableStream<Uint8Array>> {
const closeTag = '</body></html>'
const suffixUnclosed = suffix ? suffix.split(closeTag)[0] : null

if (generateStaticHTML) {
await renderStream.allReady
}

const transforms: Array<TransformStream<Uint8Array, Uint8Array>> = [
createBufferedTransformStream(),
flushEffectHandler ? createFlushEffectStream(flushEffectHandler) : null,
suffixUnclosed != null ? createPrefixStream(suffixUnclosed) : null,
dataStream ? createInlineDataStream(dataStream) : null,
suffixUnclosed != null ? createSuffixStream(closeTag) : null,
].filter(Boolean) as any

return transforms.reduce(
(readable, transform) => pipeThrough(readable, transform),
renderStream
)
}

export async function renderToStream({
ReactDOMServer,
element,
Expand Down
18 changes: 14 additions & 4 deletions packages/next/server/render.tsx
Expand Up @@ -74,6 +74,8 @@ import {
chainStreams,
createBufferedTransformStream,
renderToStream,
renderToInitialStream,
continueFromInitialStream,
} from './node-web-streams-helper'
import { ImageConfigContext } from '../shared/lib/image-config-context'
import { FlushEffectsContext } from '../shared/lib/flush-effects'
Expand Down Expand Up @@ -1354,11 +1356,20 @@ export async function renderToHTML(
}

if (hasConcurrentFeatures) {
let renderStream: any

// We start rendering the shell earlier, before returning the head tags
// to `documentResult`.
const content = renderContent()
renderStream = await renderToInitialStream({
ReactDOMServer,
element: content,
})

bodyResult = async (suffix: string) => {
// this must be called inside bodyResult so appWrappers is
// up to date when getWrappedApp is called

const content = renderContent()
const flushEffectHandler = async () => {
const allFlushEffects = [
styledJsxFlushEffect,
Expand All @@ -1379,9 +1390,8 @@ export async function renderToHTML(
return flushed
}

return await renderToStream({
ReactDOMServer,
element: content,
return await continueFromInitialStream({
renderStream,
suffix,
dataStream: serverComponentsInlinedTransformStream?.readable,
generateStaticHTML: generateStaticHTML || !hasConcurrentFeatures,
Expand Down

0 comments on commit 9fc1904

Please sign in to comment.