1
1
import { IncomingMessage , ServerResponse } from 'http'
2
2
import { ParsedUrlQuery } from 'querystring'
3
+ import { PassThrough } from 'stream'
3
4
import React from 'react'
4
- import { renderToStaticMarkup , renderToString } from 'react-dom/server'
5
+ import * as ReactDOMServer from 'react-dom/server'
5
6
import { warn } from '../build/output/log'
6
7
import { UnwrapPromise } from '../lib/coalesced-function'
7
8
import {
@@ -43,6 +44,7 @@ import {
43
44
loadGetInitialProps ,
44
45
NextComponentType ,
45
46
RenderPage ,
47
+ RenderPageResult ,
46
48
} from '../shared/lib/utils'
47
49
import {
48
50
tryGetPreviewData ,
@@ -190,6 +192,7 @@ export type RenderOptsPartial = {
190
192
domainLocales ?: DomainLocale [ ]
191
193
disableOptimizedLoading ?: boolean
192
194
requireStaticHTML ?: boolean
195
+ concurrentFeatures ?: boolean
193
196
}
194
197
195
198
export type RenderOpts = LoadComponentsReturnType & RenderOptsPartial
@@ -263,7 +266,7 @@ function renderDocument(
263
266
) : string {
264
267
return (
265
268
'<!DOCTYPE html>' +
266
- renderToStaticMarkup (
269
+ ReactDOMServer . renderToStaticMarkup (
267
270
< AmpStateContext . Provider value = { ampState } >
268
271
{ Document . renderDocument ( Document , {
269
272
__NEXT_DATA__ : {
@@ -408,6 +411,7 @@ export async function renderToHTML(
408
411
previewProps,
409
412
basePath,
410
413
devOnlyCacheBusterQueryString,
414
+ concurrentFeatures,
411
415
} = renderOpts
412
416
413
417
const getFontDefinition = ( url : string ) : string => {
@@ -626,6 +630,8 @@ export async function renderToHTML(
626
630
let head : JSX . Element [ ] = defaultHead ( inAmpMode )
627
631
628
632
let scriptLoader : any = { }
633
+ const nextExport =
634
+ ! isSSG && ( renderOpts . nextExport || ( dev && ( isAutoExport || isFallback ) ) )
629
635
630
636
const AppContainer = ( { children } : any ) => (
631
637
< RouterContext . Provider value = { router } >
@@ -991,11 +997,45 @@ export async function renderToHTML(
991
997
}
992
998
}
993
999
1000
+ // TODO: Support SSR streaming of Suspense.
1001
+ const renderToString = concurrentFeatures
1002
+ ? ( element : React . ReactElement ) =>
1003
+ new Promise < string > ( ( resolve , reject ) => {
1004
+ const stream = new PassThrough ( )
1005
+ const buffers : Buffer [ ] = [ ]
1006
+ stream . on ( 'data' , ( chunk ) => {
1007
+ buffers . push ( chunk )
1008
+ } )
1009
+ stream . once ( 'end' , ( ) => {
1010
+ resolve ( Buffer . concat ( buffers ) . toString ( 'utf-8' ) )
1011
+ } )
1012
+
1013
+ const {
1014
+ abort,
1015
+ startWriting,
1016
+ } = ( ReactDOMServer as any ) . pipeToNodeWritable ( element , stream , {
1017
+ onError ( error : Error ) {
1018
+ abort ( )
1019
+ reject ( error )
1020
+ } ,
1021
+ onCompleteAll ( ) {
1022
+ startWriting ( )
1023
+ } ,
1024
+ } )
1025
+ } )
1026
+ : ReactDOMServer . renderToString
1027
+
994
1028
const renderPage : RenderPage = (
995
1029
options : ComponentsEnhancer = { }
996
- ) : { html : string ; head : any } => {
1030
+ ) : RenderPageResult | Promise < RenderPageResult > => {
997
1031
if ( ctx . err && ErrorDebug ) {
998
- return { html : renderToString ( < ErrorDebug error = { ctx . err } /> ) , head }
1032
+ const htmlOrPromise = renderToString ( < ErrorDebug error = { ctx . err } /> )
1033
+ return typeof htmlOrPromise === 'string'
1034
+ ? { html : htmlOrPromise , head }
1035
+ : htmlOrPromise . then ( ( html ) => ( {
1036
+ html,
1037
+ head,
1038
+ } ) )
999
1039
}
1000
1040
1001
1041
if ( dev && ( props . router || props . Component ) ) {
@@ -1009,13 +1049,17 @@ export async function renderToHTML(
1009
1049
Component : EnhancedComponent ,
1010
1050
} = enhanceComponents ( options , App , Component )
1011
1051
1012
- const html = renderToString (
1052
+ const htmlOrPromise = renderToString (
1013
1053
< AppContainer >
1014
1054
< EnhancedApp Component = { EnhancedComponent } router = { router } { ...props } />
1015
1055
</ AppContainer >
1016
1056
)
1017
-
1018
- return { html, head }
1057
+ return typeof htmlOrPromise === 'string'
1058
+ ? { html : htmlOrPromise , head }
1059
+ : htmlOrPromise . then ( ( html ) => ( {
1060
+ html,
1061
+ head,
1062
+ } ) )
1019
1063
}
1020
1064
const documentCtx = { ...ctx , renderPage }
1021
1065
const docProps : DocumentInitialProps = await loadGetInitialProps (
@@ -1049,8 +1093,6 @@ export async function renderToHTML(
1049
1093
const hybridAmp = ampState . hybrid
1050
1094
1051
1095
const docComponentsRendered : DocumentProps [ 'docComponentsRendered' ] = { }
1052
- const nextExport =
1053
- ! isSSG && ( renderOpts . nextExport || ( dev && ( isAutoExport || isFallback ) ) )
1054
1096
1055
1097
let html = renderDocument ( Document , {
1056
1098
...renderOpts ,
0 commit comments