Skip to content

Commit 071a059

Browse files
authored
Refactor: scope requestStore to dynamic renders only (vercel#72312)
This completes the refactor to eliminate a requestStore scoped around prerenders. Now we only scope requestStore around dynamic renders. If you are prerendering then the workUnitAsyncStorage will only have a prerneder store or undefined. While it is possible to shadow stores because you can enter a cache scope from a render or prerender it generally should never be the case that you enter a prerender from a request or enter a request from a prerender. These are effectively top level scopes. This should not change any program behavior stacked on vercel#72212
1 parent 719a187 commit 071a059

File tree

3 files changed

+79
-80
lines changed

3 files changed

+79
-80
lines changed

packages/next/src/server/app-render/action-handler.ts

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ import { isNodeNextRequest, isWebNextRequest } from '../base-http/helpers'
4545
import { RedirectStatusCode } from '../../client/components/redirect-status-code'
4646
import { synchronizeMutableCookies } from '../async-storage/request-store'
4747
import type { TemporaryReferenceSet } from 'react-server-dom-webpack/server.edge'
48+
import { workUnitAsyncStorage } from '../app-render/work-unit-async-storage.external'
4849

4950
function formDataFromSearchQueryString(query: string) {
5051
const searchParams = new URLSearchParams(query)
@@ -561,7 +562,7 @@ export async function handleAction({
561562

562563
return {
563564
type: 'done',
564-
result: await finalizeAndGenerateFlight(req, ctx, {
565+
result: await finalizeAndGenerateFlight(req, ctx, requestStore, {
565566
actionResult: promise,
566567
// if the page was not revalidated, we can skip the rendering the flight tree
567568
skipFlight: !workStore.pathWasRevalidated,
@@ -878,7 +879,9 @@ export async function handleAction({
878879
actionId!
879880
]
880881

881-
const returnVal = await actionHandler.apply(null, boundActionArguments)
882+
const returnVal = await workUnitAsyncStorage.run(requestStore, () =>
883+
actionHandler.apply(null, boundActionArguments)
884+
)
882885

883886
// For form actions, we need to continue rendering the page.
884887
if (isFetchAction) {
@@ -887,7 +890,7 @@ export async function handleAction({
887890
requestStore,
888891
})
889892

890-
actionResult = await finalizeAndGenerateFlight(req, ctx, {
893+
actionResult = await finalizeAndGenerateFlight(req, ctx, requestStore, {
891894
actionResult: Promise.resolve(returnVal),
892895
// if the page was not revalidated, or if the action was forwarded from another worker, we can skip the rendering the flight tree
893896
skipFlight: !workStore.pathWasRevalidated || actionWasForwarded,
@@ -963,7 +966,7 @@ export async function handleAction({
963966
}
964967
return {
965968
type: 'done',
966-
result: await finalizeAndGenerateFlight(req, ctx, {
969+
result: await finalizeAndGenerateFlight(req, ctx, requestStore, {
967970
skipFlight: false,
968971
actionResult: promise,
969972
temporaryReferences,
@@ -998,7 +1001,7 @@ export async function handleAction({
9981001
requestStore.phase = 'render'
9991002
return {
10001003
type: 'done',
1001-
result: await generateFlight(req, ctx, {
1004+
result: await generateFlight(req, ctx, requestStore, {
10021005
actionResult: promise,
10031006
// if the page was not revalidated, or if the action was forwarded from another worker, we can skip the rendering the flight tree
10041007
skipFlight: !workStore.pathWasRevalidated || actionWasForwarded,

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

Lines changed: 69 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -509,6 +509,7 @@ function createErrorContext(
509509
async function generateDynamicFlightRenderResult(
510510
req: BaseNextRequest,
511511
ctx: AppRenderContext,
512+
requestStore: RequestStore,
512513
options?: {
513514
actionResult: ActionResult
514515
skipFlight: boolean
@@ -534,7 +535,12 @@ async function generateDynamicFlightRenderResult(
534535
const RSCPayload: RSCPayload & {
535536
/** Only available during dynamicIO development builds. Used for logging errors. */
536537
_validation?: Promise<React.ReactNode>
537-
} = await generateDynamicRSCPayload(ctx, options)
538+
} = await workUnitAsyncStorage.run(
539+
requestStore,
540+
generateDynamicRSCPayload,
541+
ctx,
542+
options
543+
)
538544

539545
if (
540546
// We only want this behavior when running `next dev`
@@ -559,7 +565,9 @@ async function generateDynamicFlightRenderResult(
559565

560566
// For app dir, use the bundled version of Flight server renderer (renderToReadableStream)
561567
// which contains the subset React.
562-
const flightReadableStream = ctx.componentMod.renderToReadableStream(
568+
const flightReadableStream = workUnitAsyncStorage.run(
569+
requestStore,
570+
ctx.componentMod.renderToReadableStream,
563571
RSCPayload,
564572
ctx.clientReferenceManifest.clientModules,
565573
{
@@ -583,13 +591,7 @@ async function generateDynamicFlightRenderResult(
583591
async function warmupDevRender(
584592
req: BaseNextRequest,
585593
ctx: AppRenderContext,
586-
requestStore: RequestStore,
587-
options?: {
588-
actionResult: ActionResult
589-
skipFlight: boolean
590-
componentTree?: CacheNodeSeedData
591-
preloadCallbacks?: PreloadCallbacks
592-
}
594+
requestStore: RequestStore
593595
): Promise<RenderResult> {
594596
const renderOpts = ctx.renderOpts
595597
if (!renderOpts.dev) {
@@ -622,8 +624,7 @@ async function warmupDevRender(
622624
const rscPayload = await workUnitAsyncStorage.run(
623625
requestStore,
624626
generateDynamicRSCPayload,
625-
ctx,
626-
options
627+
ctx
627628
)
628629

629630
// For app dir, use the bundled version of Flight server renderer (renderToReadableStream)
@@ -989,12 +990,12 @@ async function renderToHTMLOrFlightImpl(
989990
pagePath: string,
990991
query: NextParsedUrlQuery,
991992
renderOpts: RenderOpts,
992-
requestStore: RequestStore,
993993
workStore: WorkStore,
994994
parsedRequestHeaders: ParsedRequestHeaders,
995995
requestEndedState: { ended?: boolean },
996996
postponedState: PostponedState | null,
997-
implicitTags: Array<string>
997+
implicitTags: Array<string>,
998+
serverComponentsHmrCache: ServerComponentsHmrCache | undefined
998999
) {
9991000
const isNotFoundPath = pagePath === '/404'
10001001
if (isNotFoundPath) {
@@ -1047,26 +1048,6 @@ async function renderToHTMLOrFlightImpl(
10471048
isNodeNextRequest(req)
10481049
) {
10491050
req.originalRequest.on('end', () => {
1050-
const prerenderStore = workUnitAsyncStorage.getStore()
1051-
const isPPR =
1052-
prerenderStore &&
1053-
(prerenderStore.type === 'prerender' ||
1054-
prerenderStore.type === 'prerender-ppr')
1055-
? !!prerenderStore.dynamicTracking?.dynamicAccesses?.length
1056-
: false
1057-
1058-
if (
1059-
process.env.NODE_ENV === 'development' &&
1060-
renderOpts.setAppIsrStatus &&
1061-
!isPPR &&
1062-
!requestStore.usedDynamic &&
1063-
!workStore.forceDynamic
1064-
) {
1065-
// only node can be ISR so we only need to update the status here
1066-
const { pathname } = new URL(req.url || '/', 'http://n')
1067-
renderOpts.setAppIsrStatus(pathname, true)
1068-
}
1069-
10701051
requestEndedState.ended = true
10711052

10721053
if ('performance' in globalThis) {
@@ -1130,6 +1111,7 @@ async function renderToHTMLOrFlightImpl(
11301111
isPrefetchRequest,
11311112
isRSCRequest,
11321113
isDevWarmupRequest,
1114+
isHmrRefresh,
11331115
nonce,
11341116
} = parsedRequestHeaders
11351117

@@ -1287,10 +1269,44 @@ async function renderToHTMLOrFlightImpl(
12871269
return new RenderResult(await streamToString(response.stream), options)
12881270
} else {
12891271
// We're rendering dynamically
1272+
const renderResumeDataCache =
1273+
renderOpts.devWarmupRenderResumeDataCache ??
1274+
postponedState?.renderResumeDataCache
1275+
1276+
const requestStore = createRequestStoreForRender(
1277+
req,
1278+
res,
1279+
url,
1280+
implicitTags,
1281+
renderOpts.onUpdateCookies,
1282+
renderOpts.previewProps,
1283+
isHmrRefresh,
1284+
serverComponentsHmrCache,
1285+
renderResumeDataCache
1286+
)
1287+
1288+
if (
1289+
process.env.NODE_ENV === 'development' &&
1290+
renderOpts.setAppIsrStatus &&
1291+
// The type check here ensures that `req` is correctly typed, and the
1292+
// environment variable check provides dead code elimination.
1293+
process.env.NEXT_RUNTIME !== 'edge' &&
1294+
isNodeNextRequest(req)
1295+
) {
1296+
const setAppIsrStatus = renderOpts.setAppIsrStatus
1297+
req.originalRequest.on('end', () => {
1298+
if (!requestStore.usedDynamic && !workStore.forceDynamic) {
1299+
// only node can be ISR so we only need to update the status here
1300+
const { pathname } = new URL(req.url || '/', 'http://n')
1301+
setAppIsrStatus(pathname, true)
1302+
}
1303+
})
1304+
}
1305+
12901306
if (isDevWarmupRequest) {
12911307
return warmupDevRender(req, ctx, requestStore)
12921308
} else if (isRSCRequest) {
1293-
return generateDynamicFlightRenderResult(req, ctx)
1309+
return generateDynamicFlightRenderResult(req, ctx, requestStore)
12941310
}
12951311

12961312
const renderToStreamWithTracing = getTracer().wrap(
@@ -1415,7 +1431,7 @@ export const renderToHTMLOrFlight: AppPageRender = (
14151431
isRoutePPREnabled: renderOpts.experimental.isRoutePPREnabled === true,
14161432
})
14171433

1418-
const { isHmrRefresh, isPrefetchRequest } = parsedRequestHeaders
1434+
const { isPrefetchRequest } = parsedRequestHeaders
14191435

14201436
const requestEndedState = { ended: false }
14211437
let postponedState: PostponedState | null = null
@@ -1444,32 +1460,12 @@ export const renderToHTMLOrFlight: AppPageRender = (
14441460
)
14451461
}
14461462

1447-
const renderResumeDataCache =
1448-
renderOpts.devWarmupRenderResumeDataCache ??
1449-
postponedState?.renderResumeDataCache
1450-
14511463
const implicitTags = getImplicitTags(
14521464
renderOpts.routeModule.definition.page,
14531465
url,
14541466
fallbackRouteParams
14551467
)
14561468

1457-
// TODO: We need to refactor this so that prerenders do not rely upon the
1458-
// existence of an outer scoped request store. Then we should move this
1459-
// store generation inside the appropriate scope like `renderToStream` where
1460-
// we know we're handling a Request and not a Prerender
1461-
const requestStore = createRequestStoreForRender(
1462-
req,
1463-
res,
1464-
url,
1465-
implicitTags,
1466-
renderOpts.onUpdateCookies,
1467-
renderResumeDataCache,
1468-
renderOpts.previewProps,
1469-
isHmrRefresh,
1470-
serverComponentsHmrCache
1471-
)
1472-
14731469
const workStore = createWorkStore({
14741470
page: renderOpts.routeModule.definition.page,
14751471
fallbackRouteParams,
@@ -1479,24 +1475,24 @@ export const renderToHTMLOrFlight: AppPageRender = (
14791475
isPrefetchRequest,
14801476
})
14811477

1482-
return workAsyncStorage.run(workStore, () => {
1483-
return workUnitAsyncStorage.run(requestStore, () => {
1484-
return renderToHTMLOrFlightImpl(
1485-
req,
1486-
res,
1487-
url,
1488-
pagePath,
1489-
query,
1490-
renderOpts,
1491-
requestStore,
1492-
workStore,
1493-
parsedRequestHeaders,
1494-
requestEndedState,
1495-
postponedState,
1496-
implicitTags
1497-
)
1498-
})
1499-
})
1478+
return workAsyncStorage.run(
1479+
workStore,
1480+
// The function to run
1481+
renderToHTMLOrFlightImpl,
1482+
// all of it's args
1483+
req,
1484+
res,
1485+
url,
1486+
pagePath,
1487+
query,
1488+
renderOpts,
1489+
workStore,
1490+
parsedRequestHeaders,
1491+
requestEndedState,
1492+
postponedState,
1493+
implicitTags,
1494+
serverComponentsHmrCache
1495+
)
15001496
}
15011497

15021498
async function renderToStream(

packages/next/src/server/async-storage/request-store.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -108,10 +108,10 @@ export function createRequestStoreForRender(
108108
url: RequestContext['url'],
109109
implicitTags: RequestContext['implicitTags'],
110110
onUpdateCookies: RenderOpts['onUpdateCookies'],
111-
renderResumeDataCache: RenderResumeDataCache | undefined,
112111
previewProps: WrapperRenderOpts['previewProps'],
113112
isHmrRefresh: RequestContext['isHmrRefresh'],
114-
serverComponentsHmrCache: RequestContext['serverComponentsHmrCache']
113+
serverComponentsHmrCache: RequestContext['serverComponentsHmrCache'],
114+
renderResumeDataCache: RenderResumeDataCache | undefined
115115
): RequestStore {
116116
return createRequestStoreImpl(
117117
// Pages start in render phase by default

0 commit comments

Comments
 (0)