Skip to content

Commit

Permalink
Use renderToStream with React 18 (vercel#34106)
Browse files Browse the repository at this point in the history
As per React 18 recommendation, we should use e.g. `renderToReadableStream` whenever we use `createRoot`. This is particularly important for currently supported suspense features like `React.lazy` to work properly during SSR.

However, unless you have opted in to streaming support (via [the `runtime` flag](vercel#34068)), we will wait until `onCompleteAll` before sending it (via the `generateStaticHTML` flag).

---

Fixes vercel#33879
  • Loading branch information
devknoll authored and natew committed Feb 16, 2022
1 parent 97a49c8 commit a24ef59
Show file tree
Hide file tree
Showing 5 changed files with 11 additions and 2 deletions.
1 change: 1 addition & 0 deletions packages/next/build/entries.ts
Expand Up @@ -141,6 +141,7 @@ export function createEntrypoints(
'base64'
),
i18n: config.i18n ? JSON.stringify(config.i18n) : '',
reactRoot: config.experimental.reactRoot ? 'true' : '',
}

Object.keys(pages).forEach((page) => {
Expand Down
Expand Up @@ -31,6 +31,7 @@ export type ServerlessLoaderQuery = {
previewProps: string
loadedEnvFiles: string
i18n: string
reactRoot: string
}

const nextServerlessLoader: webpack.loader.Loader = function () {
Expand All @@ -52,6 +53,7 @@ const nextServerlessLoader: webpack.loader.Loader = function () {
previewProps,
loadedEnvFiles,
i18n,
reactRoot,
}: ServerlessLoaderQuery =
typeof this.query === 'string' ? parse(this.query.substr(1)) : this.query

Expand Down Expand Up @@ -193,6 +195,7 @@ const nextServerlessLoader: webpack.loader.Loader = function () {
canonicalBase: "${canonicalBase}",
generateEtags: ${generateEtags || 'false'},
poweredByHeader: ${poweredByHeader || 'false'},
reactRoot: ${reactRoot || 'false'},
runtimeConfig,
buildManifest,
Expand Down
1 change: 1 addition & 0 deletions packages/next/export/index.ts
Expand Up @@ -386,6 +386,7 @@ export default async function exportApp(
optimizeCss: nextConfig.experimental.optimizeCss,
optimizeFonts: nextConfig.optimizeFonts,
optimizeImages: nextConfig.experimental.optimizeImages,
reactRoot: nextConfig.experimental.reactRoot || false,
}

const { serverRuntimeConfig, publicRuntimeConfig } = nextConfig
Expand Down
2 changes: 2 additions & 0 deletions packages/next/server/base-server.ts
Expand Up @@ -163,6 +163,7 @@ export default abstract class Server {
serverComponentManifest?: any
renderServerComponentData?: boolean
serverComponentProps?: any
reactRoot: boolean
}
private incrementalCache: IncrementalCache
private responseCache: ResponseCache
Expand Down Expand Up @@ -321,6 +322,7 @@ export default abstract class Server {
crossOrigin: this.nextConfig.crossOrigin
? this.nextConfig.crossOrigin
: undefined,
reactRoot: this.nextConfig.experimental.reactRoot === true,
}

// Only the `publicRuntimeConfig` key is exposed to the client side
Expand Down
6 changes: 4 additions & 2 deletions packages/next/server/render.tsx
Expand Up @@ -232,6 +232,7 @@ export type RenderOptsPartial = {
serverComponents?: boolean
customServer?: boolean
crossOrigin?: string
reactRoot: boolean
}

export type RenderOpts = LoadComponentsReturnType & RenderOptsPartial
Expand Down Expand Up @@ -456,6 +457,7 @@ export async function renderToHTML(
basePath,
devOnlyCacheBusterQueryString,
supportsDynamicHTML,
reactRoot,
runtime,
} = renderOpts

Expand Down Expand Up @@ -1265,7 +1267,7 @@ export async function renderToHTML(
)
}

if (hasConcurrentFeatures) {
if (reactRoot) {
bodyResult = async (suffix: string) => {
// this must be called inside bodyResult so appWrappers is
// up to date when getWrappedApp is called
Expand All @@ -1277,7 +1279,7 @@ export async function renderToHTML(
suffix,
serverComponentsInlinedTransformStream?.readable ??
streamFromArray([]),
generateStaticHTML
generateStaticHTML || !hasConcurrentFeatures
)
}
} else {
Expand Down

0 comments on commit a24ef59

Please sign in to comment.