diff --git a/packages/next/src/server/app-render/dynamic-rendering.ts b/packages/next/src/server/app-render/dynamic-rendering.ts index 4b68b210d3a254..45c9501a08be67 100644 --- a/packages/next/src/server/app-render/dynamic-rendering.ts +++ b/packages/next/src/server/app-render/dynamic-rendering.ts @@ -265,9 +265,7 @@ function abortOnSynchronousDynamicDataAccess( const error = createPrerenderInterruptedError(reason) - if (prerenderStore.controller) { - prerenderStore.controller.abort(error) - } + prerenderStore.controller.abort(error) const dynamicTracking = prerenderStore.dynamicTracking if (dynamicTracking) { diff --git a/packages/next/src/server/app-render/work-unit-async-storage.external.ts b/packages/next/src/server/app-render/work-unit-async-storage.external.ts index a168e69c5cd726..b22f046608996a 100644 --- a/packages/next/src/server/app-render/work-unit-async-storage.external.ts +++ b/packages/next/src/server/app-render/work-unit-async-storage.external.ts @@ -67,11 +67,18 @@ export type RequestStore = { export type PrerenderStoreModern = { type: 'prerender' readonly implicitTags: string[] + + /** + * This signal is aborted when the React render is complete. (i.e. it is the same signal passed to react) + */ + readonly renderSignal: AbortSignal /** - * This is the AbortController passed to React. It can be used to abort the prerender - * if we encounter conditions that do not require further rendering + * This is the AbortController which represents the boundary between Prerender and dynamic. In some renders it is + * the same as the controller for the renderSignal but in others it is a separate controller. It should be aborted + * whenever the we are no longer in the prerender phase of rendering. Typically this is after one task or when you call + * a sync API which requires the prerender to end immediately */ - readonly controller: null | AbortController + readonly controller: AbortController /** * when not null this signal is used to track cache reads during prerendering and @@ -79,11 +86,6 @@ export type PrerenderStoreModern = { */ readonly cacheSignal: null | CacheSignal - /** - * This signal is used to clean up the prerender once it is complete. - */ - readonly renderSignal: AbortSignal - /** * During some prerenders we want to track dynamic access. */ diff --git a/packages/next/src/server/route-modules/app-route/module.ts b/packages/next/src/server/route-modules/app-route/module.ts index 6fc2b1b1abd516..d38119a643c8cd 100644 --- a/packages/next/src/server/route-modules/app-route/module.ts +++ b/packages/next/src/server/route-modules/app-route/module.ts @@ -61,7 +61,6 @@ import { postponeWithTracking, createDynamicTrackingState, getFirstDynamicReason, - isPrerenderInterruptedError, } from '../../app-render/dynamic-rendering' import { ReflectAdapter } from '../../web/spec-extension/adapters/reflect' import type { RenderOptsPartial } from '../../app-render/types' @@ -349,10 +348,10 @@ export class AppRouteRouteModule extends RouteModule< phase: 'action', implicitTags: implicitTags, renderSignal: prospectiveController.signal, + controller: prospectiveController, cacheSignal, // During prospective render we don't use a controller // because we need to let all caches fill. - controller: null, dynamicTracking, revalidate: defaultRevalidate, expire: INFINITE_CACHE, @@ -369,11 +368,14 @@ export class AppRouteRouteModule extends RouteModule< handlerContext ) } catch (err) { - if (isPrerenderInterruptedError(err)) { + if (prospectiveController.signal.aborted) { // the route handler called an API which is always dynamic // there is no need to try again prospectiveRenderIsDynamic = true - } else if (process.env.NEXT_DEBUG_BUILD) { + } else if ( + process.env.NEXT_DEBUG_BUILD || + process.env.__NEXT_VERBOSE_LOGGING + ) { printDebugThrownValueForProspectiveRender(err, workStore.route) } } @@ -387,7 +389,7 @@ export class AppRouteRouteModule extends RouteModule< ;(prospectiveResult as any as Promise).then( () => {}, (err) => { - if (isPrerenderInterruptedError(err)) { + if (prospectiveController.signal.aborted) { // the route handler called an API which is always dynamic // there is no need to try again prospectiveRenderIsDynamic = true @@ -431,8 +433,8 @@ export class AppRouteRouteModule extends RouteModule< phase: 'action', implicitTags: implicitTags, renderSignal: finalController.signal, - cacheSignal: null, controller: finalController, + cacheSignal: null, dynamicTracking, revalidate: defaultRevalidate, expire: INFINITE_CACHE,