Skip to content

Commit 557f5d5

Browse files
committed
[Cache Components] set environment labels if caches are disabled
1 parent c6ff4d0 commit 557f5d5

File tree

1 file changed

+204
-72
lines changed

1 file changed

+204
-72
lines changed

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

Lines changed: 204 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,10 @@ import {
168168
prerenderAndAbortInSequentialTasks,
169169
} from './app-render-prerender-utils'
170170
import { printDebugThrownValueForProspectiveRender } from './prospective-render-utils'
171-
import { pipelineInSequentialTasks } from './app-render-render-utils'
171+
import {
172+
pipelineInSequentialTasks,
173+
scheduleInSequentialTasks,
174+
} from './app-render-render-utils'
172175
import { waitAtLeastOneReactRenderTask } from '../../lib/scheduler'
173176
import {
174177
workUnitAsyncStorage,
@@ -604,52 +607,149 @@ async function generateDynamicFlightRenderResult(
604607
onFlightDataRenderError
605608
)
606609

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

629612
if (debugChannel) {
630613
setReactDebugChannel(debugChannel.clientSide, htmlRequestId, requestId)
631614
}
632615

633-
// For app dir, use the bundled version of Flight server renderer (renderToReadableStream)
634-
// which contains the subset React.
635-
const flightReadableStream = workUnitAsyncStorage.run(
636-
requestStore,
637-
renderToReadableStream,
638-
RSCPayload,
639-
clientReferenceManifest.clientModules,
640-
{
641-
onError,
642-
temporaryReferences: options?.temporaryReferences,
643-
filterStackFrame,
644-
debugChannel: debugChannel?.serverSide,
616+
let stream: ReadableStream<Uint8Array<ArrayBufferLike>>
617+
if (process.env.NODE_ENV === 'development' && renderOpts.cacheComponents) {
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+
) => {
625+
const rscPayload: RSCPayload & RSCPayloadDevProperties =
626+
await workUnitAsyncStorage.run(
627+
requestStore,
628+
generateDynamicRSCPayload,
629+
ctx,
630+
options
631+
)
632+
if (isBypassingCachesInDev(renderOpts, requestStore)) {
633+
rscPayload._bypassCachesInDev = createElement(
634+
WarnForBypassCachesInDev,
635+
{
636+
route: workStore.route,
637+
}
638+
)
639+
}
640+
return rscPayload
645641
}
646-
)
647642

648-
return new FlightRenderResult(flightReadableStream, {
643+
stream = await stagedRenderToReadableStreamWithoutCachesInDev(
644+
ctx,
645+
requestStore,
646+
getPayload,
647+
clientReferenceManifest,
648+
{
649+
onError,
650+
temporaryReferences: options?.temporaryReferences,
651+
filterStackFrame,
652+
debugChannel: debugChannel?.serverSide,
653+
}
654+
)
655+
} else {
656+
// For app dir, use the bundled version of Flight server renderer (renderToReadableStream)
657+
// which contains the subset React.
658+
const rscPayload = await workUnitAsyncStorage.run(
659+
requestStore,
660+
generateDynamicRSCPayload,
661+
ctx,
662+
options
663+
)
664+
665+
stream = workUnitAsyncStorage.run(
666+
requestStore,
667+
renderToReadableStream,
668+
rscPayload,
669+
clientReferenceManifest.clientModules,
670+
{
671+
onError,
672+
temporaryReferences: options?.temporaryReferences,
673+
filterStackFrame,
674+
debugChannel: debugChannel?.serverSide,
675+
}
676+
)
677+
}
678+
679+
return new FlightRenderResult(stream, {
649680
fetchMetrics: workStore.fetchMetrics,
650681
})
651682
}
652683

684+
type RenderToReadableStreamServerOptions = NonNullable<
685+
Parameters<
686+
(typeof import('react-server-dom-webpack/server.node'))['renderToReadableStream']
687+
>[2]
688+
>
689+
690+
async function stagedRenderToReadableStreamWithoutCachesInDev(
691+
ctx: AppRenderContext,
692+
requestStore: RequestStore,
693+
getPayload: (requestStore: RequestStore) => Promise<RSCPayload>,
694+
clientReferenceManifest: NonNullable<RenderOpts['clientReferenceManifest']>,
695+
options: Omit<RenderToReadableStreamServerOptions, 'environmentName'>
696+
) {
697+
const {
698+
componentMod: { renderToReadableStream },
699+
} = ctx
700+
// We're rendering while bypassing caches,
701+
// so we have no hope of showing a useful runtime stage.
702+
// But we still want things like `params` to show up in devtools correctly,
703+
// which relies on mechanisms we've set up for staged rendering,
704+
// so we do a 2-task version (Static -> Dynamic) instead.
705+
706+
const stageController = new StagedRenderingController()
707+
console.log('rendering RSC payload without caches')
708+
const environmentName = () => {
709+
const currentStage = stageController.currentStage
710+
switch (currentStage) {
711+
case RenderStage.Static:
712+
return 'Prerender'
713+
case RenderStage.Runtime:
714+
case RenderStage.Dynamic:
715+
return 'Server'
716+
default:
717+
currentStage satisfies never
718+
throw new InvariantError(`Invalid render stage: ${currentStage}`)
719+
}
720+
}
721+
722+
requestStore.stagedRendering = stageController
723+
requestStore.asyncApiPromises = createAsyncApiPromisesInDev(
724+
stageController,
725+
requestStore.cookies,
726+
requestStore.mutableCookies,
727+
requestStore.headers
728+
)
729+
730+
const rscPayload = await getPayload(requestStore)
731+
732+
return await workUnitAsyncStorage.run(
733+
requestStore,
734+
scheduleInSequentialTasks,
735+
() => {
736+
const stream = renderToReadableStream(
737+
rscPayload,
738+
clientReferenceManifest.clientModules,
739+
{
740+
...options,
741+
environmentName,
742+
}
743+
)
744+
stageController.advanceStage(RenderStage.Dynamic)
745+
return stream
746+
},
747+
() => {
748+
stageController.advanceStage(RenderStage.Dynamic)
749+
}
750+
)
751+
}
752+
653753
/**
654754
* Fork of `generateDynamicFlightRenderResult` that renders using `renderWithRestartOnCacheMissInDev`
655755
* to ensure correct separation of environments Prerender/Server (for use in Cache Components)
@@ -2192,6 +2292,12 @@ function applyMetadataFromPrerenderResult(
21922292
}
21932293
}
21942294

2295+
type RSCPayloadDevProperties = {
2296+
/** Only available during cacheComponents development builds. Used for logging errors. */
2297+
_validation?: Promise<ReactNode>
2298+
_bypassCachesInDev?: ReactNode
2299+
}
2300+
21952301
async function renderToStream(
21962302
requestStore: RequestStore,
21972303
req: BaseNextRequest,
@@ -2334,26 +2440,15 @@ async function renderToStream(
23342440
// Edge routes never prerender so we don't have a Prerender environment for anything in edge runtime
23352441
process.env.NEXT_RUNTIME !== 'edge' &&
23362442
// We only have a Prerender environment for projects opted into cacheComponents
2337-
cacheComponents &&
2338-
// We only do this flow if we can safely recreate the store from scratch
2339-
// (which is not the case for renders after an action)
2340-
createRequestStore &&
2341-
// We only do this flow if we're not bypassing caches in dev using
2342-
// "disable cache" in devtools or a hard refresh (cache-control: "no-store")
2343-
!isBypassingCachesInDev(renderOpts, requestStore)
2443+
cacheComponents
23442444
) {
2345-
type RSCPayloadWithValidation = InitialRSCPayload & {
2346-
/** Only available during cacheComponents development builds. Used for logging errors. */
2347-
_validation?: Promise<ReactNode>
2348-
}
2349-
23502445
const [resolveValidation, validationOutlet] = createValidationOutlet()
2351-
2446+
let debugChannel: DebugChannelPair | undefined
23522447
const getPayload = async (
23532448
// eslint-disable-next-line @typescript-eslint/no-shadow
23542449
requestStore: RequestStore
2355-
): Promise<RSCPayloadWithValidation> => {
2356-
const payload: RSCPayloadWithValidation =
2450+
) => {
2451+
const payload: InitialRSCPayload & RSCPayloadDevProperties =
23572452
await workUnitAsyncStorage.run(
23582453
requestStore,
23592454
getRSCPayload,
@@ -2366,23 +2461,61 @@ async function renderToStream(
23662461
// because we're not going to wait for the stream to complete,
23672462
// so leaving the validation unresolved is fine.
23682463
payload._validation = validationOutlet
2464+
2465+
if (isBypassingCachesInDev(renderOpts, requestStore)) {
2466+
// Mark the RSC payload to indicate that caches were bypassed in dev.
2467+
// This lets the client know not to cache anything based on this render.
2468+
payload._bypassCachesInDev = createElement(WarnForBypassCachesInDev, {
2469+
route: workStore.route,
2470+
})
2471+
}
2472+
23692473
return payload
23702474
}
23712475

2372-
const {
2373-
stream: serverStream,
2374-
debugChannel,
2375-
requestStore: finalRequestStore,
2376-
} = await renderWithRestartOnCacheMissInDev(
2377-
ctx,
2378-
requestStore,
2379-
createRequestStore,
2380-
getPayload,
2381-
serverComponentsErrorHandler
2382-
)
2476+
if (
2477+
// We only do this flow if we can safely recreate the store from scratch
2478+
// (which is not the case for renders after an action)
2479+
createRequestStore &&
2480+
// We only do this flow if we're not bypassing caches in dev using
2481+
// "disable cache" in devtools or a hard refresh (cache-control: "no-store")
2482+
!isBypassingCachesInDev(renderOpts, requestStore)
2483+
) {
2484+
const {
2485+
stream: serverStream,
2486+
debugChannel: returnedDebugChannel,
2487+
requestStore: finalRequestStore,
2488+
} = await renderWithRestartOnCacheMissInDev(
2489+
ctx,
2490+
requestStore,
2491+
createRequestStore,
2492+
getPayload,
2493+
serverComponentsErrorHandler
2494+
)
2495+
2496+
reactServerResult = new ReactServerResult(serverStream)
2497+
requestStore = finalRequestStore
2498+
debugChannel = returnedDebugChannel
2499+
} else {
2500+
// We're either bypassing caches or we can't restart the render.
2501+
// Do a dynamic render, but with (basic) environment labels.
23832502

2384-
reactServerResult = new ReactServerResult(serverStream)
2385-
requestStore = finalRequestStore
2503+
debugChannel = setReactDebugChannel && createDebugChannel()
2504+
2505+
const serverStream =
2506+
await stagedRenderToReadableStreamWithoutCachesInDev(
2507+
ctx,
2508+
requestStore,
2509+
getPayload,
2510+
clientReferenceManifest,
2511+
{
2512+
onError: serverComponentsErrorHandler,
2513+
filterStackFrame,
2514+
debugChannel: debugChannel?.serverSide,
2515+
}
2516+
)
2517+
reactServerResult = new ReactServerResult(serverStream)
2518+
}
23862519

23872520
if (debugChannel && setReactDebugChannel) {
23882521
const [readableSsr, readableBrowser] =
@@ -2413,15 +2546,14 @@ async function renderToStream(
24132546
)
24142547
} else {
24152548
// This is a dynamic render. We don't do dynamic tracking because we're not prerendering
2416-
const RSCPayload: RSCPayload & {
2417-
_bypassCachesInDev?: React.ReactNode
2418-
} = await workUnitAsyncStorage.run(
2419-
requestStore,
2420-
getRSCPayload,
2421-
tree,
2422-
ctx,
2423-
res.statusCode === 404
2424-
)
2549+
const RSCPayload: RSCPayload & RSCPayloadDevProperties =
2550+
await workUnitAsyncStorage.run(
2551+
requestStore,
2552+
getRSCPayload,
2553+
tree,
2554+
ctx,
2555+
res.statusCode === 404
2556+
)
24252557

24262558
if (isBypassingCachesInDev(renderOpts, requestStore)) {
24272559
// Mark the RSC payload to indicate that caches were bypassed in dev.

0 commit comments

Comments
 (0)