From f9314e48a8f681503472ae62296dd92c530d0e64 Mon Sep 17 00:00:00 2001 From: Hendrik Liebau Date: Thu, 15 May 2025 20:04:12 +0200 Subject: [PATCH] Remove prospective fallback prerenders The prospective fallback prerenders, which ensured that a fallback shell could be prerendered without errors, were only needed when Dynamic IO was enabled and PPR was disabled. This scenario is no longer supported. --- packages/next/src/build/index.ts | 52 ------------------ packages/next/src/export/routes/app-page.ts | 52 ------------------ packages/next/src/export/worker.ts | 21 +------- packages/next/src/server/config-schema.ts | 1 - packages/next/src/server/config-shared.ts | 10 ---- .../src/server/request/fallback-params.ts | 2 +- ...mic-io-errors.prospective-fallback.test.ts | 54 ------------------- .../app/blog/[slug]/page.jsx | 4 -- .../prospective-fallback/app/layout.jsx | 7 --- .../prospective-fallback/next.config.js | 6 --- 10 files changed, 2 insertions(+), 207 deletions(-) delete mode 100644 test/e2e/app-dir/dynamic-io-errors/dynamic-io-errors.prospective-fallback.test.ts delete mode 100644 test/e2e/app-dir/dynamic-io-errors/fixtures/prospective-fallback/app/blog/[slug]/page.jsx delete mode 100644 test/e2e/app-dir/dynamic-io-errors/fixtures/prospective-fallback/app/layout.jsx delete mode 100644 test/e2e/app-dir/dynamic-io-errors/fixtures/prospective-fallback/next.config.js diff --git a/packages/next/src/build/index.ts b/packages/next/src/build/index.ts index 614e512d78a51..e629539aef7ad 100644 --- a/packages/next/src/build/index.ts +++ b/packages/next/src/build/index.ts @@ -193,7 +193,6 @@ import { } from '../server/lib/experimental/ppr' import { FallbackMode, fallbackModeToFallbackField } from '../lib/fallback' import { RenderingMode } from './rendering-mode' -import { getParamKeys } from '../server/request/fallback-params' import { InvariantError } from '../shared/lib/invariant-error' import { HTML_LIMITED_BOT_UA_RE_STRING } from '../shared/lib/router/utils/is-bot' import type { UseCacheTrackerKey } from './webpack/plugins/telemetry-plugin/use-cache-tracker-utils' @@ -1613,10 +1612,6 @@ export default async function build( const serverPropsPages = new Set() const additionalPaths = new Map() const staticPaths = new Map() - const prospectiveRenders = new Map< - string, - { page: string; originalAppPath: string } - >() const appNormalizedPaths = new Map() const fallbackModes = new Map() const appDefaultConfigs = new Map() @@ -1975,31 +1970,6 @@ export default async function build( staticPaths.set(originalAppPath, []) } - // As PPR isn't enabled for this route, if dynamic IO - // is enabled, and this is a dynamic route, we should - // complete a prospective render for the route so that - // we can use the fallback behavior. This lets us - // check that dynamic pages won't error when they - // enable PPR. - else if (config.experimental.dynamicIO && isDynamic) { - // If there's a page with a more specific render - // available, then we should skip the prospective - // render because it'll be done as a part of the - // that render to validate the dynamic state. - if ( - // The existence of any prerendered routes when - // PPR is disabled means that the route has more - // specific prerendered routes that should be - // used for the diagnostic render anyways. - !workerResult.prerenderedRoutes || - workerResult.prerenderedRoutes.length === 0 - ) { - prospectiveRenders.set(originalAppPath, { - page, - originalAppPath, - }) - } - } if (workerResult.prerenderedRoutes) { staticPaths.set( @@ -2718,28 +2688,6 @@ export default async function build( }) }) - // If the app does have dynamic IO enabled but does not have PPR - // enabled, then we need to perform a prospective render for all - // the dynamic pages to ensure that they won't error during - // rendering (due to a missing prelude). - for (const { - page, - originalAppPath, - } of prospectiveRenders.values()) { - defaultMap[page] = { - page: originalAppPath, - _ssgPath: page, - _fallbackRouteParams: getParamKeys(page), - // Prospective renders are only enabled for app pages. - _isAppDir: true, - // Prospective renders are only enabled when PPR is disabled. - _isRoutePPREnabled: false, - _isProspectiveRender: true, - // Dynamic IO does not currently support `dynamic === 'error'`. - _isDynamicError: false, - } - } - if (i18n) { for (const page of [ ...staticPages, diff --git a/packages/next/src/export/routes/app-page.ts b/packages/next/src/export/routes/app-page.ts index 7d896b57b5029..bfbaaa1a2b7b0 100644 --- a/packages/next/src/export/routes/app-page.ts +++ b/packages/next/src/export/routes/app-page.ts @@ -30,58 +30,6 @@ import type { RequestLifecycleOpts } from '../../server/base-server' import type { AppSharedContext } from '../../server/app-render/app-render' import type { MultiFileWriter } from '../../lib/multi-file-writer' -export async function prospectiveRenderAppPage( - req: MockedRequest, - res: MockedResponse, - page: string, - pathname: string, - query: NextParsedUrlQuery, - fallbackRouteParams: FallbackRouteParams | null, - partialRenderOpts: Omit, - sharedContext: AppSharedContext -): Promise { - const afterRunner = new AfterRunner() - - // If the page is `/_not-found`, then we should update the page to be `/404`. - // UNDERSCORE_NOT_FOUND_ROUTE value used here, however we don't want to import it here as it causes constants to be inlined which we don't want here. - if (page === '/_not-found/page') { - pathname = '/404' - } - - try { - await lazyRenderAppPage( - new NodeNextRequest(req), - new NodeNextResponse(res), - pathname, - query, - fallbackRouteParams, - { - ...partialRenderOpts, - waitUntil: afterRunner.context.waitUntil, - onClose: afterRunner.context.onClose, - onAfterTaskError: afterRunner.context.onTaskError, - }, - undefined, - false, - sharedContext - ) - - // TODO(after): if we abort a prerender because of an error in an after-callback - // we should probably communicate that better (and not log the error twice) - await afterRunner.executeAfter() - } catch (err) { - if (!isDynamicUsageError(err)) { - throw err - } - - // We should fail rendering if a client side rendering bailout - // occurred at the page level. - if (isBailoutToCSRError(err)) { - throw err - } - } -} - /** * Renders & exports a page associated with the /app directory */ diff --git a/packages/next/src/export/worker.ts b/packages/next/src/export/worker.ts index 7ef928b90104e..2aa6a4e48459f 100644 --- a/packages/next/src/export/worker.ts +++ b/packages/next/src/export/worker.ts @@ -26,7 +26,7 @@ import { createRequestResponseMocks } from '../server/lib/mock-request' import { isAppRouteRoute } from '../lib/is-app-route-route' import { hasNextSupport } from '../server/ci-info' import { exportAppRoute } from './routes/app-route' -import { exportAppPage, prospectiveRenderAppPage } from './routes/app-page' +import { exportAppPage } from './routes/app-page' import { exportPagesPage } from './routes/pages' import { getParams } from './helpers/get-params' import { createIncrementalCache } from './helpers/create-incremental-cache' @@ -104,10 +104,6 @@ async function exportPageImpl( // the renderOpts. _isRoutePPREnabled: isRoutePPREnabled, - // If this is a prospective render, we don't actually want to persist the - // result, we just want to use it to error the build if there's a problem. - _isProspectiveRender: isProspectiveRender = false, - // Configure the rendering of the page not to throw if an empty static shell // is generated while rendering using PPR. _doNotThrowOnEmptyStaticShell: doNotThrowOnEmptyStaticShell = false, @@ -287,21 +283,6 @@ async function exportPageImpl( buildId: input.buildId, } - // If this is a prospective render, don't return any metrics or revalidate - // timings as we aren't persisting this render (it was only to error). - if (isProspectiveRender) { - return prospectiveRenderAppPage( - req, - res, - page, - pathname, - query, - fallbackRouteParams, - renderOpts, - sharedContext - ) - } - return exportAppPage( req, res, diff --git a/packages/next/src/server/config-schema.ts b/packages/next/src/server/config-schema.ts index 5722af8fe92e2..cf2e060444674 100644 --- a/packages/next/src/server/config-schema.ts +++ b/packages/next/src/server/config-schema.ts @@ -41,7 +41,6 @@ const zExportMap: zod.ZodType = z.record( _isAppDir: z.boolean().optional(), _isDynamicError: z.boolean().optional(), _isRoutePPREnabled: z.boolean().optional(), - _isProspectiveRender: z.boolean().optional(), _doNotThrowOnEmptyStaticShell: z.boolean().optional(), }) ) diff --git a/packages/next/src/server/config-shared.ts b/packages/next/src/server/config-shared.ts index 0b5314a1e3461..1d1a8c3cf277d 100644 --- a/packages/next/src/server/config-shared.ts +++ b/packages/next/src/server/config-shared.ts @@ -743,16 +743,6 @@ export type ExportPathMap = { */ _isRoutePPREnabled?: boolean - /** - * When true, it indicates that this page is being rendered in an attempt to - * discover if the page will be safe to generate with PPR. This is only - * enabled when the app has `experimental.dynamicIO` enabled but does not - * have `experimental.ppr` enabled. - * - * @internal - */ - _isProspectiveRender?: boolean - /** * When true, it indicates that the diagnostic render for this page is * disabled. This is only used when the app has `experimental.ppr` and diff --git a/packages/next/src/server/request/fallback-params.ts b/packages/next/src/server/request/fallback-params.ts index e3af245362e80..52ebe2c2f7f65 100644 --- a/packages/next/src/server/request/fallback-params.ts +++ b/packages/next/src/server/request/fallback-params.ts @@ -3,7 +3,7 @@ import { getRouteRegex } from '../../shared/lib/router/utils/route-regex' export type FallbackRouteParams = ReadonlyMap -export function getParamKeys(page: string) { +function getParamKeys(page: string) { const pattern = getRouteRegex(page) const matcher = getRouteMatcher(pattern) diff --git a/test/e2e/app-dir/dynamic-io-errors/dynamic-io-errors.prospective-fallback.test.ts b/test/e2e/app-dir/dynamic-io-errors/dynamic-io-errors.prospective-fallback.test.ts deleted file mode 100644 index d9c59ec790450..0000000000000 --- a/test/e2e/app-dir/dynamic-io-errors/dynamic-io-errors.prospective-fallback.test.ts +++ /dev/null @@ -1,54 +0,0 @@ -import { nextTestSetup } from 'e2e-utils' - -describe(`Dynamic IO Prospective Fallback`, () => { - const { next, isNextDev, skipped } = nextTestSetup({ - files: __dirname + '/fixtures/prospective-fallback', - skipStart: true, - skipDeployment: true, - }) - - if (skipped) { - return - } - - if (isNextDev) { - it('should not error when visiting the page', async () => { - // Start the server, we expect this to succeed. - await next.start() - - const res = await next.fetch('/blog/123') - expect(res.status).toBe(200) - }) - } else { - it('should error on the build due to a missing suspense boundary', async () => { - try { - await next.start() - } catch { - // we expect the build to fail - } - - // TODO: Assert on component stack - expect(next.cliOutput).toContain( - 'Route "/blog/[slug]": A component accessed data, headers, params, searchParams, or a short-lived cache without a Suspense boundary nor a "use cache" above it.' - ) - }) - - it('should not error when we add the missing suspense boundary', async () => { - await next.patchFile( - 'app/blog/[slug]/loading.jsx', - ` - export default function Loading() { - return
Loading...
- } - ` - ) - - // We expect this to succeed. - await next.start() - - expect(next.cliOutput).not.toContain( - 'Route "/blog/[slug]": A component accessed data, headers, params, searchParams, or a short-lived cache without a Suspense boundary nor a "use cache" above it.' - ) - }) - } -}) diff --git a/test/e2e/app-dir/dynamic-io-errors/fixtures/prospective-fallback/app/blog/[slug]/page.jsx b/test/e2e/app-dir/dynamic-io-errors/fixtures/prospective-fallback/app/blog/[slug]/page.jsx deleted file mode 100644 index cc65c1e63b57b..0000000000000 --- a/test/e2e/app-dir/dynamic-io-errors/fixtures/prospective-fallback/app/blog/[slug]/page.jsx +++ /dev/null @@ -1,4 +0,0 @@ -export default async function BlogPage({ params }) { - const { slug } = await params - return
Blog: {slug}
-} diff --git a/test/e2e/app-dir/dynamic-io-errors/fixtures/prospective-fallback/app/layout.jsx b/test/e2e/app-dir/dynamic-io-errors/fixtures/prospective-fallback/app/layout.jsx deleted file mode 100644 index 803f17d863c8a..0000000000000 --- a/test/e2e/app-dir/dynamic-io-errors/fixtures/prospective-fallback/app/layout.jsx +++ /dev/null @@ -1,7 +0,0 @@ -export default function RootLayout({ children }) { - return ( - - {children} - - ) -} diff --git a/test/e2e/app-dir/dynamic-io-errors/fixtures/prospective-fallback/next.config.js b/test/e2e/app-dir/dynamic-io-errors/fixtures/prospective-fallback/next.config.js deleted file mode 100644 index 195ad29be3c54..0000000000000 --- a/test/e2e/app-dir/dynamic-io-errors/fixtures/prospective-fallback/next.config.js +++ /dev/null @@ -1,6 +0,0 @@ -module.exports = { - experimental: { - dynamicIO: true, - serverSourceMaps: true, - }, -}