Skip to content

Commit 57029eb

Browse files
committed
staged render for initial load
1 parent 2968a1f commit 57029eb

File tree

1 file changed

+153
-86
lines changed

1 file changed

+153
-86
lines changed

packages/next/src/server/app-render/app-render.tsx

Lines changed: 153 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -607,26 +607,6 @@ async function generateDynamicFlightRenderResult(
607607
onFlightDataRenderError
608608
)
609609

610-
const RSCPayload: RSCPayload & {
611-
/** Only available during cacheComponents development builds. Used for logging errors. */
612-
_validation?: Promise<ReactNode>
613-
_bypassCachesInDev?: React.ReactNode
614-
} = await workUnitAsyncStorage.run(
615-
requestStore,
616-
generateDynamicRSCPayload,
617-
ctx,
618-
options
619-
)
620-
621-
if (
622-
process.env.NODE_ENV === 'development' &&
623-
isBypassingCachesInDev(renderOpts, requestStore)
624-
) {
625-
RSCPayload._bypassCachesInDev = createElement(WarnForBypassCachesInDev, {
626-
route: workStore.route,
627-
})
628-
}
629-
630610
const debugChannel = setReactDebugChannel && createDebugChannel()
631611

632612
if (debugChannel) {
@@ -635,63 +615,60 @@ async function generateDynamicFlightRenderResult(
635615

636616
let stream: ReadableStream<Uint8Array<ArrayBufferLike>>
637617
if (process.env.NODE_ENV === 'development' && renderOpts.cacheComponents) {
638-
// If we got here in Cache Components dev, we're rendering while bypassing caches,
639-
// so we have no hope of showing a useful runtime stage.
640-
// But we still want things like `params` to show up in devtools correctly,
641-
// which relies on mechanisms we've set up for staged rendering,
642-
// so we do a 2-task version (Static -> Dynamic) instead.
643-
644-
const stageController = new StagedRenderingController()
645-
646-
const environmentName = () => {
647-
const currentStage = stageController.currentStage
648-
switch (currentStage) {
649-
case RenderStage.Static:
650-
return 'Prerender'
651-
case RenderStage.Runtime:
652-
case RenderStage.Dynamic:
653-
return 'Server'
654-
default:
655-
currentStage satisfies never
656-
throw new InvariantError(`Invalid render stage: ${currentStage}`)
618+
// If we got here, it means we didn't go into `generateDynamicFlightRenderResultWithCachesInDev`,
619+
// so caches must be disabled. Render dynamically, but include basic environment labels.
620+
621+
const getPayload = async (
622+
// eslint-disable-next-line @typescript-eslint/no-shadow
623+
requestStore: RequestStore
624+
): Promise<RSCPayload> => {
625+
const rscPayload: RSCPayload & {
626+
/** Only available during cacheComponents development builds. Used for logging errors. */
627+
_validation?: Promise<ReactNode>
628+
_bypassCachesInDev?: React.ReactNode
629+
} = await workUnitAsyncStorage.run(
630+
requestStore,
631+
generateDynamicRSCPayload,
632+
ctx,
633+
options
634+
)
635+
if (isBypassingCachesInDev(renderOpts, requestStore)) {
636+
rscPayload._bypassCachesInDev = createElement(
637+
WarnForBypassCachesInDev,
638+
{
639+
route: workStore.route,
640+
}
641+
)
657642
}
643+
return rscPayload
658644
}
659645

660-
requestStore.stagedRendering = stageController
661-
requestStore.asyncApiPromises = createAsyncApiPromisesInDev(
662-
stageController,
663-
requestStore.cookies,
664-
requestStore.mutableCookies,
665-
requestStore.headers
666-
)
667-
668-
stream = await workUnitAsyncStorage.run(
646+
stream = await stagedRenderToReadableStreamWithoutCachesInDev(
647+
ctx,
669648
requestStore,
670-
scheduleInSequentialTasks,
671-
() => {
672-
return renderToReadableStream(
673-
RSCPayload,
674-
clientReferenceManifest.clientModules,
675-
{
676-
onError,
677-
temporaryReferences: options?.temporaryReferences,
678-
filterStackFrame,
679-
debugChannel: debugChannel?.serverSide,
680-
environmentName,
681-
}
682-
)
683-
},
684-
() => {
685-
stageController.advanceStage(RenderStage.Dynamic)
649+
getPayload,
650+
clientReferenceManifest,
651+
{
652+
onError,
653+
temporaryReferences: options?.temporaryReferences,
654+
filterStackFrame,
655+
debugChannel: debugChannel?.serverSide,
686656
}
687657
)
688658
} else {
689659
// For app dir, use the bundled version of Flight server renderer (renderToReadableStream)
690660
// which contains the subset React.
661+
const rscPayload = await workUnitAsyncStorage.run(
662+
requestStore,
663+
generateDynamicRSCPayload,
664+
ctx,
665+
options
666+
)
667+
691668
stream = workUnitAsyncStorage.run(
692669
requestStore,
693670
renderToReadableStream,
694-
RSCPayload,
671+
rscPayload,
695672
clientReferenceManifest.clientModules,
696673
{
697674
onError,
@@ -707,6 +684,75 @@ async function generateDynamicFlightRenderResult(
707684
})
708685
}
709686

687+
type RenderToReadableStreamServerOptions = NonNullable<
688+
Parameters<
689+
(typeof import('react-server-dom-webpack/server.node'))['renderToReadableStream']
690+
>[2]
691+
>
692+
693+
async function stagedRenderToReadableStreamWithoutCachesInDev(
694+
ctx: AppRenderContext,
695+
requestStore: RequestStore,
696+
getPayload: (requestStore: RequestStore) => Promise<RSCPayload>,
697+
clientReferenceManifest: NonNullable<RenderOpts['clientReferenceManifest']>,
698+
options: Omit<RenderToReadableStreamServerOptions, 'environmentName'>
699+
) {
700+
const {
701+
componentMod: { renderToReadableStream },
702+
} = ctx
703+
// We're rendering while bypassing caches,
704+
// so we have no hope of showing a useful runtime stage.
705+
// But we still want things like `params` to show up in devtools correctly,
706+
// which relies on mechanisms we've set up for staged rendering,
707+
// so we do a 2-task version (Static -> Dynamic) instead.
708+
709+
const stageController = new StagedRenderingController()
710+
console.log('rendering RSC payload without caches')
711+
const environmentName = () => {
712+
const currentStage = stageController.currentStage
713+
switch (currentStage) {
714+
case RenderStage.Static:
715+
return 'Prerender'
716+
case RenderStage.Runtime:
717+
case RenderStage.Dynamic:
718+
return 'Server'
719+
default:
720+
currentStage satisfies never
721+
throw new InvariantError(`Invalid render stage: ${currentStage}`)
722+
}
723+
}
724+
725+
requestStore.stagedRendering = stageController
726+
requestStore.asyncApiPromises = createAsyncApiPromisesInDev(
727+
stageController,
728+
requestStore.cookies,
729+
requestStore.mutableCookies,
730+
requestStore.headers
731+
)
732+
733+
const rscPayload = await getPayload(requestStore)
734+
735+
return await workUnitAsyncStorage.run(
736+
requestStore,
737+
scheduleInSequentialTasks,
738+
() => {
739+
const stream = renderToReadableStream(
740+
rscPayload,
741+
clientReferenceManifest.clientModules,
742+
{
743+
...options,
744+
environmentName,
745+
}
746+
)
747+
stageController.advanceStage(RenderStage.Dynamic)
748+
return stream
749+
},
750+
() => {
751+
stageController.advanceStage(RenderStage.Dynamic)
752+
}
753+
)
754+
}
755+
710756
/**
711757
* Fork of `generateDynamicFlightRenderResult` that renders using `renderWithRestartOnCacheMissInDev`
712758
* to ensure correct separation of environments Prerender/Server (for use in Cache Components)
@@ -2375,21 +2421,15 @@ async function renderToStream(
23752421
// Edge routes never prerender so we don't have a Prerender environment for anything in edge runtime
23762422
process.env.NEXT_RUNTIME !== 'edge' &&
23772423
// We only have a Prerender environment for projects opted into cacheComponents
2378-
cacheComponents &&
2379-
// We only do this flow if we can safely recreate the store from scratch
2380-
// (which is not the case for renders after an action)
2381-
createRequestStore &&
2382-
// We only do this flow if we're not bypassing caches in dev using
2383-
// "disable cache" in devtools or a hard refresh (cache-control: "no-store")
2384-
!isBypassingCachesInDev(renderOpts, requestStore)
2424+
cacheComponents
23852425
) {
23862426
type RSCPayloadWithValidation = InitialRSCPayload & {
23872427
/** Only available during cacheComponents development builds. Used for logging errors. */
23882428
_validation?: Promise<ReactNode>
23892429
}
23902430

23912431
const [resolveValidation, validationOutlet] = createValidationOutlet()
2392-
2432+
let debugChannel: DebugChannelPair | undefined
23932433
const getPayload = async (
23942434
// eslint-disable-next-line @typescript-eslint/no-shadow
23952435
requestStore: RequestStore
@@ -2410,20 +2450,47 @@ async function renderToStream(
24102450
return payload
24112451
}
24122452

2413-
const {
2414-
stream: serverStream,
2415-
debugChannel,
2416-
requestStore: finalRequestStore,
2417-
} = await renderWithRestartOnCacheMissInDev(
2418-
ctx,
2419-
requestStore,
2420-
createRequestStore,
2421-
getPayload,
2422-
serverComponentsErrorHandler
2423-
)
2453+
if (
2454+
// We only do this flow if we can safely recreate the store from scratch
2455+
// (which is not the case for renders after an action)
2456+
createRequestStore &&
2457+
// We only do this flow if we're not bypassing caches in dev using
2458+
// "disable cache" in devtools or a hard refresh (cache-control: "no-store")
2459+
!isBypassingCachesInDev(renderOpts, requestStore)
2460+
) {
2461+
const {
2462+
stream: serverStream,
2463+
debugChannel: returnedDebugChannel,
2464+
requestStore: finalRequestStore,
2465+
} = await renderWithRestartOnCacheMissInDev(
2466+
ctx,
2467+
requestStore,
2468+
createRequestStore,
2469+
getPayload,
2470+
serverComponentsErrorHandler
2471+
)
24242472

2425-
reactServerResult = new ReactServerResult(serverStream)
2426-
requestStore = finalRequestStore
2473+
reactServerResult = new ReactServerResult(serverStream)
2474+
requestStore = finalRequestStore
2475+
debugChannel = returnedDebugChannel
2476+
} else {
2477+
// We're either bypassing caches or we can't restart the render.
2478+
// Do a dynamic render, but with (basic) environment labels.
2479+
debugChannel = createDebugChannel && createDebugChannel()
2480+
const serverStream =
2481+
await stagedRenderToReadableStreamWithoutCachesInDev(
2482+
ctx,
2483+
requestStore,
2484+
getPayload,
2485+
clientReferenceManifest,
2486+
{
2487+
onError: serverComponentsErrorHandler,
2488+
filterStackFrame,
2489+
debugChannel: debugChannel?.serverSide,
2490+
}
2491+
)
2492+
reactServerResult = new ReactServerResult(serverStream)
2493+
}
24272494

24282495
if (debugChannel && setReactDebugChannel) {
24292496
const [readableSsr, readableBrowser] =

0 commit comments

Comments
 (0)