From 6c2de5fca171b056e6ccca53bf1f63261f482c20 Mon Sep 17 00:00:00 2001 From: Hendrik Liebau Date: Thu, 15 May 2025 14:36:01 +0200 Subject: [PATCH 01/10] Remove DIO w/o PPR branch from `app-render.tsx` --- .../next/src/server/app-render/app-render.tsx | 1260 ++++++----------- 1 file changed, 408 insertions(+), 852 deletions(-) diff --git a/packages/next/src/server/app-render/app-render.tsx b/packages/next/src/server/app-render/app-render.tsx index 2fe923bdb642..47c698a89bdf 100644 --- a/packages/next/src/server/app-render/app-render.tsx +++ b/packages/next/src/server/app-render/app-render.tsx @@ -127,7 +127,6 @@ import { isPrerenderInterruptedError, createDynamicTrackingState, createDynamicValidationState, - getFirstDynamicReason, trackAllowedDynamicAccess, throwIfDisallowedDynamic, consumeDynamicAccess, @@ -150,7 +149,6 @@ import { createMutableActionQueue } from '../../client/components/app-router-ins import { getRevalidateReason } from '../instrumentation/utils' import { PAGE_SEGMENT_KEY } from '../../shared/lib/segment' import type { FallbackRouteParams } from '../request/fallback-params' -import { DynamicServerError } from '../../client/components/hooks-server-context' import { ServerPrerenderStreamResult, processPrelude, @@ -2754,49 +2752,168 @@ async function prerenderToStream( try { if (experimental.dynamicIO) { - if (experimental.isRoutePPREnabled) { - /** - * dynamicIO with PPR - * - * The general approach is to render the RSC stream first allowing any cache reads to resolve. - * Once we have settled all cache reads we restart the render and abort after a single Task. - * - * Unlike with the non PPR case we can't synchronously abort the render when a dynamic API is used - * during the initial render because we need to ensure all caches can be filled as part of the initial Task - * and a synchronous abort might prevent us from filling all caches. - * - * Once the render is complete we allow the SSR render to finish and use a combination of the postponed state - * and the reactServerIsDynamic value to determine how to treat the resulting render - */ - - // Prerender controller represents the lifetime of the prerender. - // It will be aborted when a Task is complete or a synchronously aborting - // API is called. Notably during cache-filling renders this does not actually - // terminate the render itself which will continue until all caches are filled - const initialServerPrerenderController = new AbortController() - - // This controller represents the lifetime of the React render call. Notably - // during the cache-filling render it is different from the prerender controller - // because we don't want to end the react render until all caches are filled. - const initialServerRenderController = new AbortController() - - // The cacheSignal helps us track whether caches are still filling or we are ready - // to cut the render off. - const cacheSignal = new CacheSignal() - - // The resume data cache here should use a fresh instance as it's - // performing a fresh prerender. If we get to implementing the - // prerendering of an already prerendered page, we should use the passed - // resume data cache instead. - const prerenderResumeDataCache = createPrerenderResumeDataCache() - - const initialServerPrerenderStore: PrerenderStore = (prerenderStore = { + /** + * dynamicIO with PPR + * + * The general approach is to render the RSC stream first allowing any cache reads to resolve. + * Once we have settled all cache reads we restart the render and abort after a single Task. + * + * Unlike with the non PPR case we can't synchronously abort the render when a dynamic API is used + * during the initial render because we need to ensure all caches can be filled as part of the initial Task + * and a synchronous abort might prevent us from filling all caches. + * + * Once the render is complete we allow the SSR render to finish and use a combination of the postponed state + * and the reactServerIsDynamic value to determine how to treat the resulting render + */ + + // Prerender controller represents the lifetime of the prerender. + // It will be aborted when a Task is complete or a synchronously aborting + // API is called. Notably during cache-filling renders this does not actually + // terminate the render itself which will continue until all caches are filled + const initialServerPrerenderController = new AbortController() + + // This controller represents the lifetime of the React render call. Notably + // during the cache-filling render it is different from the prerender controller + // because we don't want to end the react render until all caches are filled. + const initialServerRenderController = new AbortController() + + // The cacheSignal helps us track whether caches are still filling or we are ready + // to cut the render off. + const cacheSignal = new CacheSignal() + + // The resume data cache here should use a fresh instance as it's + // performing a fresh prerender. If we get to implementing the + // prerendering of an already prerendered page, we should use the passed + // resume data cache instead. + const prerenderResumeDataCache = createPrerenderResumeDataCache() + + const initialServerPrerenderStore: PrerenderStore = (prerenderStore = { + type: 'prerender', + phase: 'render', + rootParams, + implicitTags, + renderSignal: initialServerRenderController.signal, + controller: initialServerPrerenderController, + cacheSignal, + dynamicTracking: null, + revalidate: INFINITE_CACHE, + expire: INFINITE_CACHE, + stale: INFINITE_CACHE, + tags: [...implicitTags.tags], + prerenderResumeDataCache, + hmrRefreshHash: undefined, + }) + + // We're not going to use the result of this render because the only time it could be used + // is if it completes in a microtask and that's likely very rare for any non-trivial app + const initialServerPayload = await workUnitAsyncStorage.run( + initialServerPrerenderStore, + getRSCPayload, + tree, + ctx, + res.statusCode === 404 + ) + + const pendingInitialServerResult = workUnitAsyncStorage.run( + initialServerPrerenderStore, + ComponentMod.prerender, + initialServerPayload, + clientReferenceManifest.clientModules, + { + onError: (err) => { + const digest = getDigestForWellKnownError(err) + + if (digest) { + return digest + } + + if (initialServerPrerenderController.signal.aborted) { + // The render aborted before this error was handled which indicates + // the error is caused by unfinished components within the render + return + } else if ( + process.env.NEXT_DEBUG_BUILD || + process.env.__NEXT_VERBOSE_LOGGING + ) { + printDebugThrownValueForProspectiveRender(err, workStore.route) + } + }, + // we don't care to track postpones during the prospective render because we need + // to always do a final render anyway + onPostpone: undefined, + // We don't want to stop rendering until the cacheSignal is complete so we pass + // a different signal to this render call than is used by dynamic APIs to signify + // transitioning out of the prerender environment + signal: initialServerRenderController.signal, + } + ) + + // Wait for all caches to be finished filling and for async imports to resolve + trackPendingModules(cacheSignal) + await cacheSignal.cacheReady() + + initialServerRenderController.abort() + initialServerPrerenderController.abort() + + // We don't need to continue the prerender process if we already + // detected invalid dynamic usage in the initial prerender phase. + if (workStore.invalidDynamicUsageError) { + if ( + isUseCacheTimeoutError(workStore.invalidDynamicUsageError) && + allowEmptyStaticShell + ) { + // If this is a "use cache" timeout error, and empty shells are + // allowed (i.e. we're prerendering a fallback shell, and there are + // also more specific routes prerendered) we return an empty shell. + return { + digestErrorsMap: reactServerErrorsByDigest, + ssrErrors: allCapturedErrors, + stream: new ReadableStream({ + start(controller) { + controller.close() + }, + }), + collectedRevalidate: INFINITE_CACHE, + collectedExpire: INFINITE_CACHE, + collectedStale: INFINITE_CACHE, + collectedTags: null, + } + } + + // Otherwise we throw the error to fail the build. + throw workStore.invalidDynamicUsageError + } + + let initialServerResult + try { + initialServerResult = await createReactServerPrerenderResult( + pendingInitialServerResult + ) + } catch (err) { + if ( + initialServerRenderController.signal.aborted || + initialServerPrerenderController.signal.aborted + ) { + // These are expected errors that might error the prerender. we ignore them. + } else if ( + process.env.NEXT_DEBUG_BUILD || + process.env.__NEXT_VERBOSE_LOGGING + ) { + // We don't normally log these errors because we are going to retry anyway but + // it can be useful for debugging Next.js itself to get visibility here when needed + printDebugThrownValueForProspectiveRender(err, workStore.route) + } + } + + if (initialServerResult) { + const initialClientController = new AbortController() + const initialClientPrerenderStore: PrerenderStore = { type: 'prerender', phase: 'render', rootParams, implicitTags, - renderSignal: initialServerRenderController.signal, - controller: initialServerPrerenderController, + renderSignal: initialClientController.signal, + controller: initialClientController, cacheSignal, dynamicTracking: null, revalidate: INFINITE_CACHE, @@ -2805,24 +2922,24 @@ async function prerenderToStream( tags: [...implicitTags.tags], prerenderResumeDataCache, hmrRefreshHash: undefined, - }) - - // We're not going to use the result of this render because the only time it could be used - // is if it completes in a microtask and that's likely very rare for any non-trivial app - const initialServerPayload = await workUnitAsyncStorage.run( - initialServerPrerenderStore, - getRSCPayload, - tree, - ctx, - res.statusCode === 404 - ) + } - const pendingInitialServerResult = workUnitAsyncStorage.run( - initialServerPrerenderStore, - ComponentMod.prerender, - initialServerPayload, - clientReferenceManifest.clientModules, + const prerender = require('react-dom/static.edge') + .prerender as (typeof import('react-dom/static.edge'))['prerender'] + const pendingInitialClientResult = workUnitAsyncStorage.run( + initialClientPrerenderStore, + prerender, + , { + signal: initialClientController.signal, onError: (err) => { const digest = getDigestForWellKnownError(err) @@ -2830,72 +2947,25 @@ async function prerenderToStream( return digest } - if (initialServerPrerenderController.signal.aborted) { - // The render aborted before this error was handled which indicates - // the error is caused by unfinished components within the render - return + if (initialClientController.signal.aborted) { + // These are expected errors that might error the prerender. we ignore them. } else if ( process.env.NEXT_DEBUG_BUILD || process.env.__NEXT_VERBOSE_LOGGING ) { + // We don't normally log these errors because we are going to retry anyway but + // it can be useful for debugging Next.js itself to get visibility here when needed printDebugThrownValueForProspectiveRender(err, workStore.route) } }, - // we don't care to track postpones during the prospective render because we need - // to always do a final render anyway - onPostpone: undefined, - // We don't want to stop rendering until the cacheSignal is complete so we pass - // a different signal to this render call than is used by dynamic APIs to signify - // transitioning out of the prerender environment - signal: initialServerRenderController.signal, + bootstrapScripts: [bootstrapScript], } ) - // Wait for all caches to be finished filling and for async imports to resolve - trackPendingModules(cacheSignal) - await cacheSignal.cacheReady() - - initialServerRenderController.abort() - initialServerPrerenderController.abort() - - // We don't need to continue the prerender process if we already - // detected invalid dynamic usage in the initial prerender phase. - if (workStore.invalidDynamicUsageError) { - if ( - isUseCacheTimeoutError(workStore.invalidDynamicUsageError) && - allowEmptyStaticShell - ) { - // If this is a "use cache" timeout error, and empty shells are - // allowed (i.e. we're prerendering a fallback shell, and there are - // also more specific routes prerendered) we return an empty shell. - return { - digestErrorsMap: reactServerErrorsByDigest, - ssrErrors: allCapturedErrors, - stream: new ReadableStream({ - start(controller) { - controller.close() - }, - }), - collectedRevalidate: INFINITE_CACHE, - collectedExpire: INFINITE_CACHE, - collectedStale: INFINITE_CACHE, - collectedTags: null, - } - } - - // Otherwise we throw the error to fail the build. - throw workStore.invalidDynamicUsageError - } - - let initialServerResult - try { - initialServerResult = await createReactServerPrerenderResult( - pendingInitialServerResult - ) - } catch (err) { + pendingInitialClientResult.catch((err) => { if ( initialServerRenderController.signal.aborted || - initialServerPrerenderController.signal.aborted + isPrerenderInterruptedError(err) ) { // These are expected errors that might error the prerender. we ignore them. } else if ( @@ -2906,809 +2976,295 @@ async function prerenderToStream( // it can be useful for debugging Next.js itself to get visibility here when needed printDebugThrownValueForProspectiveRender(err, workStore.route) } - } - - if (initialServerResult) { - const initialClientController = new AbortController() - const initialClientPrerenderStore: PrerenderStore = { - type: 'prerender', - phase: 'render', - rootParams, - implicitTags, - renderSignal: initialClientController.signal, - controller: initialClientController, - cacheSignal, - dynamicTracking: null, - revalidate: INFINITE_CACHE, - expire: INFINITE_CACHE, - stale: INFINITE_CACHE, - tags: [...implicitTags.tags], - prerenderResumeDataCache, - hmrRefreshHash: undefined, - } - - const prerender = require('react-dom/static.edge') - .prerender as (typeof import('react-dom/static.edge'))['prerender'] - const pendingInitialClientResult = workUnitAsyncStorage.run( - initialClientPrerenderStore, - prerender, - , - { - signal: initialClientController.signal, - onError: (err) => { - const digest = getDigestForWellKnownError(err) - - if (digest) { - return digest - } - - if (initialClientController.signal.aborted) { - // These are expected errors that might error the prerender. we ignore them. - } else if ( - process.env.NEXT_DEBUG_BUILD || - process.env.__NEXT_VERBOSE_LOGGING - ) { - // We don't normally log these errors because we are going to retry anyway but - // it can be useful for debugging Next.js itself to get visibility here when needed - printDebugThrownValueForProspectiveRender( - err, - workStore.route - ) - } - }, - bootstrapScripts: [bootstrapScript], - } - ) - - pendingInitialClientResult.catch((err) => { - if ( - initialServerRenderController.signal.aborted || - isPrerenderInterruptedError(err) - ) { - // These are expected errors that might error the prerender. we ignore them. - } else if ( - process.env.NEXT_DEBUG_BUILD || - process.env.__NEXT_VERBOSE_LOGGING - ) { - // We don't normally log these errors because we are going to retry anyway but - // it can be useful for debugging Next.js itself to get visibility here when needed - printDebugThrownValueForProspectiveRender(err, workStore.route) - } - }) - - // This is mostly needed for dynamic `import()`s in client components. - // Promises passed to client were already awaited above (assuming that they came from cached functions) - trackPendingModules(cacheSignal) - await cacheSignal.cacheReady() - initialClientController.abort() - } - - let serverIsDynamic = false - const finalServerController = new AbortController() - const serverDynamicTracking = createDynamicTrackingState( - isDebugDynamicAccesses - ) - - const finalRenderPrerenderStore: PrerenderStore = (prerenderStore = { - type: 'prerender', - phase: 'render', - rootParams, - implicitTags, - renderSignal: finalServerController.signal, - controller: finalServerController, - // During the final prerender we don't need to track cache access so we omit the signal - cacheSignal: null, - dynamicTracking: serverDynamicTracking, - revalidate: INFINITE_CACHE, - expire: INFINITE_CACHE, - stale: INFINITE_CACHE, - tags: [...implicitTags.tags], - prerenderResumeDataCache, - hmrRefreshHash: undefined, }) - const finalAttemptRSCPayload = await workUnitAsyncStorage.run( - finalRenderPrerenderStore, - getRSCPayload, - tree, - ctx, - res.statusCode === 404 - ) - let prerenderIsPending = true - const reactServerResult = (reactServerPrerenderResult = - await createReactServerPrerenderResult( - prerenderAndAbortInSequentialTasks( - async () => { - const prerenderResult = await workUnitAsyncStorage.run( - // The store to scope - finalRenderPrerenderStore, - // The function to run - ComponentMod.prerender, - // ... the arguments for the function to run - finalAttemptRSCPayload, - clientReferenceManifest.clientModules, - { - onError: (err: unknown) => { - return serverComponentsErrorHandler(err) - }, - signal: finalServerController.signal, - } - ) - prerenderIsPending = false - return prerenderResult - }, - () => { - if (finalServerController.signal.aborted) { - // If the server controller is already aborted we must have called something - // that required aborting the prerender synchronously such as with new Date() - serverIsDynamic = true - return - } - - if (prerenderIsPending) { - // If prerenderIsPending then we have blocked for longer than a Task and we assume - // there is something unfinished. - serverIsDynamic = true - } - finalServerController.abort() - } - ) - )) + // This is mostly needed for dynamic `import()`s in client components. + // Promises passed to client were already awaited above (assuming that they came from cached functions) + trackPendingModules(cacheSignal) + await cacheSignal.cacheReady() + initialClientController.abort() + } - const clientDynamicTracking = createDynamicTrackingState( - isDebugDynamicAccesses - ) - const finalClientController = new AbortController() - const finalClientPrerenderStore: PrerenderStore = { - type: 'prerender', - phase: 'render', - rootParams, - implicitTags, - renderSignal: finalClientController.signal, - controller: finalClientController, - // For HTML Generation we don't need to track cache reads (RSC only) - cacheSignal: null, - dynamicTracking: clientDynamicTracking, - revalidate: INFINITE_CACHE, - expire: INFINITE_CACHE, - stale: INFINITE_CACHE, - tags: [...implicitTags.tags], - prerenderResumeDataCache, - hmrRefreshHash: undefined, - } + let serverIsDynamic = false + const finalServerController = new AbortController() + const serverDynamicTracking = createDynamicTrackingState( + isDebugDynamicAccesses + ) - let clientIsDynamic = false - let dynamicValidation = createDynamicValidationState() + const finalRenderPrerenderStore: PrerenderStore = (prerenderStore = { + type: 'prerender', + phase: 'render', + rootParams, + implicitTags, + renderSignal: finalServerController.signal, + controller: finalServerController, + // During the final prerender we don't need to track cache access so we omit the signal + cacheSignal: null, + dynamicTracking: serverDynamicTracking, + revalidate: INFINITE_CACHE, + expire: INFINITE_CACHE, + stale: INFINITE_CACHE, + tags: [...implicitTags.tags], + prerenderResumeDataCache, + hmrRefreshHash: undefined, + }) - const prerender = require('react-dom/static.edge') - .prerender as (typeof import('react-dom/static.edge'))['prerender'] - let { prelude: unprocessedPrelude, postponed } = - await prerenderAndAbortInSequentialTasks( - () => - workUnitAsyncStorage.run( - finalClientPrerenderStore, - prerender, - , + const finalAttemptRSCPayload = await workUnitAsyncStorage.run( + finalRenderPrerenderStore, + getRSCPayload, + tree, + ctx, + res.statusCode === 404 + ) + let prerenderIsPending = true + const reactServerResult = (reactServerPrerenderResult = + await createReactServerPrerenderResult( + prerenderAndAbortInSequentialTasks( + async () => { + const prerenderResult = await workUnitAsyncStorage.run( + // The store to scope + finalRenderPrerenderStore, + // The function to run + ComponentMod.prerender, + // ... the arguments for the function to run + finalAttemptRSCPayload, + clientReferenceManifest.clientModules, { - signal: finalClientController.signal, - onError: (err: unknown, errorInfo: ErrorInfo) => { - if ( - isPrerenderInterruptedError(err) || - finalClientController.signal.aborted - ) { - clientIsDynamic = true - - const componentStack: string | undefined = ( - errorInfo as any - ).componentStack - if (typeof componentStack === 'string') { - trackAllowedDynamicAccess( - workStore.route, - componentStack, - dynamicValidation - ) - } - return - } - - return htmlRendererErrorHandler(err, errorInfo) - }, - onHeaders: (headers: Headers) => { - headers.forEach((value, key) => { - appendHeader(key, value) - }) + onError: (err: unknown) => { + return serverComponentsErrorHandler(err) }, - maxHeadersLength: reactMaxHeadersLength, - bootstrapScripts: [bootstrapScript], + signal: finalServerController.signal, } - ), + ) + prerenderIsPending = false + return prerenderResult + }, () => { - finalClientController.abort() - } - ) + if (finalServerController.signal.aborted) { + // If the server controller is already aborted we must have called something + // that required aborting the prerender synchronously such as with new Date() + serverIsDynamic = true + return + } - const { prelude, preludeIsEmpty } = - await processPrelude(unprocessedPrelude) - - // If we're allowing an empty static shell, then we don't need to track - // any dynamic access that occurs above the suspense boundary because - // we'll do so in the route shell. - if (!allowEmptyStaticShell) { - throwIfDisallowedDynamic( - workStore, - preludeIsEmpty, - dynamicValidation, - serverDynamicTracking, - clientDynamicTracking + if (prerenderIsPending) { + // If prerenderIsPending then we have blocked for longer than a Task and we assume + // there is something unfinished. + serverIsDynamic = true + } + finalServerController.abort() + } ) - } - - const getServerInsertedHTML = makeGetServerInsertedHTML({ - polyfills, - renderServerInsertedHTML, - serverCapturedErrors: allCapturedErrors, - basePath, - tracingMetadata: tracingMetadata, - }) - - const flightData = await streamToBuffer(reactServerResult.asStream()) - metadata.flightData = flightData - metadata.segmentData = await collectSegmentData( - flightData, - finalRenderPrerenderStore, - ComponentMod, - renderOpts, - fallbackRouteParams - ) - - if (serverIsDynamic || clientIsDynamic) { - if (postponed != null) { - // Dynamic HTML case - metadata.postponed = await getDynamicHTMLPostponedState( - postponed, - fallbackRouteParams, - prerenderResumeDataCache - ) - } else { - // Dynamic Data case - metadata.postponed = await getDynamicDataPostponedState( - prerenderResumeDataCache - ) - } - reactServerResult.consume() - return { - digestErrorsMap: reactServerErrorsByDigest, - ssrErrors: allCapturedErrors, - stream: await continueDynamicPrerender(prelude, { - getServerInsertedHTML, - getServerInsertedMetadata, - }), - dynamicAccess: consumeDynamicAccess( - serverDynamicTracking, - clientDynamicTracking - ), - // TODO: Should this include the SSR pass? - collectedRevalidate: finalRenderPrerenderStore.revalidate, - collectedExpire: finalRenderPrerenderStore.expire, - collectedStale: finalRenderPrerenderStore.stale, - collectedTags: finalRenderPrerenderStore.tags, - } - } else { - // Static case - if (workStore.forceDynamic) { - throw new StaticGenBailoutError( - 'Invariant: a Page with `dynamic = "force-dynamic"` did not trigger the dynamic pathway. This is a bug in Next.js' - ) - } + )) - let htmlStream = prelude - if (postponed != null) { - // We postponed but nothing dynamic was used. We resume the render now and immediately abort it - // so we can set all the postponed boundaries to client render mode before we store the HTML response - const resume = require('react-dom/server.edge') - .resume as (typeof import('react-dom/server.edge'))['resume'] + const clientDynamicTracking = createDynamicTrackingState( + isDebugDynamicAccesses + ) + const finalClientController = new AbortController() + const finalClientPrerenderStore: PrerenderStore = { + type: 'prerender', + phase: 'render', + rootParams, + implicitTags, + renderSignal: finalClientController.signal, + controller: finalClientController, + // For HTML Generation we don't need to track cache reads (RSC only) + cacheSignal: null, + dynamicTracking: clientDynamicTracking, + revalidate: INFINITE_CACHE, + expire: INFINITE_CACHE, + stale: INFINITE_CACHE, + tags: [...implicitTags.tags], + prerenderResumeDataCache, + hmrRefreshHash: undefined, + } - // We don't actually want to render anything so we just pass a stream - // that never resolves. The resume call is going to abort immediately anyway - const foreverStream = new ReadableStream() + let clientIsDynamic = false + let dynamicValidation = createDynamicValidationState() - const resumeStream = await resume( + const prerender = require('react-dom/static.edge') + .prerender as (typeof import('react-dom/static.edge'))['prerender'] + let { prelude: unprocessedPrelude, postponed } = + await prerenderAndAbortInSequentialTasks( + () => + workUnitAsyncStorage.run( + finalClientPrerenderStore, + prerender, {}} + reactServerStream={reactServerResult.asUnclosingStream()} + preinitScripts={preinitScripts} clientReferenceManifest={clientReferenceManifest} ServerInsertedHTMLProvider={ServerInsertedHTMLProvider} ServerInsertedMetadataProvider={ServerInsertedMetadataProvider} gracefullyDegrade={!!botType} nonce={nonce} />, - JSON.parse(JSON.stringify(postponed)), { - signal: createPostponedAbortSignal('static prerender resume'), - onError: htmlRendererErrorHandler, - nonce, - } - ) - - // First we write everything from the prerender, then we write everything from the aborted resume render - htmlStream = chainStreams(prelude, resumeStream) - } + signal: finalClientController.signal, + onError: (err: unknown, errorInfo: ErrorInfo) => { + if ( + isPrerenderInterruptedError(err) || + finalClientController.signal.aborted + ) { + clientIsDynamic = true + + const componentStack: string | undefined = ( + errorInfo as any + ).componentStack + if (typeof componentStack === 'string') { + trackAllowedDynamicAccess( + workStore.route, + componentStack, + dynamicValidation + ) + } + return + } - return { - digestErrorsMap: reactServerErrorsByDigest, - ssrErrors: allCapturedErrors, - stream: await continueStaticPrerender(htmlStream, { - inlinedDataStream: createInlinedDataReadableStream( - reactServerResult.consumeAsStream(), - nonce, - formState - ), - getServerInsertedHTML, - getServerInsertedMetadata, - }), - dynamicAccess: consumeDynamicAccess( - serverDynamicTracking, - clientDynamicTracking + return htmlRendererErrorHandler(err, errorInfo) + }, + onHeaders: (headers: Headers) => { + headers.forEach((value, key) => { + appendHeader(key, value) + }) + }, + maxHeadersLength: reactMaxHeadersLength, + bootstrapScripts: [bootstrapScript], + } ), - // TODO: Should this include the SSR pass? - collectedRevalidate: finalRenderPrerenderStore.revalidate, - collectedExpire: finalRenderPrerenderStore.expire, - collectedStale: finalRenderPrerenderStore.stale, - collectedTags: finalRenderPrerenderStore.tags, + () => { + finalClientController.abort() } - } - } else { - /** - * dynamicIO without PPR - * - * The general approach is to render the RSC tree first allowing for any inflight - * caches to resolve. Once we have settled inflight caches we can check and see if any - * synchronous dynamic APIs were used. If so we don't need to bother doing anything more - * because the page will be dynamic on re-render anyway - * - * If no sync dynamic APIs were used we then re-render and abort after a single Task. - * If the render errors we know that the page has some dynamic IO. This assumes and relies - * upon caches reading from a in process memory cache and resolving in a microtask. While this - * is true from our own default cache implementation and if you don't exceed our LRU size it - * might not be true for custom cache implementations. - * - * Future implementations can do some different strategies during build like using IPC to - * synchronously fill caches during this special rendering mode. For now this heuristic should work - */ - - const cache = workStore.incrementalCache - if (!cache) { - throw new Error( - 'Expected incremental cache to exist. This is a bug in Next.js' - ) - } - - // Prerender controller represents the lifetime of the prerender. - // It will be aborted when a Task is complete or a synchronously aborting - // API is called. Notably during cache-filling renders this does not actually - // terminate the render itself which will continue until all caches are filled - const initialServerPrerenderController = new AbortController() - - // This controller represents the lifetime of the React render call. Notably - // during the cache-filling render it is different from the prerender controller - // because we don't want to end the react render until all caches are filled. - const initialServerRenderController = new AbortController() - - const cacheSignal = new CacheSignal() - const prerenderResumeDataCache = createPrerenderResumeDataCache() - - const initialServerPrerenderStore: PrerenderStore = (prerenderStore = { - type: 'prerender', - phase: 'render', - rootParams, - implicitTags, - renderSignal: initialServerRenderController.signal, - controller: initialServerPrerenderController, - cacheSignal, - dynamicTracking: null, - revalidate: INFINITE_CACHE, - expire: INFINITE_CACHE, - stale: INFINITE_CACHE, - tags: [...implicitTags.tags], - prerenderResumeDataCache, - hmrRefreshHash: undefined, - }) + ) - const initialClientController = new AbortController() - const initialClientPrerenderStore: PrerenderStore = (prerenderStore = { - type: 'prerender', - phase: 'render', - rootParams, - implicitTags, - renderSignal: initialClientController.signal, - controller: initialClientController, - cacheSignal, - dynamicTracking: null, - revalidate: INFINITE_CACHE, - expire: INFINITE_CACHE, - stale: INFINITE_CACHE, - tags: [...implicitTags.tags], - prerenderResumeDataCache, - hmrRefreshHash: undefined, - }) + const { prelude, preludeIsEmpty } = + await processPrelude(unprocessedPrelude) - // We're not going to use the result of this render because the only time it could be used - // is if it completes in a microtask and that's likely very rare for any non-trivial app - const firstAttemptRSCPayload = await workUnitAsyncStorage.run( - initialServerPrerenderStore, - getRSCPayload, - tree, - ctx, - res.statusCode === 404 + // If we're allowing an empty static shell, then we don't need to track + // any dynamic access that occurs above the suspense boundary because + // we'll do so in the route shell. + if (!allowEmptyStaticShell) { + throwIfDisallowedDynamic( + workStore, + preludeIsEmpty, + dynamicValidation, + serverDynamicTracking, + clientDynamicTracking ) + } - let initialServerStream - try { - initialServerStream = workUnitAsyncStorage.run( - initialServerPrerenderStore, - ComponentMod.renderToReadableStream, - firstAttemptRSCPayload, - clientReferenceManifest.clientModules, - { - onError: (err) => { - const digest = getDigestForWellKnownError(err) + const getServerInsertedHTML = makeGetServerInsertedHTML({ + polyfills, + renderServerInsertedHTML, + serverCapturedErrors: allCapturedErrors, + basePath, + tracingMetadata: tracingMetadata, + }) - if (digest) { - return digest - } + const flightData = await streamToBuffer(reactServerResult.asStream()) + metadata.flightData = flightData + metadata.segmentData = await collectSegmentData( + flightData, + finalRenderPrerenderStore, + ComponentMod, + renderOpts, + fallbackRouteParams + ) - if ( - initialServerPrerenderController.signal.aborted || - initialServerRenderController.signal.aborted - ) { - // The render aborted before this error was handled which indicates - // the error is caused by unfinished components within the render - return - } else if ( - process.env.NEXT_DEBUG_BUILD || - process.env.__NEXT_VERBOSE_LOGGING - ) { - printDebugThrownValueForProspectiveRender( - err, - workStore.route - ) - } - }, - signal: initialServerRenderController.signal, - } + if (serverIsDynamic || clientIsDynamic) { + if (postponed != null) { + // Dynamic HTML case + metadata.postponed = await getDynamicHTMLPostponedState( + postponed, + fallbackRouteParams, + prerenderResumeDataCache + ) + } else { + // Dynamic Data case + metadata.postponed = await getDynamicDataPostponedState( + prerenderResumeDataCache + ) + } + reactServerResult.consume() + return { + digestErrorsMap: reactServerErrorsByDigest, + ssrErrors: allCapturedErrors, + stream: await continueDynamicPrerender(prelude, { + getServerInsertedHTML, + getServerInsertedMetadata, + }), + dynamicAccess: consumeDynamicAccess( + serverDynamicTracking, + clientDynamicTracking + ), + // TODO: Should this include the SSR pass? + collectedRevalidate: finalRenderPrerenderStore.revalidate, + collectedExpire: finalRenderPrerenderStore.expire, + collectedStale: finalRenderPrerenderStore.stale, + collectedTags: finalRenderPrerenderStore.tags, + } + } else { + // Static case + if (workStore.forceDynamic) { + throw new StaticGenBailoutError( + 'Invariant: a Page with `dynamic = "force-dynamic"` did not trigger the dynamic pathway. This is a bug in Next.js' ) - } catch (err: unknown) { - if ( - initialServerPrerenderController.signal.aborted || - initialServerRenderController.signal.aborted - ) { - // These are expected errors that might error the prerender. we ignore them. - } else if ( - process.env.NEXT_DEBUG_BUILD || - process.env.__NEXT_VERBOSE_LOGGING - ) { - // We don't normally log these errors because we are going to retry anyway but - // it can be useful for debugging Next.js itself to get visibility here when needed - printDebugThrownValueForProspectiveRender(err, workStore.route) - } } - if (initialServerStream) { - const prerender = require('react-dom/static.edge') - .prerender as (typeof import('react-dom/static.edge'))['prerender'] - const pendingInitialClientResult = workUnitAsyncStorage.run( - initialClientPrerenderStore, - prerender, + let htmlStream = prelude + if (postponed != null) { + // We postponed but nothing dynamic was used. We resume the render now and immediately abort it + // so we can set all the postponed boundaries to client render mode before we store the HTML response + const resume = require('react-dom/server.edge') + .resume as (typeof import('react-dom/server.edge'))['resume'] + + // We don't actually want to render anything so we just pass a stream + // that never resolves. The resume call is going to abort immediately anyway + const foreverStream = new ReadableStream() + + const resumeStream = await resume( {}} clientReferenceManifest={clientReferenceManifest} ServerInsertedHTMLProvider={ServerInsertedHTMLProvider} ServerInsertedMetadataProvider={ServerInsertedMetadataProvider} gracefullyDegrade={!!botType} nonce={nonce} />, + JSON.parse(JSON.stringify(postponed)), { - signal: initialClientController.signal, - onError: (err) => { - const digest = getDigestForWellKnownError(err) - - if (digest) { - return digest - } - - if (initialClientController.signal.aborted) { - // These are expected errors that might error the prerender. we ignore them. - } else if ( - process.env.NEXT_DEBUG_BUILD || - process.env.__NEXT_VERBOSE_LOGGING - ) { - // We don't normally log these errors because we are going to retry anyway but - // it can be useful for debugging Next.js itself to get visibility here when needed - printDebugThrownValueForProspectiveRender( - err, - workStore.route - ) - } - }, - bootstrapScripts: [bootstrapScript], - } - ) - pendingInitialClientResult.catch((err: unknown) => { - if (initialClientController.signal.aborted) { - // We aborted the render normally and can ignore this error - } else { - // We're going to retry to so we normally would suppress this error but - // when verbose logging is on we print it - if (process.env.__NEXT_VERBOSE_LOGGING) { - printDebugThrownValueForProspectiveRender(err, workStore.route) - } - } - }) - } - - // Wait for all caches to be finished filling and for async imports to resolve - trackPendingModules(cacheSignal) - await cacheSignal.cacheReady() - - // It is important that we abort the SSR render first to avoid - // connection closed errors from having an incomplete RSC stream - initialClientController.abort() - initialServerRenderController.abort() - initialServerPrerenderController.abort() - - // We don't need to continue the prerender process if we already - // detected invalid dynamic usage in the initial prerender phase. - if (workStore.invalidDynamicUsageError) { - throw workStore.invalidDynamicUsageError - } - - // We've now filled caches and triggered any inadvertant sync bailouts - // due to lazy module initialization. We can restart our render to capture results - - let serverIsDynamic = false - const finalServerController = new AbortController() - const serverDynamicTracking = createDynamicTrackingState( - isDebugDynamicAccesses - ) - - const finalServerPrerenderStore: PrerenderStore = (prerenderStore = { - type: 'prerender', - phase: 'render', - rootParams, - implicitTags, - renderSignal: finalServerController.signal, - controller: finalServerController, - // During the final prerender we don't need to track cache access so we omit the signal - cacheSignal: null, - dynamicTracking: serverDynamicTracking, - revalidate: INFINITE_CACHE, - expire: INFINITE_CACHE, - stale: INFINITE_CACHE, - tags: [...implicitTags.tags], - prerenderResumeDataCache, - hmrRefreshHash: undefined, - }) - - let clientIsDynamic = false - const finalClientController = new AbortController() - const clientDynamicTracking = createDynamicTrackingState( - isDebugDynamicAccesses - ) - const dynamicValidation = createDynamicValidationState() - - const finalClientPrerenderStore: PrerenderStore = (prerenderStore = { - type: 'prerender', - phase: 'render', - rootParams, - implicitTags, - renderSignal: finalClientController.signal, - controller: finalClientController, - // During the final prerender we don't need to track cache access so we omit the signal - cacheSignal: null, - dynamicTracking: clientDynamicTracking, - revalidate: INFINITE_CACHE, - expire: INFINITE_CACHE, - stale: INFINITE_CACHE, - tags: [...implicitTags.tags], - prerenderResumeDataCache, - hmrRefreshHash: undefined, - }) - - const finalServerPayload = await workUnitAsyncStorage.run( - finalServerPrerenderStore, - getRSCPayload, - tree, - ctx, - res.statusCode === 404 - ) - - const serverPrerenderStreamResult = (reactServerPrerenderResult = - await prerenderServerWithPhases( - finalServerController.signal, - () => - workUnitAsyncStorage.run( - finalServerPrerenderStore, - ComponentMod.renderToReadableStream, - finalServerPayload, - clientReferenceManifest.clientModules, - { - onError: (err: unknown) => { - if (finalServerController.signal.aborted) { - serverIsDynamic = true - if (isPrerenderInterruptedError(err)) { - return err.digest - } - return getDigestForWellKnownError(err) - } - - return serverComponentsErrorHandler(err) - }, - signal: finalServerController.signal, - } - ), - () => { - finalServerController.abort() - } - )) - - let htmlStream, preludeIsEmpty - const serverPhasedStream = serverPrerenderStreamResult.asPhasedStream() - try { - const prerender = require('react-dom/static.edge') - .prerender as (typeof import('react-dom/static.edge'))['prerender'] - const result = await prerenderClientWithPhases( - () => - workUnitAsyncStorage.run( - finalClientPrerenderStore, - prerender, - , - { - signal: finalClientController.signal, - onError: (err: unknown, errorInfo: ErrorInfo) => { - if ( - isPrerenderInterruptedError(err) || - finalClientController.signal.aborted - ) { - clientIsDynamic = true - - const componentStack: string | undefined = ( - errorInfo as any - ).componentStack - if (typeof componentStack === 'string') { - trackAllowedDynamicAccess( - workStore.route, - componentStack, - dynamicValidation - ) - } - return - } - - return htmlRendererErrorHandler(err, errorInfo) - }, - bootstrapScripts: [bootstrapScript], - } - ), - () => { - finalClientController.abort() - serverPhasedStream.assertExhausted() + signal: createPostponedAbortSignal('static prerender resume'), + onError: htmlRendererErrorHandler, + nonce, } ) - const processed = await processPrelude(result.prelude) - htmlStream = processed.prelude - preludeIsEmpty = processed.preludeIsEmpty - } catch (err) { - if ( - isPrerenderInterruptedError(err) || - finalClientController.signal.aborted - ) { - preludeIsEmpty = true - } else { - // This error is something else and should bubble up - throw err - } - } - // If we're allowing an empty static shell, then we don't need to track - // any dynamic access that occurs above the suspense boundary because - // we'll do so in the route shell. - if (!allowEmptyStaticShell) { - // We don't have a shell because the root errored when we aborted. - throwIfDisallowedDynamic( - workStore, - preludeIsEmpty, - dynamicValidation, - serverDynamicTracking, - clientDynamicTracking - ) - } - - if (serverIsDynamic || clientIsDynamic) { - const dynamicReason = serverIsDynamic - ? getFirstDynamicReason(serverDynamicTracking) - : getFirstDynamicReason(clientDynamicTracking) - if (dynamicReason) { - throw new DynamicServerError( - `Route "${workStore.route}" couldn't be rendered statically because it used \`${dynamicReason}\`. See more info here: https://nextjs.org/docs/messages/next-prerender-data` - ) - } else { - throw new DynamicServerError( - `Route "${workStore.route}" couldn't be rendered statically it accessed data without explicitly caching it. See more info here: https://nextjs.org/docs/messages/next-prerender-data` - ) - } + // First we write everything from the prerender, then we write everything from the aborted resume render + htmlStream = chainStreams(prelude, resumeStream) } - const flightData = await streamToBuffer( - serverPrerenderStreamResult.asStream() - ) - metadata.flightData = flightData - metadata.segmentData = await collectSegmentData( - flightData, - finalClientPrerenderStore, - ComponentMod, - renderOpts, - fallbackRouteParams - ) - - const getServerInsertedHTML = makeGetServerInsertedHTML({ - polyfills, - renderServerInsertedHTML, - serverCapturedErrors: allCapturedErrors, - basePath, - tracingMetadata: tracingMetadata, - }) return { digestErrorsMap: reactServerErrorsByDigest, ssrErrors: allCapturedErrors, - stream: await continueFizzStream(htmlStream!, { + stream: await continueStaticPrerender(htmlStream, { inlinedDataStream: createInlinedDataReadableStream( - serverPrerenderStreamResult.asStream(), + reactServerResult.consumeAsStream(), nonce, formState ), - isStaticGeneration: true, getServerInsertedHTML, getServerInsertedMetadata, - validateRootLayout: dev, }), dynamicAccess: consumeDynamicAccess( serverDynamicTracking, clientDynamicTracking ), // TODO: Should this include the SSR pass? - collectedRevalidate: finalServerPrerenderStore.revalidate, - collectedExpire: finalServerPrerenderStore.expire, - collectedStale: finalServerPrerenderStore.stale, - collectedTags: finalServerPrerenderStore.tags, + collectedRevalidate: finalRenderPrerenderStore.revalidate, + collectedExpire: finalRenderPrerenderStore.expire, + collectedStale: finalRenderPrerenderStore.stale, + collectedTags: finalRenderPrerenderStore.tags, } } } else if (experimental.isRoutePPREnabled) { From eb6537842e0e47d5838a2aadeef097bae1531a6e Mon Sep 17 00:00:00 2001 From: Hendrik Liebau Date: Thu, 15 May 2025 19:50:47 +0200 Subject: [PATCH 02/10] Enable `ppr` when `dynamicIO` is enabled ...and forbid combination with `'incremental'` or `false`. --- packages/next/src/server/config-shared.ts | 10 +++-- packages/next/src/server/config.ts | 45 +++++++++++++++++------ 2 files changed, 40 insertions(+), 15 deletions(-) diff --git a/packages/next/src/server/config-shared.ts b/packages/next/src/server/config-shared.ts index c01642255a12..bdba7d7d107c 100644 --- a/packages/next/src/server/config-shared.ts +++ b/packages/next/src/server/config-shared.ts @@ -637,8 +637,10 @@ export interface ExperimentalConfig { serverComponentsHmrCache?: boolean /** - * When enabled will cause IO in App Router to be excluded from prerenders - * unless explicitly cached. + * When enabled, will cause IO in App Router to be excluded from prerenders, + * unless explicitly cached. This also enables the experimental Partial + * Prerendering feature of Next.js, and it enables `react@experimental` being + * used for the `app` directory. */ dynamicIO?: boolean @@ -1200,7 +1202,7 @@ export interface NextConfig extends Record { htmlLimitedBots?: RegExp } -export const defaultConfig: NextConfig = { +export const defaultConfig = { env: {}, webpack: null, eslint: { @@ -1393,7 +1395,7 @@ export const defaultConfig: NextConfig = { }, htmlLimitedBots: undefined, bundlePagesRouterDependencies: false, -} +} satisfies NextConfig export async function normalizeConfig(phase: string, config: any) { if (typeof config === 'function') { diff --git a/packages/next/src/server/config.ts b/packages/next/src/server/config.ts index fd9ae3c18bb3..4e3d6160b934 100644 --- a/packages/next/src/server/config.ts +++ b/packages/next/src/server/config.ts @@ -143,9 +143,9 @@ function warnCustomizedOption( function assignDefaults( dir: string, - userConfig: { [key: string]: any }, + userConfig: { [key: string]: any; configFileName: string }, silent: boolean -) { +): NextConfigComplete { const configFileName = userConfig.configFileName if (typeof userConfig.exportTrailingSlash !== 'undefined') { if (!silent) { @@ -213,9 +213,15 @@ function assignDefaults( }) } - if (!!value && value.constructor === Object) { + const defaultValue = (defaultConfig as Record)[key] + + if ( + !!value && + value.constructor === Object && + typeof defaultValue === 'object' + ) { currentConfig[key] = { - ...defaultConfig[key], + ...defaultValue, ...Object.keys(value).reduce((c, k) => { const v = value[k] if (v !== undefined && v !== null) { @@ -231,7 +237,7 @@ function assignDefaults( return currentConfig }, {} - ) + ) as NextConfig & { configFileName: string } // TODO: remove these once we've made PPR default // If this was defaulted to true, it implies that the configuration was @@ -532,7 +538,7 @@ function assignDefaults( if ( hasWarnedBuildActivityPosition && result.devIndicators !== false && - result.devIndicators?.buildActivityPosition && + 'buildActivityPosition' in result.devIndicators && result.devIndicators.buildActivityPosition !== result.devIndicators.position ) { Log.warnOnce( @@ -963,7 +969,11 @@ function assignDefaults( reason: 'key must only use characters a-z and -', }) } else { - const handlerPath = result.experimental.cacheHandlers[key] + const handlerPath = ( + result.experimental.cacheHandlers as { + [handlerName: string]: string | undefined + } + )[key] if (handlerPath && !existsSync(handlerPath)) { invalidHandlerItems.push({ @@ -1095,7 +1105,21 @@ function assignDefaults( result.experimental.useCache = result.experimental.dynamicIO } - return result + // If dynamicIO is enabled, we also enable PPR. + if (result.experimental.dynamicIO) { + if ( + config.experimental?.ppr === false || + config.experimental?.ppr === 'incremental' + ) { + throw new Error( + `\`experimental.ppr\` can not be \`${JSON.stringify(config.experimental?.ppr)}\` when \`experimental.dynamicIO\` is \`true\`. PPR is implicitly enabled when Dynamic IO is enabled.` + ) + } + + result.experimental.ppr = true + } + + return result as NextConfigComplete } async function applyModifyConfig( @@ -1401,10 +1425,9 @@ export default async function loadConfig( // reactRoot can be updated correctly even with no next.config.js const completeConfig = assignDefaults( dir, - defaultConfig, + { ...defaultConfig, configFileName }, silent ) as NextConfigComplete - completeConfig.configFileName = configFileName setHttpClientAndAgentOptions(completeConfig) return await applyModifyConfig(completeConfig, phase, silent) } @@ -1438,7 +1461,7 @@ export function getConfiguredExperimentalFeatures( if ( name in defaultConfig.experimental && - value !== defaultConfig.experimental[name] + value !== (defaultConfig.experimental as Record)[name] ) { configuredExperimentalFeatures.push( typeof value === 'boolean' From 8779f57bf027d95427978bc8d07a69de11e5f103 Mon Sep 17 00:00:00 2001 From: Hendrik Liebau Date: Thu, 15 May 2025 19:52:33 +0200 Subject: [PATCH 03/10] Remove obsolete `ppr` configs from DIO tests --- .../bundled/next.config.js | 1 - .../external/next.config.js | 1 - .../fixtures/dynamic-boundary/next.config.js | 1 - .../next.config.js | 1 - .../next.config.js | 1 - .../next.config.js | 1 - .../next.config.js | 2 -- .../fixtures/dynamic-root/next.config.js | 1 - .../next.config.js | 1 - .../next.config.js | 1 - .../fixtures/lazy-module-init/next.config.js | 1 - .../prospective-render-errors/next.config.js | 1 - .../fixtures/static/next.config.js | 1 - .../next.config.js | 1 - .../next.config.js | 1 - .../sync-cookies-with-fallback/next.config.js | 1 - .../next.config.js | 1 - .../sync-random-with-fallback/next.config.js | 1 - .../next.config.js | 1 - .../next.config.js | 1 - .../next.config.js | 1 - .../next.config.js | 1 - .../next.config.js | 1 - test/e2e/app-dir/dynamic-io/next.config.js | 1 - .../empty-fallback-shells/next.config.js | 1 - .../fixtures/random/dynamic-io/next.config.js | 1 - .../fixtures/multiple/next.config.js | 1 - .../fixtures/nested/next.config.js | 1 - .../fixtures/single/next.config.js | 1 - .../segment-cache/basic/next.config.js | 1 - .../client-only-opt-in/next.config.js | 1 - .../dynamic-on-hover/next.config.js | 1 - .../segment-cache/export/next.config.js | 1 - .../incremental-opt-in/next.config.js | 1 - .../memory-pressure/next.config.js | 1 - .../mpa-navigations/next.config.js | 1 - .../prefetch-auto/next.config.js | 1 - .../prefetch-scheduling/next.config.js | 1 - .../segment-cache/revalidation/next.config.js | 1 - .../search-params/next.config.js | 1 - .../segment-cache/staleness/next.config.js | 1 - .../use-cache-hanging-inputs/next.config.js | 1 - test/ppr-tests-manifest.json | 31 ++++++++++++------- 43 files changed, 20 insertions(+), 54 deletions(-) diff --git a/test/e2e/app-dir/dynamic-io-dynamic-imports/bundled/next.config.js b/test/e2e/app-dir/dynamic-io-dynamic-imports/bundled/next.config.js index 10cc99d480a1..3dac20d4703c 100644 --- a/test/e2e/app-dir/dynamic-io-dynamic-imports/bundled/next.config.js +++ b/test/e2e/app-dir/dynamic-io-dynamic-imports/bundled/next.config.js @@ -3,7 +3,6 @@ */ const nextConfig = { experimental: { - ppr: process.env.__NEXT_EXPERIMENTAL_PPR === 'true', dynamicIO: true, prerenderEarlyExit: false, }, diff --git a/test/e2e/app-dir/dynamic-io-dynamic-imports/external/next.config.js b/test/e2e/app-dir/dynamic-io-dynamic-imports/external/next.config.js index 026621644ec9..81eccfbacdd8 100644 --- a/test/e2e/app-dir/dynamic-io-dynamic-imports/external/next.config.js +++ b/test/e2e/app-dir/dynamic-io-dynamic-imports/external/next.config.js @@ -3,7 +3,6 @@ */ const nextConfig = { experimental: { - ppr: process.env.__NEXT_EXPERIMENTAL_PPR === 'true', dynamicIO: true, }, serverExternalPackages: ['external-esm-pkg-with-async-import'], diff --git a/test/e2e/app-dir/dynamic-io-errors/fixtures/dynamic-boundary/next.config.js b/test/e2e/app-dir/dynamic-io-errors/fixtures/dynamic-boundary/next.config.js index b23633baadc7..74b8245cfe26 100644 --- a/test/e2e/app-dir/dynamic-io-errors/fixtures/dynamic-boundary/next.config.js +++ b/test/e2e/app-dir/dynamic-io-errors/fixtures/dynamic-boundary/next.config.js @@ -3,7 +3,6 @@ */ const nextConfig = { experimental: { - ppr: process.env.__NEXT_EXPERIMENTAL_PPR === 'true', dynamicIO: true, serverMinification: true, }, diff --git a/test/e2e/app-dir/dynamic-io-errors/fixtures/dynamic-metadata-dynamic-route/next.config.js b/test/e2e/app-dir/dynamic-io-errors/fixtures/dynamic-metadata-dynamic-route/next.config.js index b23633baadc7..74b8245cfe26 100644 --- a/test/e2e/app-dir/dynamic-io-errors/fixtures/dynamic-metadata-dynamic-route/next.config.js +++ b/test/e2e/app-dir/dynamic-io-errors/fixtures/dynamic-metadata-dynamic-route/next.config.js @@ -3,7 +3,6 @@ */ const nextConfig = { experimental: { - ppr: process.env.__NEXT_EXPERIMENTAL_PPR === 'true', dynamicIO: true, serverMinification: true, }, diff --git a/test/e2e/app-dir/dynamic-io-errors/fixtures/dynamic-metadata-error-route/next.config.js b/test/e2e/app-dir/dynamic-io-errors/fixtures/dynamic-metadata-error-route/next.config.js index b23633baadc7..74b8245cfe26 100644 --- a/test/e2e/app-dir/dynamic-io-errors/fixtures/dynamic-metadata-error-route/next.config.js +++ b/test/e2e/app-dir/dynamic-io-errors/fixtures/dynamic-metadata-error-route/next.config.js @@ -3,7 +3,6 @@ */ const nextConfig = { experimental: { - ppr: process.env.__NEXT_EXPERIMENTAL_PPR === 'true', dynamicIO: true, serverMinification: true, }, diff --git a/test/e2e/app-dir/dynamic-io-errors/fixtures/dynamic-metadata-static-route/next.config.js b/test/e2e/app-dir/dynamic-io-errors/fixtures/dynamic-metadata-static-route/next.config.js index b23633baadc7..74b8245cfe26 100644 --- a/test/e2e/app-dir/dynamic-io-errors/fixtures/dynamic-metadata-static-route/next.config.js +++ b/test/e2e/app-dir/dynamic-io-errors/fixtures/dynamic-metadata-static-route/next.config.js @@ -3,7 +3,6 @@ */ const nextConfig = { experimental: { - ppr: process.env.__NEXT_EXPERIMENTAL_PPR === 'true', dynamicIO: true, serverMinification: true, }, diff --git a/test/e2e/app-dir/dynamic-io-errors/fixtures/dynamic-metadata-static-with-suspense/next.config.js b/test/e2e/app-dir/dynamic-io-errors/fixtures/dynamic-metadata-static-with-suspense/next.config.js index a3d79eebc79c..74b8245cfe26 100644 --- a/test/e2e/app-dir/dynamic-io-errors/fixtures/dynamic-metadata-static-with-suspense/next.config.js +++ b/test/e2e/app-dir/dynamic-io-errors/fixtures/dynamic-metadata-static-with-suspense/next.config.js @@ -3,8 +3,6 @@ */ const nextConfig = { experimental: { - ppr: process.env.__NEXT_EXPERIMENTAL_PPR === 'true', - pprFallbacks: process.env.__NEXT_EXPERIMENTAL_PPR === 'true', dynamicIO: true, serverMinification: true, }, diff --git a/test/e2e/app-dir/dynamic-io-errors/fixtures/dynamic-root/next.config.js b/test/e2e/app-dir/dynamic-io-errors/fixtures/dynamic-root/next.config.js index a4a933871151..97365ac042aa 100644 --- a/test/e2e/app-dir/dynamic-io-errors/fixtures/dynamic-root/next.config.js +++ b/test/e2e/app-dir/dynamic-io-errors/fixtures/dynamic-root/next.config.js @@ -3,7 +3,6 @@ */ const nextConfig = { experimental: { - ppr: process.env.__NEXT_EXPERIMENTAL_PPR === 'true', dynamicIO: true, serverMinification: false, }, diff --git a/test/e2e/app-dir/dynamic-io-errors/fixtures/dynamic-viewport-dynamic-route/next.config.js b/test/e2e/app-dir/dynamic-io-errors/fixtures/dynamic-viewport-dynamic-route/next.config.js index b23633baadc7..74b8245cfe26 100644 --- a/test/e2e/app-dir/dynamic-io-errors/fixtures/dynamic-viewport-dynamic-route/next.config.js +++ b/test/e2e/app-dir/dynamic-io-errors/fixtures/dynamic-viewport-dynamic-route/next.config.js @@ -3,7 +3,6 @@ */ const nextConfig = { experimental: { - ppr: process.env.__NEXT_EXPERIMENTAL_PPR === 'true', dynamicIO: true, serverMinification: true, }, diff --git a/test/e2e/app-dir/dynamic-io-errors/fixtures/dynamic-viewport-static-route/next.config.js b/test/e2e/app-dir/dynamic-io-errors/fixtures/dynamic-viewport-static-route/next.config.js index b23633baadc7..74b8245cfe26 100644 --- a/test/e2e/app-dir/dynamic-io-errors/fixtures/dynamic-viewport-static-route/next.config.js +++ b/test/e2e/app-dir/dynamic-io-errors/fixtures/dynamic-viewport-static-route/next.config.js @@ -3,7 +3,6 @@ */ const nextConfig = { experimental: { - ppr: process.env.__NEXT_EXPERIMENTAL_PPR === 'true', dynamicIO: true, serverMinification: true, }, diff --git a/test/e2e/app-dir/dynamic-io-errors/fixtures/lazy-module-init/next.config.js b/test/e2e/app-dir/dynamic-io-errors/fixtures/lazy-module-init/next.config.js index a4a933871151..97365ac042aa 100644 --- a/test/e2e/app-dir/dynamic-io-errors/fixtures/lazy-module-init/next.config.js +++ b/test/e2e/app-dir/dynamic-io-errors/fixtures/lazy-module-init/next.config.js @@ -3,7 +3,6 @@ */ const nextConfig = { experimental: { - ppr: process.env.__NEXT_EXPERIMENTAL_PPR === 'true', dynamicIO: true, serverMinification: false, }, diff --git a/test/e2e/app-dir/dynamic-io-errors/fixtures/prospective-render-errors/next.config.js b/test/e2e/app-dir/dynamic-io-errors/fixtures/prospective-render-errors/next.config.js index a4a933871151..97365ac042aa 100644 --- a/test/e2e/app-dir/dynamic-io-errors/fixtures/prospective-render-errors/next.config.js +++ b/test/e2e/app-dir/dynamic-io-errors/fixtures/prospective-render-errors/next.config.js @@ -3,7 +3,6 @@ */ const nextConfig = { experimental: { - ppr: process.env.__NEXT_EXPERIMENTAL_PPR === 'true', dynamicIO: true, serverMinification: false, }, diff --git a/test/e2e/app-dir/dynamic-io-errors/fixtures/static/next.config.js b/test/e2e/app-dir/dynamic-io-errors/fixtures/static/next.config.js index b23633baadc7..74b8245cfe26 100644 --- a/test/e2e/app-dir/dynamic-io-errors/fixtures/static/next.config.js +++ b/test/e2e/app-dir/dynamic-io-errors/fixtures/static/next.config.js @@ -3,7 +3,6 @@ */ const nextConfig = { experimental: { - ppr: process.env.__NEXT_EXPERIMENTAL_PPR === 'true', dynamicIO: true, serverMinification: true, }, diff --git a/test/e2e/app-dir/dynamic-io-errors/fixtures/sync-client-search-with-fallback/next.config.js b/test/e2e/app-dir/dynamic-io-errors/fixtures/sync-client-search-with-fallback/next.config.js index b23633baadc7..74b8245cfe26 100644 --- a/test/e2e/app-dir/dynamic-io-errors/fixtures/sync-client-search-with-fallback/next.config.js +++ b/test/e2e/app-dir/dynamic-io-errors/fixtures/sync-client-search-with-fallback/next.config.js @@ -3,7 +3,6 @@ */ const nextConfig = { experimental: { - ppr: process.env.__NEXT_EXPERIMENTAL_PPR === 'true', dynamicIO: true, serverMinification: true, }, diff --git a/test/e2e/app-dir/dynamic-io-errors/fixtures/sync-client-search-without-fallback/next.config.js b/test/e2e/app-dir/dynamic-io-errors/fixtures/sync-client-search-without-fallback/next.config.js index b23633baadc7..74b8245cfe26 100644 --- a/test/e2e/app-dir/dynamic-io-errors/fixtures/sync-client-search-without-fallback/next.config.js +++ b/test/e2e/app-dir/dynamic-io-errors/fixtures/sync-client-search-without-fallback/next.config.js @@ -3,7 +3,6 @@ */ const nextConfig = { experimental: { - ppr: process.env.__NEXT_EXPERIMENTAL_PPR === 'true', dynamicIO: true, serverMinification: true, }, diff --git a/test/e2e/app-dir/dynamic-io-errors/fixtures/sync-cookies-with-fallback/next.config.js b/test/e2e/app-dir/dynamic-io-errors/fixtures/sync-cookies-with-fallback/next.config.js index b23633baadc7..74b8245cfe26 100644 --- a/test/e2e/app-dir/dynamic-io-errors/fixtures/sync-cookies-with-fallback/next.config.js +++ b/test/e2e/app-dir/dynamic-io-errors/fixtures/sync-cookies-with-fallback/next.config.js @@ -3,7 +3,6 @@ */ const nextConfig = { experimental: { - ppr: process.env.__NEXT_EXPERIMENTAL_PPR === 'true', dynamicIO: true, serverMinification: true, }, diff --git a/test/e2e/app-dir/dynamic-io-errors/fixtures/sync-cookies-without-fallback/next.config.js b/test/e2e/app-dir/dynamic-io-errors/fixtures/sync-cookies-without-fallback/next.config.js index b23633baadc7..74b8245cfe26 100644 --- a/test/e2e/app-dir/dynamic-io-errors/fixtures/sync-cookies-without-fallback/next.config.js +++ b/test/e2e/app-dir/dynamic-io-errors/fixtures/sync-cookies-without-fallback/next.config.js @@ -3,7 +3,6 @@ */ const nextConfig = { experimental: { - ppr: process.env.__NEXT_EXPERIMENTAL_PPR === 'true', dynamicIO: true, serverMinification: true, }, diff --git a/test/e2e/app-dir/dynamic-io-errors/fixtures/sync-random-with-fallback/next.config.js b/test/e2e/app-dir/dynamic-io-errors/fixtures/sync-random-with-fallback/next.config.js index b23633baadc7..74b8245cfe26 100644 --- a/test/e2e/app-dir/dynamic-io-errors/fixtures/sync-random-with-fallback/next.config.js +++ b/test/e2e/app-dir/dynamic-io-errors/fixtures/sync-random-with-fallback/next.config.js @@ -3,7 +3,6 @@ */ const nextConfig = { experimental: { - ppr: process.env.__NEXT_EXPERIMENTAL_PPR === 'true', dynamicIO: true, serverMinification: true, }, diff --git a/test/e2e/app-dir/dynamic-io-errors/fixtures/sync-random-without-fallback/next.config.js b/test/e2e/app-dir/dynamic-io-errors/fixtures/sync-random-without-fallback/next.config.js index b23633baadc7..74b8245cfe26 100644 --- a/test/e2e/app-dir/dynamic-io-errors/fixtures/sync-random-without-fallback/next.config.js +++ b/test/e2e/app-dir/dynamic-io-errors/fixtures/sync-random-without-fallback/next.config.js @@ -3,7 +3,6 @@ */ const nextConfig = { experimental: { - ppr: process.env.__NEXT_EXPERIMENTAL_PPR === 'true', dynamicIO: true, serverMinification: true, }, diff --git a/test/e2e/app-dir/dynamic-io-errors/fixtures/sync-server-search-with-fallback/next.config.js b/test/e2e/app-dir/dynamic-io-errors/fixtures/sync-server-search-with-fallback/next.config.js index b23633baadc7..74b8245cfe26 100644 --- a/test/e2e/app-dir/dynamic-io-errors/fixtures/sync-server-search-with-fallback/next.config.js +++ b/test/e2e/app-dir/dynamic-io-errors/fixtures/sync-server-search-with-fallback/next.config.js @@ -3,7 +3,6 @@ */ const nextConfig = { experimental: { - ppr: process.env.__NEXT_EXPERIMENTAL_PPR === 'true', dynamicIO: true, serverMinification: true, }, diff --git a/test/e2e/app-dir/dynamic-io-errors/fixtures/sync-server-search-without-fallback/next.config.js b/test/e2e/app-dir/dynamic-io-errors/fixtures/sync-server-search-without-fallback/next.config.js index b23633baadc7..74b8245cfe26 100644 --- a/test/e2e/app-dir/dynamic-io-errors/fixtures/sync-server-search-without-fallback/next.config.js +++ b/test/e2e/app-dir/dynamic-io-errors/fixtures/sync-server-search-without-fallback/next.config.js @@ -3,7 +3,6 @@ */ const nextConfig = { experimental: { - ppr: process.env.__NEXT_EXPERIMENTAL_PPR === 'true', dynamicIO: true, serverMinification: true, }, diff --git a/test/e2e/app-dir/dynamic-io-request-apis/fixtures/reject-hanging-promises-dynamic/next.config.js b/test/e2e/app-dir/dynamic-io-request-apis/fixtures/reject-hanging-promises-dynamic/next.config.js index b23633baadc7..74b8245cfe26 100644 --- a/test/e2e/app-dir/dynamic-io-request-apis/fixtures/reject-hanging-promises-dynamic/next.config.js +++ b/test/e2e/app-dir/dynamic-io-request-apis/fixtures/reject-hanging-promises-dynamic/next.config.js @@ -3,7 +3,6 @@ */ const nextConfig = { experimental: { - ppr: process.env.__NEXT_EXPERIMENTAL_PPR === 'true', dynamicIO: true, serverMinification: true, }, diff --git a/test/e2e/app-dir/dynamic-io-request-apis/fixtures/reject-hanging-promises-static/next.config.js b/test/e2e/app-dir/dynamic-io-request-apis/fixtures/reject-hanging-promises-static/next.config.js index b23633baadc7..74b8245cfe26 100644 --- a/test/e2e/app-dir/dynamic-io-request-apis/fixtures/reject-hanging-promises-static/next.config.js +++ b/test/e2e/app-dir/dynamic-io-request-apis/fixtures/reject-hanging-promises-static/next.config.js @@ -3,7 +3,6 @@ */ const nextConfig = { experimental: { - ppr: process.env.__NEXT_EXPERIMENTAL_PPR === 'true', dynamicIO: true, serverMinification: true, }, diff --git a/test/e2e/app-dir/dynamic-io/next.config.js b/test/e2e/app-dir/dynamic-io/next.config.js index 18a5238c3afd..ac4afcf43219 100644 --- a/test/e2e/app-dir/dynamic-io/next.config.js +++ b/test/e2e/app-dir/dynamic-io/next.config.js @@ -3,7 +3,6 @@ */ const nextConfig = { experimental: { - ppr: process.env.__NEXT_EXPERIMENTAL_PPR === 'true', dynamicIO: true, }, } diff --git a/test/e2e/app-dir/empty-fallback-shells/next.config.js b/test/e2e/app-dir/empty-fallback-shells/next.config.js index ec78f96957b8..3dac20d4703c 100644 --- a/test/e2e/app-dir/empty-fallback-shells/next.config.js +++ b/test/e2e/app-dir/empty-fallback-shells/next.config.js @@ -4,7 +4,6 @@ const nextConfig = { experimental: { dynamicIO: true, - ppr: true, prerenderEarlyExit: false, }, } diff --git a/test/e2e/app-dir/node-extensions/fixtures/random/dynamic-io/next.config.js b/test/e2e/app-dir/node-extensions/fixtures/random/dynamic-io/next.config.js index 18a5238c3afd..ac4afcf43219 100644 --- a/test/e2e/app-dir/node-extensions/fixtures/random/dynamic-io/next.config.js +++ b/test/e2e/app-dir/node-extensions/fixtures/random/dynamic-io/next.config.js @@ -3,7 +3,6 @@ */ const nextConfig = { experimental: { - ppr: process.env.__NEXT_EXPERIMENTAL_PPR === 'true', dynamicIO: true, }, } diff --git a/test/e2e/app-dir/ppr-missing-root-params/fixtures/multiple/next.config.js b/test/e2e/app-dir/ppr-missing-root-params/fixtures/multiple/next.config.js index 16e8384b655b..ac4afcf43219 100644 --- a/test/e2e/app-dir/ppr-missing-root-params/fixtures/multiple/next.config.js +++ b/test/e2e/app-dir/ppr-missing-root-params/fixtures/multiple/next.config.js @@ -3,7 +3,6 @@ */ const nextConfig = { experimental: { - ppr: true, dynamicIO: true, }, } diff --git a/test/e2e/app-dir/ppr-missing-root-params/fixtures/nested/next.config.js b/test/e2e/app-dir/ppr-missing-root-params/fixtures/nested/next.config.js index 16e8384b655b..ac4afcf43219 100644 --- a/test/e2e/app-dir/ppr-missing-root-params/fixtures/nested/next.config.js +++ b/test/e2e/app-dir/ppr-missing-root-params/fixtures/nested/next.config.js @@ -3,7 +3,6 @@ */ const nextConfig = { experimental: { - ppr: true, dynamicIO: true, }, } diff --git a/test/e2e/app-dir/ppr-missing-root-params/fixtures/single/next.config.js b/test/e2e/app-dir/ppr-missing-root-params/fixtures/single/next.config.js index 16e8384b655b..ac4afcf43219 100644 --- a/test/e2e/app-dir/ppr-missing-root-params/fixtures/single/next.config.js +++ b/test/e2e/app-dir/ppr-missing-root-params/fixtures/single/next.config.js @@ -3,7 +3,6 @@ */ const nextConfig = { experimental: { - ppr: true, dynamicIO: true, }, } diff --git a/test/e2e/app-dir/segment-cache/basic/next.config.js b/test/e2e/app-dir/segment-cache/basic/next.config.js index a74129c5a24f..76e3ac83796a 100644 --- a/test/e2e/app-dir/segment-cache/basic/next.config.js +++ b/test/e2e/app-dir/segment-cache/basic/next.config.js @@ -3,7 +3,6 @@ */ const nextConfig = { experimental: { - ppr: true, dynamicIO: true, clientSegmentCache: true, }, diff --git a/test/e2e/app-dir/segment-cache/client-only-opt-in/next.config.js b/test/e2e/app-dir/segment-cache/client-only-opt-in/next.config.js index 64a383e0b691..209191815e9e 100644 --- a/test/e2e/app-dir/segment-cache/client-only-opt-in/next.config.js +++ b/test/e2e/app-dir/segment-cache/client-only-opt-in/next.config.js @@ -4,7 +4,6 @@ const nextConfig = { experimental: { ppr: 'incremental', - dynamicIO: true, clientSegmentCache: 'client-only', }, } diff --git a/test/e2e/app-dir/segment-cache/dynamic-on-hover/next.config.js b/test/e2e/app-dir/segment-cache/dynamic-on-hover/next.config.js index 7383394c57fd..45797a4db7de 100644 --- a/test/e2e/app-dir/segment-cache/dynamic-on-hover/next.config.js +++ b/test/e2e/app-dir/segment-cache/dynamic-on-hover/next.config.js @@ -3,7 +3,6 @@ */ const nextConfig = { experimental: { - ppr: true, dynamicIO: true, clientSegmentCache: true, dynamicOnHover: true, diff --git a/test/e2e/app-dir/segment-cache/export/next.config.js b/test/e2e/app-dir/segment-cache/export/next.config.js index e740e0c5233b..0a7ffa13a5f3 100644 --- a/test/e2e/app-dir/segment-cache/export/next.config.js +++ b/test/e2e/app-dir/segment-cache/export/next.config.js @@ -4,7 +4,6 @@ const nextConfig = { output: 'export', experimental: { - ppr: false, dynamicIO: true, clientSegmentCache: true, }, diff --git a/test/e2e/app-dir/segment-cache/incremental-opt-in/next.config.js b/test/e2e/app-dir/segment-cache/incremental-opt-in/next.config.js index ee74ac5cb97b..1fb210a73ee9 100644 --- a/test/e2e/app-dir/segment-cache/incremental-opt-in/next.config.js +++ b/test/e2e/app-dir/segment-cache/incremental-opt-in/next.config.js @@ -4,7 +4,6 @@ const nextConfig = { experimental: { ppr: 'incremental', - dynamicIO: true, clientSegmentCache: true, }, } diff --git a/test/e2e/app-dir/segment-cache/memory-pressure/next.config.js b/test/e2e/app-dir/segment-cache/memory-pressure/next.config.js index a74129c5a24f..76e3ac83796a 100644 --- a/test/e2e/app-dir/segment-cache/memory-pressure/next.config.js +++ b/test/e2e/app-dir/segment-cache/memory-pressure/next.config.js @@ -3,7 +3,6 @@ */ const nextConfig = { experimental: { - ppr: true, dynamicIO: true, clientSegmentCache: true, }, diff --git a/test/e2e/app-dir/segment-cache/mpa-navigations/next.config.js b/test/e2e/app-dir/segment-cache/mpa-navigations/next.config.js index a74129c5a24f..76e3ac83796a 100644 --- a/test/e2e/app-dir/segment-cache/mpa-navigations/next.config.js +++ b/test/e2e/app-dir/segment-cache/mpa-navigations/next.config.js @@ -3,7 +3,6 @@ */ const nextConfig = { experimental: { - ppr: true, dynamicIO: true, clientSegmentCache: true, }, diff --git a/test/e2e/app-dir/segment-cache/prefetch-auto/next.config.js b/test/e2e/app-dir/segment-cache/prefetch-auto/next.config.js index 16e8384b655b..ac4afcf43219 100644 --- a/test/e2e/app-dir/segment-cache/prefetch-auto/next.config.js +++ b/test/e2e/app-dir/segment-cache/prefetch-auto/next.config.js @@ -3,7 +3,6 @@ */ const nextConfig = { experimental: { - ppr: true, dynamicIO: true, }, } diff --git a/test/e2e/app-dir/segment-cache/prefetch-scheduling/next.config.js b/test/e2e/app-dir/segment-cache/prefetch-scheduling/next.config.js index a74129c5a24f..76e3ac83796a 100644 --- a/test/e2e/app-dir/segment-cache/prefetch-scheduling/next.config.js +++ b/test/e2e/app-dir/segment-cache/prefetch-scheduling/next.config.js @@ -3,7 +3,6 @@ */ const nextConfig = { experimental: { - ppr: true, dynamicIO: true, clientSegmentCache: true, }, diff --git a/test/e2e/app-dir/segment-cache/revalidation/next.config.js b/test/e2e/app-dir/segment-cache/revalidation/next.config.js index a74129c5a24f..76e3ac83796a 100644 --- a/test/e2e/app-dir/segment-cache/revalidation/next.config.js +++ b/test/e2e/app-dir/segment-cache/revalidation/next.config.js @@ -3,7 +3,6 @@ */ const nextConfig = { experimental: { - ppr: true, dynamicIO: true, clientSegmentCache: true, }, diff --git a/test/e2e/app-dir/segment-cache/search-params/next.config.js b/test/e2e/app-dir/segment-cache/search-params/next.config.js index a74129c5a24f..76e3ac83796a 100644 --- a/test/e2e/app-dir/segment-cache/search-params/next.config.js +++ b/test/e2e/app-dir/segment-cache/search-params/next.config.js @@ -3,7 +3,6 @@ */ const nextConfig = { experimental: { - ppr: true, dynamicIO: true, clientSegmentCache: true, }, diff --git a/test/e2e/app-dir/segment-cache/staleness/next.config.js b/test/e2e/app-dir/segment-cache/staleness/next.config.js index 6d88eaf9a30c..2defaa678730 100644 --- a/test/e2e/app-dir/segment-cache/staleness/next.config.js +++ b/test/e2e/app-dir/segment-cache/staleness/next.config.js @@ -3,7 +3,6 @@ */ const nextConfig = { experimental: { - ppr: true, dynamicIO: true, clientSegmentCache: true, staleTimes: { diff --git a/test/e2e/app-dir/use-cache-hanging-inputs/next.config.js b/test/e2e/app-dir/use-cache-hanging-inputs/next.config.js index df2126c94163..3dac20d4703c 100644 --- a/test/e2e/app-dir/use-cache-hanging-inputs/next.config.js +++ b/test/e2e/app-dir/use-cache-hanging-inputs/next.config.js @@ -5,7 +5,6 @@ const nextConfig = { experimental: { dynamicIO: true, prerenderEarlyExit: false, - ppr: process.env.__NEXT_EXPERIMENTAL_PPR === 'true', }, } diff --git a/test/ppr-tests-manifest.json b/test/ppr-tests-manifest.json index f0cf5b8e6b13..97b403a7e105 100644 --- a/test/ppr-tests-manifest.json +++ b/test/ppr-tests-manifest.json @@ -83,26 +83,35 @@ "test/development/acceptance-app/**/*.test.{t,j}s{,x}" ], "exclude": [ - "test/integration/app-dir-export/**/*", - "test/e2e/app-dir/next-font/**/*", - "test/e2e/app-dir/ppr/**/*", - "test/e2e/app-dir/ppr-*/**/*", - "test/e2e/app-dir/app-prefetch*/**/*", - "test/e2e/app-dir/searchparams-static-bailout/searchparams-static-bailout.test.ts", + "test/development/app-dir/dynamic-io-*/**/*", + "test/e2e/app-dir/app-client-cache/client-cache.defaults.test.ts", "test/e2e/app-dir/app-client-cache/client-cache.experimental.test.ts", "test/e2e/app-dir/app-client-cache/client-cache.original.test.ts", - "test/e2e/app-dir/app-client-cache/client-cache.defaults.test.ts", - "test/e2e/app-dir/react-max-headers-length/react-max-headers-length.test.ts", + "test/e2e/app-dir/app-prefetch*/**/*", + "test/e2e/app-dir/dynamic-io-*/**/*", + "test/e2e/app-dir/dynamic-io/**/*", + "test/e2e/app-dir/empty-fallback-shells/**/*", "test/e2e/app-dir/front-redirect-issue/front-redirect-issue.test.ts", - "test/e2e/app-dir/static-shell-debugging/static-shell-debugging.test.ts", - "test/e2e/app-dir/dynamic-io-errors/dynamic-io-errors.prospective-fallback.test.ts", + "test/e2e/app-dir/next-font/**/*", + "test/e2e/app-dir/ppr-*/**/*", + "test/e2e/app-dir/ppr/**/*", + "test/e2e/app-dir/react-max-headers-length/react-max-headers-length.test.ts", + "test/e2e/app-dir/searchparams-static-bailout/searchparams-static-bailout.test.ts", "test/e2e/app-dir/segment-cache/basic/segment-cache-basic.test.ts", "test/e2e/app-dir/segment-cache/incremental-opt-in/segment-cache-incremental-opt-in.test.ts", "test/e2e/app-dir/segment-cache/memory-pressure/segment-cache-memory-pressure.test.ts", "test/e2e/app-dir/segment-cache/prefetch-scheduling/prefetch-scheduling.test.ts", "test/e2e/app-dir/segment-cache/revalidation/segment-cache-revalidation.test.ts", "test/e2e/app-dir/segment-cache/staleness/segment-cache-stale-time.test.ts", - "test/production/app-dir/build-output-tree-view/build-output-tree-view.test.ts" + "test/e2e/app-dir/static-shell-debugging/static-shell-debugging.test.ts", + "test/e2e/app-dir/use-cache-close-over-function/**/*", + "test/e2e/app-dir/use-cache-custom-handler/**/*", + "test/e2e/app-dir/use-cache-hanging-inputs/**/*", + "test/e2e/app-dir/use-cache-metadata-route-handler/**/*", + "test/e2e/app-dir/use-cache-route-handler-only/**/*", + "test/integration/app-dir-export/**/*", + "test/production/app-dir/build-output-tree-view/build-output-tree-view.test.ts", + "test/production/app-dir/dynamic-io-cache-handlers/**/*" ] } } From 8bfe25aefb2fc50a1dd573590ca990847b642b0f Mon Sep 17 00:00:00 2001 From: Hendrik Liebau Date: Thu, 15 May 2025 19:52:50 +0200 Subject: [PATCH 04/10] Remove non-PPR branches from DIO tests --- .../dynamic-io-dynamic-imports.test.ts | 10 +- ...dynamic-io-errors.platform-dynamic.test.ts | 14 +- .../dynamic-io-errors.sync-dynamic.test.ts | 38 +- .../dynamic-io-errors.test.ts | 71 +- .../dynamic-io-request-apis.test.ts | 18 +- .../dynamic-io/dynamic-io.connection.test.ts | 67 +- .../dynamic-io/dynamic-io.cookies.test.ts | 104 +- .../dynamic-io/dynamic-io.date.test.ts | 21 +- .../dynamic-io/dynamic-io.headers.test.ts | 104 +- .../dynamic-io/dynamic-io.node-crypto.test.ts | 56 +- .../dynamic-io/dynamic-io.params.test.ts | 2729 +++++------------ .../dynamic-io/dynamic-io.random.test.ts | 7 +- .../dynamic-io/dynamic-io.search.test.ts | 588 ++-- .../dynamic-io.server-action.test.ts | 15 +- .../e2e/app-dir/dynamic-io/dynamic-io.test.ts | 454 +-- .../dynamic-io/dynamic-io.web-crypto.test.ts | 14 +- 16 files changed, 1328 insertions(+), 2982 deletions(-) diff --git a/test/e2e/app-dir/dynamic-io-dynamic-imports/dynamic-io-dynamic-imports.test.ts b/test/e2e/app-dir/dynamic-io-dynamic-imports/dynamic-io-dynamic-imports.test.ts index 23ab8a844228..fbb0dbd3c704 100644 --- a/test/e2e/app-dir/dynamic-io-dynamic-imports/dynamic-io-dynamic-imports.test.ts +++ b/test/e2e/app-dir/dynamic-io-dynamic-imports/dynamic-io-dynamic-imports.test.ts @@ -17,11 +17,10 @@ describe('async imports in dynamicIO', () => { await next.readFile('.next/prerender-manifest.json') ) - let prerenderedRoutes = Object.keys(prerenderManifest.routes).sort() - - if (process.env.__NEXT_EXPERIMENTAL_PPR === 'true') { - // For the purpose of this test we don't consider an incomplete shell. - prerenderedRoutes = prerenderedRoutes.filter((route) => { + // For the purpose of this test we don't consider an incomplete shell. + const prerenderedRoutes = Object.keys(prerenderManifest.routes) + .sort() + .filter((route) => { const filename = route.replace(/^\//, '').replace(/^$/, 'index') try { return next @@ -37,7 +36,6 @@ describe('async imports in dynamicIO', () => { } } }) - } expect(prerenderedRoutes).toMatchInlineSnapshot(` [ diff --git a/test/e2e/app-dir/dynamic-io-errors/dynamic-io-errors.platform-dynamic.test.ts b/test/e2e/app-dir/dynamic-io-errors/dynamic-io-errors.platform-dynamic.test.ts index 268c63cfc768..f2cfe9f952ee 100644 --- a/test/e2e/app-dir/dynamic-io-errors/dynamic-io-errors.platform-dynamic.test.ts +++ b/test/e2e/app-dir/dynamic-io-errors/dynamic-io-errors.platform-dynamic.test.ts @@ -1,7 +1,5 @@ import { nextTestSetup } from 'e2e-utils' -const WITH_PPR = !!process.env.__NEXT_EXPERIMENTAL_PPR - const stackStart = /\s+at / function createExpectError(cliOutput: string) { @@ -75,15 +73,9 @@ function runTests(options: { withMinification: boolean }) { throw new Error('expected build not to fail for fully static project') } - if (WITH_PPR) { - expect(next.cliOutput).toContain('◐ / ') - const $ = await next.render$('/') - expect($('[data-fallback]').length).toBe(2) - } else { - expect(next.cliOutput).toContain('ƒ / ') - const $ = await next.render$('/') - expect($('[data-fallback]').length).toBe(0) - } + expect(next.cliOutput).toContain('◐ / ') + const $ = await next.render$('/') + expect($('[data-fallback]').length).toBe(2) }) }) diff --git a/test/e2e/app-dir/dynamic-io-errors/dynamic-io-errors.sync-dynamic.test.ts b/test/e2e/app-dir/dynamic-io-errors/dynamic-io-errors.sync-dynamic.test.ts index 54d46b635fc0..3b32bfb96d07 100644 --- a/test/e2e/app-dir/dynamic-io-errors/dynamic-io-errors.sync-dynamic.test.ts +++ b/test/e2e/app-dir/dynamic-io-errors/dynamic-io-errors.sync-dynamic.test.ts @@ -1,7 +1,5 @@ import { nextTestSetup } from 'e2e-utils' -const WITH_PPR = !!process.env.__NEXT_EXPERIMENTAL_PPR - const stackStart = /\s+at / function createExpectError(cliOutput: string) { @@ -75,15 +73,9 @@ function runTests(options: { withMinification: boolean }) { throw new Error('expected build not to fail for fully static project') } - if (WITH_PPR) { - expect(next.cliOutput).toContain('◐ / ') - const $ = await next.render$('/') - expect($('[data-fallback]').length).toBe(2) - } else { - expect(next.cliOutput).toContain('ƒ / ') - const $ = await next.render$('/') - expect($('[data-fallback]').length).toBe(2) - } + expect(next.cliOutput).toContain('◐ / ') + const $ = await next.render$('/') + expect($('[data-fallback]').length).toBe(2) }) }) @@ -162,15 +154,9 @@ function runTests(options: { withMinification: boolean }) { throw new Error('expected build not to fail for fully static project') } - if (WITH_PPR) { - expect(next.cliOutput).toContain('◐ / ') - const $ = await next.render$('/') - expect($('[data-fallback]').length).toBe(2) - } else { - expect(next.cliOutput).toContain('ƒ / ') - const $ = await next.render$('/') - expect($('[data-fallback]').length).toBe(0) - } + expect(next.cliOutput).toContain('◐ / ') + const $ = await next.render$('/') + expect($('[data-fallback]').length).toBe(2) }) }) @@ -249,15 +235,9 @@ function runTests(options: { withMinification: boolean }) { throw new Error('expected build not to fail for fully static project') } - if (WITH_PPR) { - expect(next.cliOutput).toContain('◐ / ') - const $ = await next.render$('/') - expect($('[data-fallback]').length).toBe(2) - } else { - expect(next.cliOutput).toContain('ƒ / ') - const $ = await next.render$('/') - expect($('[data-fallback]').length).toBe(0) - } + expect(next.cliOutput).toContain('◐ / ') + const $ = await next.render$('/') + expect($('[data-fallback]').length).toBe(2) }) }) diff --git a/test/e2e/app-dir/dynamic-io-errors/dynamic-io-errors.test.ts b/test/e2e/app-dir/dynamic-io-errors/dynamic-io-errors.test.ts index 337fd4bdea38..c904b560dd77 100644 --- a/test/e2e/app-dir/dynamic-io-errors/dynamic-io-errors.test.ts +++ b/test/e2e/app-dir/dynamic-io-errors/dynamic-io-errors.test.ts @@ -1,7 +1,5 @@ import { nextTestSetup } from 'e2e-utils' -const WITH_PPR = !!process.env.__NEXT_EXPERIMENTAL_PPR - const stackStart = /\s+at / function createExpectError(cliOutput: string) { @@ -205,16 +203,10 @@ function runTests(options: { withMinification: boolean }) { throw new Error('expected build not to fail for fully static project') } - if (WITH_PPR) { - expect(next.cliOutput).toContain('◐ / ') - const $ = await next.render$('/') - expect($('#dynamic').text()).toBe('Dynamic') - expect($('[data-fallback]').length).toBe(1) - } else { - expect(next.cliOutput).toContain('ƒ / ') - const $ = await next.render$('/') - expect($('#dynamic').text()).toBe('Dynamic') - } + expect(next.cliOutput).toContain('◐ / ') + const $ = await next.render$('/') + expect($('#dynamic').text()).toBe('Dynamic') + expect($('[data-fallback]').length).toBe(1) }) }) @@ -379,16 +371,11 @@ function runTests(options: { withMinification: boolean }) { // Turbopack doesn't support disabling minification yet withMinification || isTurbopack ? undefined : 'IndirectionTwo' ) - if (WITH_PPR) { - // React currently fatals the render in canary because we don't have access to the prerender API there. with a fatal only - // one task actually reports and error at the moment. We should fix upstream but for now we exclude the second error when PPR is off - // because we are using canary React and renderToReadableStream rather than experimental React and prerender - expectError( - 'Route "/": A component accessed data, headers, params, searchParams, or a short-lived cache without a Suspense boundary nor a "use cache" above it.', - // Turbopack doesn't support disabling minification yet - withMinification || isTurbopack ? undefined : 'IndirectionThree' - ) - } + expectError( + 'Route "/": A component accessed data, headers, params, searchParams, or a short-lived cache without a Suspense boundary nor a "use cache" above it.', + // Turbopack doesn't support disabling minification yet + withMinification || isTurbopack ? undefined : 'IndirectionThree' + ) expectError('Error occurred prerendering page "/"') expectError('exiting the build.') }) @@ -421,36 +408,18 @@ function runTests(options: { withMinification: boolean }) { } }) - if (WITH_PPR) { - it('should partially prerender when all dynamic components are inside a Suspense boundary', async () => { - try { - await next.start() - } catch { - throw new Error( - 'expected build not to fail for fully static project' - ) - // we expect the build to fail - } - - expect(next.cliOutput).toContain('◐ / ') - const $ = await next.render$('/') - expect($('[data-fallback]').length).toBe(2) - }) - } else { - it('should not error the build when all dynamic components are inside a Suspense boundary', async () => { - try { - await next.start() - } catch { - throw new Error( - 'expected build not to fail for fully static project' - ) - } + it('should partially prerender when all dynamic components are inside a Suspense boundary', async () => { + try { + await next.start() + } catch { + throw new Error('expected build not to fail for fully static project') + // we expect the build to fail + } - expect(next.cliOutput).toContain('ƒ / ') - const $ = await next.render$('/') - expect($('[data-fallback]').length).toBe(2) - }) - } + expect(next.cliOutput).toContain('◐ / ') + const $ = await next.render$('/') + expect($('[data-fallback]').length).toBe(2) + }) }) }) } diff --git a/test/e2e/app-dir/dynamic-io-request-apis/dynamic-io-request-apis.test.ts b/test/e2e/app-dir/dynamic-io-request-apis/dynamic-io-request-apis.test.ts index 07efcdd2484e..b7b126558c31 100644 --- a/test/e2e/app-dir/dynamic-io-request-apis/dynamic-io-request-apis.test.ts +++ b/test/e2e/app-dir/dynamic-io-request-apis/dynamic-io-request-apis.test.ts @@ -1,7 +1,5 @@ import { nextTestSetup } from 'e2e-utils' -const WITH_PPR = !!process.env.__NEXT_EXPERIMENTAL_PPR - const stackStart = /\s+at / function createExpectError(cliOutput: string) { @@ -62,11 +60,9 @@ describe(`Request Promises`, () => { } const expectError = createExpectError(next.cliOutput) - if (WITH_PPR) { - expectError( - 'Error: During prerendering, `params` rejects when the prerender is complete' - ) - } + expectError( + 'Error: During prerendering, `params` rejects when the prerender is complete' + ) expectError( 'Error: During prerendering, `searchParams` rejects when the prerender is complete' ) @@ -104,11 +100,9 @@ describe(`Request Promises`, () => { } const expectError = createExpectError(next.cliOutput) - if (WITH_PPR) { - expectError( - 'Error: During prerendering, `params` rejects when the prerender is complete' - ) - } + expectError( + 'Error: During prerendering, `params` rejects when the prerender is complete' + ) expectError( 'Error: During prerendering, `searchParams` rejects when the prerender is complete' ) diff --git a/test/e2e/app-dir/dynamic-io/dynamic-io.connection.test.ts b/test/e2e/app-dir/dynamic-io/dynamic-io.connection.test.ts index 94570682a7e1..20270c4c361b 100644 --- a/test/e2e/app-dir/dynamic-io/dynamic-io.connection.test.ts +++ b/test/e2e/app-dir/dynamic-io/dynamic-io.connection.test.ts @@ -1,7 +1,5 @@ import { nextTestSetup } from 'e2e-utils' -const WITH_PPR = !!process.env.__NEXT_EXPERIMENTAL_PPR - describe('dynamic-io', () => { const { next, isNextDev, skipped } = nextTestSetup({ files: __dirname, @@ -12,46 +10,29 @@ describe('dynamic-io', () => { return } - if (WITH_PPR) { - it('should partially prerender pages that use connection', async () => { - let $ = await next.render$('/connection/static-behavior/boundary', {}) - if (isNextDev) { - expect($('#layout').text()).toBe('at runtime') - expect($('#page').text()).toBe('at runtime') - expect($('#foo').text()).toBe('foo') - } else { - expect($('#layout').text()).toBe('at buildtime') - expect($('#page').text()).toBe('at buildtime') - expect($('#foo').text()).toBe('foo') - } - }) - } else { - it('should produce dynamic pages when using connection', async () => { - let $ = await next.render$('/connection/static-behavior/boundary', {}) - if (isNextDev) { - expect($('#layout').text()).toBe('at runtime') - expect($('#page').text()).toBe('at runtime') - expect($('#foo').text()).toBe('foo') - } else { - expect($('#layout').text()).toBe('at runtime') - expect($('#page').text()).toBe('at runtime') - expect($('#foo').text()).toBe('foo') - } - }) - } + it('should partially prerender pages that use connection', async () => { + let $ = await next.render$('/connection/static-behavior/boundary', {}) + if (isNextDev) { + expect($('#layout').text()).toBe('at runtime') + expect($('#page').text()).toBe('at runtime') + expect($('#foo').text()).toBe('foo') + } else { + expect($('#layout').text()).toBe('at buildtime') + expect($('#page').text()).toBe('at buildtime') + expect($('#foo').text()).toBe('foo') + } + }) - if (WITH_PPR) { - it('should be able to pass connection as a promise to another component and trigger an intermediate Suspense boundary', async () => { - const $ = await next.render$('/connection/static-behavior/pass-deeply') - if (isNextDev) { - expect($('#layout').text()).toBe('at runtime') - expect($('#fallback').length).toBe(0) - expect($('#page').text()).toBe('at runtime') - } else { - expect($('#layout').text()).toBe('at buildtime') - expect($('#fallback').text()).toBe('at buildtime') - expect($('#page').text()).toBe('at runtime') - } - }) - } + it('should be able to pass connection as a promise to another component and trigger an intermediate Suspense boundary', async () => { + const $ = await next.render$('/connection/static-behavior/pass-deeply') + if (isNextDev) { + expect($('#layout').text()).toBe('at runtime') + expect($('#fallback').length).toBe(0) + expect($('#page').text()).toBe('at runtime') + } else { + expect($('#layout').text()).toBe('at buildtime') + expect($('#fallback').text()).toBe('at buildtime') + expect($('#page').text()).toBe('at runtime') + } + }) }) diff --git a/test/e2e/app-dir/dynamic-io/dynamic-io.cookies.test.ts b/test/e2e/app-dir/dynamic-io/dynamic-io.cookies.test.ts index 6113f3863142..dde36ca12092 100644 --- a/test/e2e/app-dir/dynamic-io/dynamic-io.cookies.test.ts +++ b/test/e2e/app-dir/dynamic-io/dynamic-io.cookies.test.ts @@ -1,7 +1,5 @@ import { nextTestSetup } from 'e2e-utils' -const WITH_PPR = !!process.env.__NEXT_EXPERIMENTAL_PPR - describe('dynamic-io', () => { const { next, isNextDev, skipped } = nextTestSetup({ files: __dirname, @@ -12,72 +10,44 @@ describe('dynamic-io', () => { return } - if (WITH_PPR) { - it('should partially prerender pages that use async cookies', async () => { - let $ = await next.render$('/cookies/static-behavior/async', {}) - if (isNextDev) { - expect($('#layout').text()).toBe('at runtime') - expect($('#page').text()).toBe('at runtime') - expect($('#x-sentinel').text()).toBe('hello') - } else { - expect($('#layout').text()).toBe('at buildtime') - expect($('#page').text()).toBe('at buildtime') - expect($('#x-sentinel').text()).toBe('hello') - } - }) - - it('should partially prerender pages that use sync cookies', async () => { - let $ = await next.render$('/cookies/static-behavior/sync', {}) - if (isNextDev) { - expect($('#layout').text()).toBe('at runtime') - expect($('#page').text()).toBe('at runtime') - expect($('#x-sentinel').text()).toBe('hello') - } else { - expect($('#layout').text()).toBe('at buildtime') - expect($('#page').text()).toBe('at buildtime') - expect($('#x-sentinel').text()).toBe('hello') - } - }) - } else { - it('should produce dynamic pages when using async or sync cookies', async () => { - let $ = await next.render$('/cookies/static-behavior/sync', {}) - if (isNextDev) { - expect($('#layout').text()).toBe('at runtime') - expect($('#page').text()).toBe('at runtime') - expect($('#x-sentinel').text()).toBe('hello') - } else { - expect($('#layout').text()).toBe('at runtime') - expect($('#page').text()).toBe('at runtime') - expect($('#x-sentinel').text()).toBe('hello') - } - - $ = await next.render$('/cookies/static-behavior/async', {}) - if (isNextDev) { - expect($('#layout').text()).toBe('at runtime') - expect($('#page').text()).toBe('at runtime') - expect($('#x-sentinel').text()).toBe('hello') - } else { - expect($('#layout').text()).toBe('at runtime') - expect($('#page').text()).toBe('at runtime') - expect($('#x-sentinel').text()).toBe('hello') - } - }) - } + it('should partially prerender pages that use async cookies', async () => { + let $ = await next.render$('/cookies/static-behavior/async', {}) + if (isNextDev) { + expect($('#layout').text()).toBe('at runtime') + expect($('#page').text()).toBe('at runtime') + expect($('#x-sentinel').text()).toBe('hello') + } else { + expect($('#layout').text()).toBe('at buildtime') + expect($('#page').text()).toBe('at buildtime') + expect($('#x-sentinel').text()).toBe('hello') + } + }) - if (WITH_PPR) { - it('should be able to pass cookies as a promise to another component and trigger an intermediate Suspense boundary', async () => { - const $ = await next.render$('/cookies/static-behavior/pass-deeply') - if (isNextDev) { - expect($('#layout').text()).toBe('at runtime') - expect($('#fallback').text()).toBe('at runtime') - expect($('#page').text()).toBe('at runtime') - } else { - expect($('#layout').text()).toBe('at buildtime') - expect($('#fallback').text()).toBe('at buildtime') - expect($('#page').text()).toBe('at runtime') - } - }) - } + it('should partially prerender pages that use sync cookies', async () => { + let $ = await next.render$('/cookies/static-behavior/sync', {}) + if (isNextDev) { + expect($('#layout').text()).toBe('at runtime') + expect($('#page').text()).toBe('at runtime') + expect($('#x-sentinel').text()).toBe('hello') + } else { + expect($('#layout').text()).toBe('at buildtime') + expect($('#page').text()).toBe('at buildtime') + expect($('#x-sentinel').text()).toBe('hello') + } + }) + + it('should be able to pass cookies as a promise to another component and trigger an intermediate Suspense boundary', async () => { + const $ = await next.render$('/cookies/static-behavior/pass-deeply') + if (isNextDev) { + expect($('#layout').text()).toBe('at runtime') + expect($('#fallback').text()).toBe('at runtime') + expect($('#page').text()).toBe('at runtime') + } else { + expect($('#layout').text()).toBe('at buildtime') + expect($('#fallback').text()).toBe('at buildtime') + expect($('#page').text()).toBe('at runtime') + } + }) it('should be able to access cookie properties asynchronously', async () => { let $ = await next.render$('/cookies/exercise/async', {}) diff --git a/test/e2e/app-dir/dynamic-io/dynamic-io.date.test.ts b/test/e2e/app-dir/dynamic-io/dynamic-io.date.test.ts index f2b4a48033a1..80df46dfe8af 100644 --- a/test/e2e/app-dir/dynamic-io/dynamic-io.date.test.ts +++ b/test/e2e/app-dir/dynamic-io/dynamic-io.date.test.ts @@ -1,6 +1,5 @@ import { nextTestSetup } from 'e2e-utils' - -const WITH_PPR = !!process.env.__NEXT_EXPERIMENTAL_PPR +import expect from 'expect' describe('dynamic-io', () => { const { next, isNextDev, skipped } = nextTestSetup({ @@ -36,12 +35,8 @@ describe('dynamic-io', () => { expect($('#layout').text()).toBe('at runtime') expect($('#page').text()).toBe('at runtime') expect($('#value').text()).toMatch(/^\d+$/) - } else if (WITH_PPR) { - expect($('#layout').text()).toBe('at buildtime') - expect($('#page').text()).toBe('at runtime') - expect($('#value').text()).toMatch(/^\d+$/) } else { - expect($('#layout').text()).toBe('at runtime') + expect($('#layout').text()).toBe('at buildtime') expect($('#page').text()).toBe('at runtime') expect($('#value').text()).toMatch(/^\d+$/) } @@ -66,12 +61,8 @@ describe('dynamic-io', () => { expect($('#layout').text()).toBe('at runtime') expect($('#page').text()).toBe('at runtime') expect($('#value').text()).toContain('GMT') - } else if (WITH_PPR) { - expect($('#layout').text()).toBe('at buildtime') - expect($('#page').text()).toBe('at runtime') - expect($('#value').text()).toContain('GMT') } else { - expect($('#layout').text()).toBe('at runtime') + expect($('#layout').text()).toBe('at buildtime') expect($('#page').text()).toBe('at runtime') expect($('#value').text()).toContain('GMT') } @@ -96,12 +87,8 @@ describe('dynamic-io', () => { expect($('#layout').text()).toBe('at runtime') expect($('#page').text()).toBe('at runtime') expect($('#value').text()).toContain('GMT') - } else if (WITH_PPR) { - expect($('#layout').text()).toBe('at buildtime') - expect($('#page').text()).toBe('at runtime') - expect($('#value').text()).toContain('GMT') } else { - expect($('#layout').text()).toBe('at runtime') + expect($('#layout').text()).toBe('at buildtime') expect($('#page').text()).toBe('at runtime') expect($('#value').text()).toContain('GMT') } diff --git a/test/e2e/app-dir/dynamic-io/dynamic-io.headers.test.ts b/test/e2e/app-dir/dynamic-io/dynamic-io.headers.test.ts index 98966384ed9d..5ea305fbe0e8 100644 --- a/test/e2e/app-dir/dynamic-io/dynamic-io.headers.test.ts +++ b/test/e2e/app-dir/dynamic-io/dynamic-io.headers.test.ts @@ -1,7 +1,5 @@ import { nextTestSetup } from 'e2e-utils' -const WITH_PPR = !!process.env.__NEXT_EXPERIMENTAL_PPR - describe('dynamic-io', () => { const { next, isNextDev, skipped } = nextTestSetup({ files: __dirname, @@ -12,72 +10,44 @@ describe('dynamic-io', () => { return } - if (WITH_PPR) { - it('should partially prerender pages that use async headers', async () => { - let $ = await next.render$('/headers/static-behavior/async', {}) - if (isNextDev) { - expect($('#layout').text()).toBe('at runtime') - expect($('#page').text()).toBe('at runtime') - expect($('#x-sentinel').text()).toBe('hello') - } else { - expect($('#layout').text()).toBe('at buildtime') - expect($('#page').text()).toBe('at buildtime') - expect($('#x-sentinel').text()).toBe('hello') - } - }) - - it('should partially prerender pages that use sync headers', async () => { - let $ = await next.render$('/headers/static-behavior/sync', {}) - if (isNextDev) { - expect($('#layout').text()).toBe('at runtime') - expect($('#page').text()).toBe('at runtime') - expect($('#x-sentinel').text()).toBe('hello') - } else { - expect($('#layout').text()).toBe('at buildtime') - expect($('#page').text()).toBe('at buildtime') - expect($('#x-sentinel').text()).toBe('hello') - } - }) - } else { - it('should produce dynamic pages when using async or sync headers', async () => { - let $ = await next.render$('/headers/static-behavior/sync', {}) - if (isNextDev) { - expect($('#layout').text()).toBe('at runtime') - expect($('#page').text()).toBe('at runtime') - expect($('#x-sentinel').text()).toBe('hello') - } else { - expect($('#layout').text()).toBe('at runtime') - expect($('#page').text()).toBe('at runtime') - expect($('#x-sentinel').text()).toBe('hello') - } - - $ = await next.render$('/headers/static-behavior/async', {}) - if (isNextDev) { - expect($('#layout').text()).toBe('at runtime') - expect($('#page').text()).toBe('at runtime') - expect($('#x-sentinel').text()).toBe('hello') - } else { - expect($('#layout').text()).toBe('at runtime') - expect($('#page').text()).toBe('at runtime') - expect($('#x-sentinel').text()).toBe('hello') - } - }) - } + it('should partially prerender pages that use async headers', async () => { + let $ = await next.render$('/headers/static-behavior/async', {}) + if (isNextDev) { + expect($('#layout').text()).toBe('at runtime') + expect($('#page').text()).toBe('at runtime') + expect($('#x-sentinel').text()).toBe('hello') + } else { + expect($('#layout').text()).toBe('at buildtime') + expect($('#page').text()).toBe('at buildtime') + expect($('#x-sentinel').text()).toBe('hello') + } + }) - if (WITH_PPR) { - it('should be able to pass headers as a promise to another component and trigger an intermediate Suspense boundary', async () => { - const $ = await next.render$('/headers/static-behavior/pass-deeply') - if (isNextDev) { - expect($('#layout').text()).toBe('at runtime') - expect($('#fallback').text()).toBe('at runtime') - expect($('#page').text()).toBe('at runtime') - } else { - expect($('#layout').text()).toBe('at buildtime') - expect($('#fallback').text()).toBe('at buildtime') - expect($('#page').text()).toBe('at runtime') - } - }) - } + it('should partially prerender pages that use sync headers', async () => { + let $ = await next.render$('/headers/static-behavior/sync', {}) + if (isNextDev) { + expect($('#layout').text()).toBe('at runtime') + expect($('#page').text()).toBe('at runtime') + expect($('#x-sentinel').text()).toBe('hello') + } else { + expect($('#layout').text()).toBe('at buildtime') + expect($('#page').text()).toBe('at buildtime') + expect($('#x-sentinel').text()).toBe('hello') + } + }) + + it('should be able to pass headers as a promise to another component and trigger an intermediate Suspense boundary', async () => { + const $ = await next.render$('/headers/static-behavior/pass-deeply') + if (isNextDev) { + expect($('#layout').text()).toBe('at runtime') + expect($('#fallback').text()).toBe('at runtime') + expect($('#page').text()).toBe('at runtime') + } else { + expect($('#layout').text()).toBe('at buildtime') + expect($('#fallback').text()).toBe('at buildtime') + expect($('#page').text()).toBe('at runtime') + } + }) it('should be able to access headers properties asynchronously', async () => { let $ = await next.render$('/headers/exercise/async', {}) diff --git a/test/e2e/app-dir/dynamic-io/dynamic-io.node-crypto.test.ts b/test/e2e/app-dir/dynamic-io/dynamic-io.node-crypto.test.ts index 9f5f10ce6f53..27fc2a1b90b8 100644 --- a/test/e2e/app-dir/dynamic-io/dynamic-io.node-crypto.test.ts +++ b/test/e2e/app-dir/dynamic-io/dynamic-io.node-crypto.test.ts @@ -1,7 +1,5 @@ import { nextTestSetup } from 'e2e-utils' -const WITH_PPR = !!process.env.__NEXT_EXPERIMENTAL_PPR - describe('dynamic-io', () => { const { next, isNextDev, skipped } = nextTestSetup({ files: __dirname, @@ -36,12 +34,8 @@ describe('dynamic-io', () => { expect($('#layout').text()).toBe('at runtime') expect($('#page').text()).toBe('at runtime') expect($('#first').text()).not.toEqual($('#second').text()) - } else if (WITH_PPR) { - expect($('#layout').text()).toBe('at buildtime') - expect($('#page').text()).toBe('at runtime') - expect($('#first').text()).not.toEqual($('#second').text()) } else { - expect($('#layout').text()).toBe('at runtime') + expect($('#layout').text()).toBe('at buildtime') expect($('#page').text()).toBe('at runtime') expect($('#first').text()).not.toEqual($('#second').text()) } @@ -66,12 +60,8 @@ describe('dynamic-io', () => { expect($('#layout').text()).toBe('at runtime') expect($('#page').text()).toBe('at runtime') expect($('#first').text()).not.toEqual($('#second').text()) - } else if (WITH_PPR) { - expect($('#layout').text()).toBe('at buildtime') - expect($('#page').text()).toBe('at runtime') - expect($('#first').text()).not.toEqual($('#second').text()) } else { - expect($('#layout').text()).toBe('at runtime') + expect($('#layout').text()).toBe('at buildtime') expect($('#page').text()).toBe('at runtime') expect($('#first').text()).not.toEqual($('#second').text()) } @@ -96,12 +86,8 @@ describe('dynamic-io', () => { expect($('#layout').text()).toBe('at runtime') expect($('#page').text()).toBe('at runtime') expect($('#first').text()).not.toEqual($('#second').text()) - } else if (WITH_PPR) { - expect($('#layout').text()).toBe('at buildtime') - expect($('#page').text()).toBe('at runtime') - expect($('#first').text()).not.toEqual($('#second').text()) } else { - expect($('#layout').text()).toBe('at runtime') + expect($('#layout').text()).toBe('at buildtime') expect($('#page').text()).toBe('at runtime') expect($('#first').text()).not.toEqual($('#second').text()) } @@ -126,12 +112,8 @@ describe('dynamic-io', () => { expect($('#layout').text()).toBe('at runtime') expect($('#page').text()).toBe('at runtime') expect($('#first').text()).not.toEqual($('#second').text()) - } else if (WITH_PPR) { - expect($('#layout').text()).toBe('at buildtime') - expect($('#page').text()).toBe('at runtime') - expect($('#first').text()).not.toEqual($('#second').text()) } else { - expect($('#layout').text()).toBe('at runtime') + expect($('#layout').text()).toBe('at buildtime') expect($('#page').text()).toBe('at runtime') expect($('#first').text()).not.toEqual($('#second').text()) } @@ -156,12 +138,8 @@ describe('dynamic-io', () => { expect($('#layout').text()).toBe('at runtime') expect($('#page').text()).toBe('at runtime') expect($('#first').text()).not.toEqual($('#second').text()) - } else if (WITH_PPR) { - expect($('#layout').text()).toBe('at buildtime') - expect($('#page').text()).toBe('at runtime') - expect($('#first').text()).not.toEqual($('#second').text()) } else { - expect($('#layout').text()).toBe('at runtime') + expect($('#layout').text()).toBe('at buildtime') expect($('#page').text()).toBe('at runtime') expect($('#first').text()).not.toEqual($('#second').text()) } @@ -186,12 +164,8 @@ describe('dynamic-io', () => { expect($('#layout').text()).toBe('at runtime') expect($('#page').text()).toBe('at runtime') expect($('#first').text()).not.toEqual($('#second').text()) - } else if (WITH_PPR) { - expect($('#layout').text()).toBe('at buildtime') - expect($('#page').text()).toBe('at runtime') - expect($('#first').text()).not.toEqual($('#second').text()) } else { - expect($('#layout').text()).toBe('at runtime') + expect($('#layout').text()).toBe('at buildtime') expect($('#page').text()).toBe('at runtime') expect($('#first').text()).not.toEqual($('#second').text()) } @@ -216,12 +190,8 @@ describe('dynamic-io', () => { expect($('#layout').text()).toBe('at runtime') expect($('#page').text()).toBe('at runtime') expect($('#first').text()).not.toEqual($('#second').text()) - } else if (WITH_PPR) { - expect($('#layout').text()).toBe('at buildtime') - expect($('#page').text()).toBe('at runtime') - expect($('#first').text()).not.toEqual($('#second').text()) } else { - expect($('#layout').text()).toBe('at runtime') + expect($('#layout').text()).toBe('at buildtime') expect($('#page').text()).toBe('at runtime') expect($('#first').text()).not.toEqual($('#second').text()) } @@ -249,12 +219,8 @@ describe('dynamic-io', () => { expect($('#layout').text()).toBe('at runtime') expect($('#page').text()).toBe('at runtime') expect($('#first').text()).not.toEqual($('#second').text()) - } else if (WITH_PPR) { - expect($('#layout').text()).toBe('at buildtime') - expect($('#page').text()).toBe('at runtime') - expect($('#first').text()).not.toEqual($('#second').text()) } else { - expect($('#layout').text()).toBe('at runtime') + expect($('#layout').text()).toBe('at buildtime') expect($('#page').text()).toBe('at runtime') expect($('#first').text()).not.toEqual($('#second').text()) } @@ -279,12 +245,8 @@ describe('dynamic-io', () => { expect($('#layout').text()).toBe('at runtime') expect($('#page').text()).toBe('at runtime') expect($('#first').text()).not.toEqual($('#second').text()) - } else if (WITH_PPR) { - expect($('#layout').text()).toBe('at buildtime') - expect($('#page').text()).toBe('at runtime') - expect($('#first').text()).not.toEqual($('#second').text()) } else { - expect($('#layout').text()).toBe('at runtime') + expect($('#layout').text()).toBe('at buildtime') expect($('#page').text()).toBe('at runtime') expect($('#first').text()).not.toEqual($('#second').text()) } diff --git a/test/e2e/app-dir/dynamic-io/dynamic-io.params.test.ts b/test/e2e/app-dir/dynamic-io/dynamic-io.params.test.ts index 7283a98f12b5..8d9cf142e332 100644 --- a/test/e2e/app-dir/dynamic-io/dynamic-io.params.test.ts +++ b/test/e2e/app-dir/dynamic-io/dynamic-io.params.test.ts @@ -1,7 +1,5 @@ import { nextTestSetup } from 'e2e-utils' -const WITH_PPR = !!process.env.__NEXT_EXPERIMENTAL_PPR - // cSpell:words lowcard highcard describe('dynamic-io', () => { const { next, isNextDev, skipped } = nextTestSetup({ @@ -28,382 +26,189 @@ describe('dynamic-io', () => { } describe('Async Params', () => { - if (WITH_PPR) { - it('should partially prerender pages that await params in a server components', async () => { - expect(getLines('Route "/params')).toEqual([]) - - let $ = await next.render$( - '/params/semantics/one/build/async/layout-access/server' - ) - if (isNextDev) { - expect($('#layout').text()).toBe('at runtime') - - expect($('#lowcard').text()).toBe('at runtime') - expect($('#highcard').text()).toBe('at runtime') - expect($('#page').text()).toBe('at runtime') - expect($('#param-lowcard').text()).toBe('one') - expect($('#param-highcard').text()).toBe('build') - expect(getLines('Route "/params')).toEqual([]) - } else { - expect($('#layout').text()).toBe('at buildtime') - expect($('#lowcard').text()).toBe('at buildtime') - expect($('#highcard').text()).toBe('at buildtime') - expect($('#page').text()).toBe('at buildtime') - expect($('#param-lowcard').text()).toBe('one') - expect($('#param-highcard').text()).toBe('build') - } + it('should partially prerender pages that await params in a server components', async () => { + expect(getLines('Route "/params')).toEqual([]) - $ = await next.render$( - '/params/semantics/one/run/async/layout-access/server' - ) - if (isNextDev) { - expect($('#layout').text()).toBe('at runtime') - - expect($('#lowcard').text()).toBe('at runtime') - expect($('#highcard').text()).toBe('at runtime') - expect($('#page').text()).toBe('at runtime') - expect($('#param-lowcard').text()).toBe('one') - expect($('#param-highcard').text()).toBe('run') - expect(getLines('Route "/params')).toEqual([]) - } else { - expect($('#layout').text()).toBe('at buildtime') - expect($('#lowcard').text()).toBe('at buildtime') - expect($('#highcard').text()).toBe('at buildtime') - expect($('#highcard-fallback').text()).toBe( - 'loading highcard children' - ) - expect($('#page').text()).toBe('at runtime') - expect($('#param-lowcard').text()).toBe('one') - expect($('#param-highcard').text()).toBe('run') - expect(getLines('Route "/params')).toEqual([]) - } + let $ = await next.render$( + '/params/semantics/one/build/async/layout-access/server' + ) + if (isNextDev) { + expect($('#layout').text()).toBe('at runtime') - $ = await next.render$( - '/params/semantics/one/build/async/page-access/server' - ) - if (isNextDev) { - expect($('#layout').text()).toBe('at runtime') - - expect($('#lowcard').text()).toBe('at runtime') - expect($('#highcard').text()).toBe('at runtime') - expect($('#page').text()).toBe('at runtime') - expect($('#param-lowcard').text()).toBe('one') - expect($('#param-highcard').text()).toBe('build') - expect(getLines('Route "/params')).toEqual([]) - } else { - expect($('#layout').text()).toBe('at buildtime') - expect($('#lowcard').text()).toBe('at buildtime') - expect($('#highcard').text()).toBe('at buildtime') - expect($('#page').text()).toBe('at buildtime') - expect($('#param-lowcard').text()).toBe('one') - expect($('#param-highcard').text()).toBe('build') - expect(getLines('Route "/params')).toEqual([]) - } + expect($('#lowcard').text()).toBe('at runtime') + expect($('#highcard').text()).toBe('at runtime') + expect($('#page').text()).toBe('at runtime') + expect($('#param-lowcard').text()).toBe('one') + expect($('#param-highcard').text()).toBe('build') + expect(getLines('Route "/params')).toEqual([]) + } else { + expect($('#layout').text()).toBe('at buildtime') + expect($('#lowcard').text()).toBe('at buildtime') + expect($('#highcard').text()).toBe('at buildtime') + expect($('#page').text()).toBe('at buildtime') + expect($('#param-lowcard').text()).toBe('one') + expect($('#param-highcard').text()).toBe('build') + } - $ = await next.render$( - '/params/semantics/one/run/async/page-access/server' - ) - if (isNextDev) { - expect($('#layout').text()).toBe('at runtime') - - expect($('#lowcard').text()).toBe('at runtime') - expect($('#highcard').text()).toBe('at runtime') - expect($('#page').text()).toBe('at runtime') - expect($('#param-lowcard').text()).toBe('one') - expect($('#param-highcard').text()).toBe('run') - expect(getLines('Route "/params')).toEqual([]) - } else { - expect($('#layout').text()).toBe('at buildtime') - expect($('#lowcard').text()).toBe('at buildtime') - expect($('#highcard').text()).toBe('at buildtime') - expect($('#highcard-fallback').text()).toBe( - 'loading highcard children' - ) - expect($('#page').text()).toBe('at runtime') - expect($('#param-lowcard').text()).toBe('one') - expect($('#param-highcard').text()).toBe('run') - expect(getLines('Route "/params')).toEqual([]) - } - }) + $ = await next.render$( + '/params/semantics/one/run/async/layout-access/server' + ) + if (isNextDev) { + expect($('#layout').text()).toBe('at runtime') - it('should partially prerender pages that use params in a client components', async () => { + expect($('#lowcard').text()).toBe('at runtime') + expect($('#highcard').text()).toBe('at runtime') + expect($('#page').text()).toBe('at runtime') + expect($('#param-lowcard').text()).toBe('one') + expect($('#param-highcard').text()).toBe('run') + expect(getLines('Route "/params')).toEqual([]) + } else { + expect($('#layout').text()).toBe('at buildtime') + expect($('#lowcard').text()).toBe('at buildtime') + expect($('#highcard').text()).toBe('at buildtime') + expect($('#highcard-fallback').text()).toBe('loading highcard children') + expect($('#page').text()).toBe('at runtime') + expect($('#param-lowcard').text()).toBe('one') + expect($('#param-highcard').text()).toBe('run') expect(getLines('Route "/params')).toEqual([]) + } - let $ = await next.render$( - '/params/semantics/one/build/async/layout-access/client' - ) - if (isNextDev) { - expect($('#layout').text()).toBe('at runtime') - expect($('#lowcard').text()).toBe('at runtime') - expect($('#highcard').text()).toBe('at runtime') - expect($('#page').text()).toBe('at runtime') - expect($('#param-lowcard').text()).toBe('one') - expect($('#param-highcard').text()).toBe('build') - expect(getLines('Route "/params')).toEqual([]) - } else { - expect($('#layout').text()).toBe('at buildtime') - expect($('#lowcard').text()).toBe('at buildtime') - expect($('#highcard').text()).toBe('at buildtime') - expect($('#page').text()).toBe('at buildtime') - expect($('#param-lowcard').text()).toBe('one') - expect($('#param-highcard').text()).toBe('build') - expect(getLines('Route "/params')).toEqual([]) - } + $ = await next.render$( + '/params/semantics/one/build/async/page-access/server' + ) + if (isNextDev) { + expect($('#layout').text()).toBe('at runtime') - $ = await next.render$( - '/params/semantics/one/run/async/layout-access/client' - ) - if (isNextDev) { - expect($('#layout').text()).toBe('at runtime') - - expect($('#lowcard').text()).toBe('at runtime') - expect($('#highcard').text()).toBe('at runtime') - expect($('#page').text()).toBe('at runtime') - expect($('#param-lowcard').text()).toBe('one') - expect($('#param-highcard').text()).toBe('run') - expect(getLines('Route "/params')).toEqual([]) - } else { - expect($('#layout').text()).toBe('at buildtime') - expect($('#lowcard').text()).toBe('at buildtime') - expect($('#highcard').text()).toBe('at buildtime') - expect($('#highcard-fallback').text()).toBe( - 'loading highcard children' - ) - expect($('#page').text()).toBe('at runtime') - expect($('#param-lowcard').text()).toBe('one') - expect($('#param-highcard').text()).toBe('run') - expect(getLines('Route "/params')).toEqual([]) - } + expect($('#lowcard').text()).toBe('at runtime') + expect($('#highcard').text()).toBe('at runtime') + expect($('#page').text()).toBe('at runtime') + expect($('#param-lowcard').text()).toBe('one') + expect($('#param-highcard').text()).toBe('build') + expect(getLines('Route "/params')).toEqual([]) + } else { + expect($('#layout').text()).toBe('at buildtime') + expect($('#lowcard').text()).toBe('at buildtime') + expect($('#highcard').text()).toBe('at buildtime') + expect($('#page').text()).toBe('at buildtime') + expect($('#param-lowcard').text()).toBe('one') + expect($('#param-highcard').text()).toBe('build') + expect(getLines('Route "/params')).toEqual([]) + } - $ = await next.render$( - '/params/semantics/one/build/async/page-access/client' - ) - if (isNextDev) { - expect($('#layout').text()).toBe('at runtime') - expect($('#lowcard').text()).toBe('at runtime') - expect($('#highcard').text()).toBe('at runtime') - expect($('#page').text()).toBe('at runtime') - expect($('#param-lowcard').text()).toBe('one') - expect($('#param-highcard').text()).toBe('build') - expect(getLines('Route "/params')).toEqual([]) - } else { - expect($('#layout').text()).toBe('at buildtime') - expect($('#lowcard').text()).toBe('at buildtime') - expect($('#highcard').text()).toBe('at buildtime') - expect($('#page').text()).toBe('at buildtime') - expect($('#param-lowcard').text()).toBe('one') - expect($('#param-highcard').text()).toBe('build') - expect(getLines('Route "/params')).toEqual([]) - } + $ = await next.render$( + '/params/semantics/one/run/async/page-access/server' + ) + if (isNextDev) { + expect($('#layout').text()).toBe('at runtime') - $ = await next.render$( - '/params/semantics/one/run/async/page-access/client' - ) - if (isNextDev) { - expect($('#layout').text()).toBe('at runtime') - expect($('#lowcard').text()).toBe('at runtime') - expect($('#highcard').text()).toBe('at runtime') - expect($('#page').text()).toBe('at runtime') - expect($('#param-lowcard').text()).toBe('one') - expect($('#param-highcard').text()).toBe('run') - expect(getLines('Route "/params')).toEqual([]) - } else { - expect($('#layout').text()).toBe('at buildtime') - expect($('#lowcard').text()).toBe('at buildtime') - expect($('#highcard').text()).toBe('at buildtime') - expect($('#highcard-fallback').text()).toBe( - 'loading highcard children' - ) - expect($('#page').text()).toBe('at runtime') - expect($('#param-lowcard').text()).toBe('one') - expect($('#param-highcard').text()).toBe('run') - expect(getLines('Route "/params')).toEqual([]) - } - }) - } else { - it('should prerender pages that await params in a server component when prebuilt', async () => { + expect($('#lowcard').text()).toBe('at runtime') + expect($('#highcard').text()).toBe('at runtime') + expect($('#page').text()).toBe('at runtime') + expect($('#param-lowcard').text()).toBe('one') + expect($('#param-highcard').text()).toBe('run') expect(getLines('Route "/params')).toEqual([]) - let $ = await next.render$( - '/params/semantics/one/build/async/layout-access/server' - ) - if (isNextDev) { - expect($('#layout').text()).toBe('at runtime') - expect($('#lowcard').text()).toBe('at runtime') - expect($('#highcard').text()).toBe('at runtime') - expect($('#page').text()).toBe('at runtime') - expect($('#param-lowcard').text()).toBe('one') - expect($('#param-highcard').text()).toBe('build') - expect(getLines('Route "/params')).toEqual([]) - } else { - expect($('#layout').text()).toBe('at buildtime') - expect($('#lowcard').text()).toBe('at buildtime') - expect($('#highcard').text()).toBe('at buildtime') - expect($('#page').text()).toBe('at buildtime') - expect($('#param-lowcard').text()).toBe('one') - expect($('#param-highcard').text()).toBe('build') - expect(getLines('Route "/params')).toEqual([]) - } + } else { + expect($('#layout').text()).toBe('at buildtime') + expect($('#lowcard').text()).toBe('at buildtime') + expect($('#highcard').text()).toBe('at buildtime') + expect($('#highcard-fallback').text()).toBe('loading highcard children') + expect($('#page').text()).toBe('at runtime') + expect($('#param-lowcard').text()).toBe('one') + expect($('#param-highcard').text()).toBe('run') + expect(getLines('Route "/params')).toEqual([]) + } + }) - $ = await next.render$( - '/params/semantics/one/build/async/page-access/server' - ) - if (isNextDev) { - expect($('#layout').text()).toBe('at runtime') - expect($('#lowcard').text()).toBe('at runtime') - expect($('#highcard').text()).toBe('at runtime') - expect($('#page').text()).toBe('at runtime') - expect($('#param-lowcard').text()).toBe('one') - expect($('#param-highcard').text()).toBe('build') - expect(getLines('Route "/params')).toEqual([]) - } else { - expect($('#layout').text()).toBe('at buildtime') - expect($('#lowcard').text()).toBe('at buildtime') - expect($('#highcard').text()).toBe('at buildtime') - expect($('#page').text()).toBe('at buildtime') - expect($('#param-lowcard').text()).toBe('one') - expect($('#param-highcard').text()).toBe('build') - expect(getLines('Route "/params')).toEqual([]) - } - }) + it('should partially prerender pages that use params in a client components', async () => { + expect(getLines('Route "/params')).toEqual([]) - it('should prerender pages that `use` params in a client component when prebuilt', async () => { + let $ = await next.render$( + '/params/semantics/one/build/async/layout-access/client' + ) + if (isNextDev) { + expect($('#layout').text()).toBe('at runtime') + expect($('#lowcard').text()).toBe('at runtime') + expect($('#highcard').text()).toBe('at runtime') + expect($('#page').text()).toBe('at runtime') + expect($('#param-lowcard').text()).toBe('one') + expect($('#param-highcard').text()).toBe('build') expect(getLines('Route "/params')).toEqual([]) - let $ = await next.render$( - '/params/semantics/one/build/async/layout-access/client' - ) - if (isNextDev) { - expect($('#layout').text()).toBe('at runtime') - expect($('#lowcard').text()).toBe('at runtime') - expect($('#highcard').text()).toBe('at runtime') - expect($('#page').text()).toBe('at runtime') - expect($('#param-lowcard').text()).toBe('one') - expect($('#param-highcard').text()).toBe('build') - expect(getLines('Route "/params')).toEqual([]) - } else { - expect($('#layout').text()).toBe('at buildtime') - expect($('#lowcard').text()).toBe('at buildtime') - expect($('#highcard').text()).toBe('at buildtime') - expect($('#page').text()).toBe('at buildtime') - expect($('#param-lowcard').text()).toBe('one') - expect($('#param-highcard').text()).toBe('build') - expect(getLines('Route "/params')).toEqual([]) - } + } else { + expect($('#layout').text()).toBe('at buildtime') + expect($('#lowcard').text()).toBe('at buildtime') + expect($('#highcard').text()).toBe('at buildtime') + expect($('#page').text()).toBe('at buildtime') + expect($('#param-lowcard').text()).toBe('one') + expect($('#param-highcard').text()).toBe('build') + expect(getLines('Route "/params')).toEqual([]) + } - $ = await next.render$( - '/params/semantics/one/build/async/page-access/client' - ) - if (isNextDev) { - expect($('#layout').text()).toBe('at runtime') - expect($('#lowcard').text()).toBe('at runtime') - expect($('#highcard').text()).toBe('at runtime') - expect($('#page').text()).toBe('at runtime') - expect($('#param-lowcard').text()).toBe('one') - expect($('#param-highcard').text()).toBe('build') - expect(getLines('Route "/params')).toEqual([]) - } else { - // TODO at the moment pages receive searchParams which are not know at build time - // and always dynamic. We have to pessimistically assume they are accessed and thus - // we cannot actually produce a static shell without PPR. - expect($('#layout').text()).toBe('at runtime') - expect($('#lowcard').text()).toBe('at runtime') - expect($('#highcard').text()).toBe('at runtime') - expect($('#page').text()).toBe('at runtime') - expect($('#param-lowcard').text()).toBe('one') - expect($('#param-highcard').text()).toBe('build') - expect(getLines('Route "/params')).toEqual([]) - } - }) + $ = await next.render$( + '/params/semantics/one/run/async/layout-access/client' + ) + if (isNextDev) { + expect($('#layout').text()).toBe('at runtime') - it('should render pages that await params in a server component when not prebuilt', async () => { + expect($('#lowcard').text()).toBe('at runtime') + expect($('#highcard').text()).toBe('at runtime') + expect($('#page').text()).toBe('at runtime') + expect($('#param-lowcard').text()).toBe('one') + expect($('#param-highcard').text()).toBe('run') expect(getLines('Route "/params')).toEqual([]) - let $ = await next.render$( - '/params/semantics/one/run/async/layout-access/server' - ) - if (isNextDev) { - expect($('#layout').text()).toBe('at runtime') - expect($('#lowcard').text()).toBe('at runtime') - expect($('#highcard').text()).toBe('at runtime') - expect($('#page').text()).toBe('at runtime') - expect($('#param-lowcard').text()).toBe('one') - expect($('#param-highcard').text()).toBe('run') - expect(getLines('Route "/params')).toEqual([]) - } else { - expect($('#layout').text()).toBe('at runtime') - expect($('#lowcard').text()).toBe('at runtime') - expect($('#highcard').text()).toBe('at runtime') - expect($('#page').text()).toBe('at runtime') - expect($('#param-lowcard').text()).toBe('one') - expect($('#param-highcard').text()).toBe('run') - expect(getLines('Route "/params')).toEqual([]) - } - - $ = await next.render$( - '/params/semantics/one/run/async/page-access/server' - ) - if (isNextDev) { - expect($('#layout').text()).toBe('at runtime') - expect($('#lowcard').text()).toBe('at runtime') - expect($('#highcard').text()).toBe('at runtime') - expect($('#page').text()).toBe('at runtime') - expect($('#param-lowcard').text()).toBe('one') - expect($('#param-highcard').text()).toBe('run') - expect(getLines('Route "/params')).toEqual([]) - } else { - expect($('#layout').text()).toBe('at runtime') - expect($('#lowcard').text()).toBe('at runtime') - expect($('#highcard').text()).toBe('at runtime') - expect($('#page').text()).toBe('at runtime') - expect($('#param-lowcard').text()).toBe('one') - expect($('#param-highcard').text()).toBe('run') - expect(getLines('Route "/params')).toEqual([]) - } - }) + } else { + expect($('#layout').text()).toBe('at buildtime') + expect($('#lowcard').text()).toBe('at buildtime') + expect($('#highcard').text()).toBe('at buildtime') + expect($('#highcard-fallback').text()).toBe('loading highcard children') + expect($('#page').text()).toBe('at runtime') + expect($('#param-lowcard').text()).toBe('one') + expect($('#param-highcard').text()).toBe('run') + expect(getLines('Route "/params')).toEqual([]) + } - it('should render pages that `use` params in a client component when not prebuilt', async () => { + $ = await next.render$( + '/params/semantics/one/build/async/page-access/client' + ) + if (isNextDev) { + expect($('#layout').text()).toBe('at runtime') + expect($('#lowcard').text()).toBe('at runtime') + expect($('#highcard').text()).toBe('at runtime') + expect($('#page').text()).toBe('at runtime') + expect($('#param-lowcard').text()).toBe('one') + expect($('#param-highcard').text()).toBe('build') expect(getLines('Route "/params')).toEqual([]) - let $ = await next.render$( - '/params/semantics/one/run/async/layout-access/client' - ) - if (isNextDev) { - expect($('#layout').text()).toBe('at runtime') - expect($('#lowcard').text()).toBe('at runtime') - expect($('#highcard').text()).toBe('at runtime') - expect($('#page').text()).toBe('at runtime') - expect($('#param-lowcard').text()).toBe('one') - expect($('#param-highcard').text()).toBe('run') - expect(getLines('Route "/params')).toEqual([]) - } else { - expect($('#layout').text()).toBe('at runtime') - expect($('#lowcard').text()).toBe('at runtime') - expect($('#highcard').text()).toBe('at runtime') - expect($('#page').text()).toBe('at runtime') - expect($('#param-lowcard').text()).toBe('one') - expect($('#param-highcard').text()).toBe('run') - expect(getLines('Route "/params')).toEqual([]) - } + } else { + expect($('#layout').text()).toBe('at buildtime') + expect($('#lowcard').text()).toBe('at buildtime') + expect($('#highcard').text()).toBe('at buildtime') + expect($('#page').text()).toBe('at buildtime') + expect($('#param-lowcard').text()).toBe('one') + expect($('#param-highcard').text()).toBe('build') + expect(getLines('Route "/params')).toEqual([]) + } - $ = await next.render$( - '/params/semantics/one/run/async/page-access/client' - ) - if (isNextDev) { - expect($('#layout').text()).toBe('at runtime') - expect($('#lowcard').text()).toBe('at runtime') - expect($('#highcard').text()).toBe('at runtime') - expect($('#page').text()).toBe('at runtime') - expect($('#param-lowcard').text()).toBe('one') - expect($('#param-highcard').text()).toBe('run') - expect(getLines('Route "/params')).toEqual([]) - } else { - expect($('#layout').text()).toBe('at runtime') - expect($('#lowcard').text()).toBe('at runtime') - expect($('#highcard').text()).toBe('at runtime') - expect($('#page').text()).toBe('at runtime') - expect($('#param-lowcard').text()).toBe('one') - expect($('#param-highcard').text()).toBe('run') - expect(getLines('Route "/params')).toEqual([]) - } - }) - } + $ = await next.render$( + '/params/semantics/one/run/async/page-access/client' + ) + if (isNextDev) { + expect($('#layout').text()).toBe('at runtime') + expect($('#lowcard').text()).toBe('at runtime') + expect($('#highcard').text()).toBe('at runtime') + expect($('#page').text()).toBe('at runtime') + expect($('#param-lowcard').text()).toBe('one') + expect($('#param-highcard').text()).toBe('run') + expect(getLines('Route "/params')).toEqual([]) + } else { + expect($('#layout').text()).toBe('at buildtime') + expect($('#lowcard').text()).toBe('at buildtime') + expect($('#highcard').text()).toBe('at buildtime') + expect($('#highcard-fallback').text()).toBe('loading highcard children') + expect($('#page').text()).toBe('at runtime') + expect($('#param-lowcard').text()).toBe('one') + expect($('#param-highcard').text()).toBe('run') + expect(getLines('Route "/params')).toEqual([]) + } + }) it('should fully prerender pages that check individual param keys after awaiting params in a server component', async () => { expect(getLines('Route "/params')).toEqual([]) @@ -466,30 +271,16 @@ describe('dynamic-io', () => { expect($('#param-has-foo').text()).toBe('false') expect(getLines('Route "/params')).toEqual([]) } else { - if (WITH_PPR) { - // With PPR fallbacks the first visit is still partially prerendered - expect($('#layout').text()).toBe('at buildtime') - expect($('#lowcard').text()).toBe('at buildtime') - expect($('#highcard').text()).toBe('at buildtime') - expect($('#highcard-fallback').text()).toBe( - 'loading highcard children' - ) - expect($('#page').text()).toBe('at runtime') - expect($('#param-has-lowcard').text()).toBe('true') - expect($('#param-has-highcard').text()).toBe('true') - expect($('#param-has-foo').text()).toBe('false') - expect(getLines('Route "/params')).toEqual([]) - } else { - // without PPR the first visit is dynamic - expect($('#layout').text()).toBe('at runtime') - expect($('#lowcard').text()).toBe('at runtime') - expect($('#highcard').text()).toBe('at runtime') - expect($('#page').text()).toBe('at runtime') - expect($('#param-has-lowcard').text()).toBe('true') - expect($('#param-has-highcard').text()).toBe('true') - expect($('#param-has-foo').text()).toBe('false') - expect(getLines('Route "/params')).toEqual([]) - } + // With PPR fallbacks the first visit is still partially prerendered + expect($('#layout').text()).toBe('at buildtime') + expect($('#lowcard').text()).toBe('at buildtime') + expect($('#highcard').text()).toBe('at buildtime') + expect($('#highcard-fallback').text()).toBe('loading highcard children') + expect($('#page').text()).toBe('at runtime') + expect($('#param-has-lowcard').text()).toBe('true') + expect($('#param-has-highcard').text()).toBe('true') + expect($('#param-has-foo').text()).toBe('false') + expect(getLines('Route "/params')).toEqual([]) } $ = await next.render$('/params/semantics/one/run/async/page-has/server') @@ -503,30 +294,16 @@ describe('dynamic-io', () => { expect($('#param-has-foo').text()).toBe('false') expect(getLines('Route "/params')).toEqual([]) } else { - if (WITH_PPR) { - // With PPR fallbacks the first visit is still partially prerendered - expect($('#layout').text()).toBe('at buildtime') - expect($('#lowcard').text()).toBe('at buildtime') - expect($('#highcard').text()).toBe('at buildtime') - expect($('#highcard-fallback').text()).toBe( - 'loading highcard children' - ) - expect($('#page').text()).toBe('at runtime') - expect($('#param-has-lowcard').text()).toBe('true') - expect($('#param-has-highcard').text()).toBe('true') - expect($('#param-has-foo').text()).toBe('false') - expect(getLines('Route "/params')).toEqual([]) - } else { - // without PPR the first visit is dynamic - expect($('#layout').text()).toBe('at runtime') - expect($('#lowcard').text()).toBe('at runtime') - expect($('#highcard').text()).toBe('at runtime') - expect($('#page').text()).toBe('at runtime') - expect($('#param-has-lowcard').text()).toBe('true') - expect($('#param-has-highcard').text()).toBe('true') - expect($('#param-has-foo').text()).toBe('false') - expect(getLines('Route "/params')).toEqual([]) - } + // With PPR fallbacks the first visit is still partially prerendered + expect($('#layout').text()).toBe('at buildtime') + expect($('#lowcard').text()).toBe('at buildtime') + expect($('#highcard').text()).toBe('at buildtime') + expect($('#highcard-fallback').text()).toBe('loading highcard children') + expect($('#page').text()).toBe('at runtime') + expect($('#param-has-lowcard').text()).toBe('true') + expect($('#param-has-highcard').text()).toBe('true') + expect($('#param-has-foo').text()).toBe('false') + expect(getLines('Route "/params')).toEqual([]) } }) @@ -568,31 +345,14 @@ describe('dynamic-io', () => { expect($('#param-has-foo').text()).toBe('false') expect(getLines('Route "/params')).toEqual([]) } else { - if (WITH_PPR) { - expect($('#layout').text()).toBe('at buildtime') - expect($('#lowcard').text()).toBe('at buildtime') - expect($('#highcard').text()).toBe('at buildtime') - expect($('#page').text()).toBe('at buildtime') - expect($('#param-has-lowcard').text()).toBe('true') - expect($('#param-has-highcard').text()).toBe('true') - expect($('#param-has-foo').text()).toBe('false') - expect(getLines('Route "/params')).toEqual([]) - } else { - // When dynamicIO is on and PPR is off the search params passed to a client page - // are enough to mark the whole route as dynamic. This is because we can't know if - // you are going to use those searchParams in an update on the client so we can't infer - // anything about your lack of use during SSR. In the future we will update searchParams - // written to the client to actually derive those params from location and thus not - // require dynamic - expect($('#layout').text()).toBe('at runtime') - expect($('#lowcard').text()).toBe('at runtime') - expect($('#highcard').text()).toBe('at runtime') - expect($('#page').text()).toBe('at runtime') - expect($('#param-has-lowcard').text()).toBe('true') - expect($('#param-has-highcard').text()).toBe('true') - expect($('#param-has-foo').text()).toBe('false') - expect(getLines('Route "/params')).toEqual([]) - } + expect($('#layout').text()).toBe('at buildtime') + expect($('#lowcard').text()).toBe('at buildtime') + expect($('#highcard').text()).toBe('at buildtime') + expect($('#page').text()).toBe('at buildtime') + expect($('#param-has-lowcard').text()).toBe('true') + expect($('#param-has-highcard').text()).toBe('true') + expect($('#param-has-foo').text()).toBe('false') + expect(getLines('Route "/params')).toEqual([]) } $ = await next.render$( @@ -608,30 +368,16 @@ describe('dynamic-io', () => { expect($('#param-has-foo').text()).toBe('false') expect(getLines('Route "/params')).toEqual([]) } else { - if (WITH_PPR) { - // With PPR fallbacks the first visit is still partially prerendered - expect($('#layout').text()).toBe('at buildtime') - expect($('#lowcard').text()).toBe('at buildtime') - expect($('#highcard').text()).toBe('at buildtime') - expect($('#highcard-fallback').text()).toBe( - 'loading highcard children' - ) - expect($('#page').text()).toBe('at runtime') - expect($('#param-has-lowcard').text()).toBe('true') - expect($('#param-has-highcard').text()).toBe('true') - expect($('#param-has-foo').text()).toBe('false') - expect(getLines('Route "/params')).toEqual([]) - } else { - // without PPR the first visit is dynamic - expect($('#layout').text()).toBe('at runtime') - expect($('#lowcard').text()).toBe('at runtime') - expect($('#highcard').text()).toBe('at runtime') - expect($('#page').text()).toBe('at runtime') - expect($('#param-has-lowcard').text()).toBe('true') - expect($('#param-has-highcard').text()).toBe('true') - expect($('#param-has-foo').text()).toBe('false') - expect(getLines('Route "/params')).toEqual([]) - } + // With PPR fallbacks the first visit is still partially prerendered + expect($('#layout').text()).toBe('at buildtime') + expect($('#lowcard').text()).toBe('at buildtime') + expect($('#highcard').text()).toBe('at buildtime') + expect($('#highcard-fallback').text()).toBe('loading highcard children') + expect($('#page').text()).toBe('at runtime') + expect($('#param-has-lowcard').text()).toBe('true') + expect($('#param-has-highcard').text()).toBe('true') + expect($('#param-has-foo').text()).toBe('false') + expect(getLines('Route "/params')).toEqual([]) } $ = await next.render$('/params/semantics/one/run/async/page-has/client') @@ -645,859 +391,422 @@ describe('dynamic-io', () => { expect($('#param-has-foo').text()).toBe('false') expect(getLines('Route "/params')).toEqual([]) } else { - if (WITH_PPR) { - // With PPR fallbacks the first visit is still partially prerendered - expect($('#layout').text()).toBe('at buildtime') - expect($('#lowcard').text()).toBe('at buildtime') - expect($('#highcard').text()).toBe('at buildtime') - expect($('#highcard-fallback').text()).toBe( - 'loading highcard children' - ) - expect($('#page').text()).toBe('at runtime') - expect($('#param-has-lowcard').text()).toBe('true') - expect($('#param-has-highcard').text()).toBe('true') - expect($('#param-has-foo').text()).toBe('false') - expect(getLines('Route "/params')).toEqual([]) - } else { - // When dynamicIO is on and PPR is off the search params passed to a client page - // are enough to mark the whole route as dynamic. This is because we can't know if - // you are going to use those searchParams in an update on the client so we can't infer - // anything about your lack of use during SSR. In the future we will update searchParams - // written to the client to actually derive those params from location and thus not - // require dynamic - expect($('#layout').text()).toBe('at runtime') - expect($('#lowcard').text()).toBe('at runtime') - expect($('#highcard').text()).toBe('at runtime') - expect($('#page').text()).toBe('at runtime') - expect($('#param-has-lowcard').text()).toBe('true') - expect($('#param-has-highcard').text()).toBe('true') - expect($('#param-has-foo').text()).toBe('false') - expect(getLines('Route "/params')).toEqual([]) - } + // With PPR fallbacks the first visit is still partially prerendered + expect($('#layout').text()).toBe('at buildtime') + expect($('#lowcard').text()).toBe('at buildtime') + expect($('#highcard').text()).toBe('at buildtime') + expect($('#highcard-fallback').text()).toBe('loading highcard children') + expect($('#page').text()).toBe('at runtime') + expect($('#param-has-lowcard').text()).toBe('true') + expect($('#param-has-highcard').text()).toBe('true') + expect($('#param-has-foo').text()).toBe('false') + expect(getLines('Route "/params')).toEqual([]) } }) - if (WITH_PPR) { - it('should partially prerender pages that spread awaited params in a server component', async () => { + it('should partially prerender pages that spread awaited params in a server component', async () => { + expect(getLines('Route "/params')).toEqual([]) + let $ = await next.render$( + '/params/semantics/one/build/async/layout-spread/server' + ) + if (isNextDev) { + expect($('#layout').text()).toBe('at runtime') + expect($('#lowcard').text()).toBe('at runtime') + expect($('#highcard').text()).toBe('at runtime') + expect($('#page').text()).toBe('at runtime') + expect($('#param-copied-lowcard').text()).toBe('one') + expect($('#param-copied-highcard').text()).toBe('build') + expect($('#param-key-count').text()).toBe('2') expect(getLines('Route "/params')).toEqual([]) - let $ = await next.render$( - '/params/semantics/one/build/async/layout-spread/server' - ) - if (isNextDev) { - expect($('#layout').text()).toBe('at runtime') - expect($('#lowcard').text()).toBe('at runtime') - expect($('#highcard').text()).toBe('at runtime') - expect($('#page').text()).toBe('at runtime') - expect($('#param-copied-lowcard').text()).toBe('one') - expect($('#param-copied-highcard').text()).toBe('build') - expect($('#param-key-count').text()).toBe('2') - expect(getLines('Route "/params')).toEqual([]) - } else { - expect($('#layout').text()).toBe('at buildtime') - expect($('#lowcard').text()).toBe('at buildtime') - expect($('#highcard').text()).toBe('at buildtime') - expect($('#page').text()).toBe('at buildtime') - expect($('#param-copied-lowcard').text()).toBe('one') - expect($('#param-copied-highcard').text()).toBe('build') - expect($('#param-key-count').text()).toBe('2') - expect(getLines('Route "/params')).toEqual([]) - } - - $ = await next.render$( - '/params/semantics/one/build/async/page-spread/server' - ) - if (isNextDev) { - expect($('#layout').text()).toBe('at runtime') - expect($('#lowcard').text()).toBe('at runtime') - expect($('#highcard').text()).toBe('at runtime') - expect($('#page').text()).toBe('at runtime') - expect($('#param-copied-lowcard').text()).toBe('one') - expect($('#param-copied-highcard').text()).toBe('build') - expect($('#param-key-count').text()).toBe('2') - expect(getLines('Route "/params')).toEqual([]) - } else { - expect($('#layout').text()).toBe('at buildtime') - expect($('#lowcard').text()).toBe('at buildtime') - expect($('#highcard').text()).toBe('at buildtime') - expect($('#page').text()).toBe('at buildtime') - expect($('#param-copied-lowcard').text()).toBe('one') - expect($('#param-copied-highcard').text()).toBe('build') - expect($('#param-key-count').text()).toBe('2') - expect(getLines('Route "/params')).toEqual([]) - } - - $ = await next.render$( - '/params/semantics/one/run/async/layout-spread/server' - ) - if (isNextDev) { - expect($('#layout').text()).toBe('at runtime') - expect($('#lowcard').text()).toBe('at runtime') - expect($('#highcard').text()).toBe('at runtime') - expect($('#page').text()).toBe('at runtime') - expect($('#param-copied-lowcard').text()).toBe('one') - expect($('#param-copied-highcard').text()).toBe('run') - expect($('#param-key-count').text()).toBe('2') - expect(getLines('Route "/params')).toEqual([]) - } else { - expect($('#layout').text()).toBe('at buildtime') - expect($('#lowcard').text()).toBe('at buildtime') - expect($('#highcard').text()).toBe('at buildtime') - expect($('#highcard-fallback').text()).toBe( - 'loading highcard children' - ) - expect($('#page').text()).toBe('at runtime') - expect($('#param-copied-lowcard').text()).toBe('one') - expect($('#param-copied-highcard').text()).toBe('run') - expect($('#param-key-count').text()).toBe('2') - expect(getLines('Route "/params')).toEqual([]) - } - - $ = await next.render$( - '/params/semantics/one/run/async/page-spread/server' - ) - if (isNextDev) { - expect($('#layout').text()).toBe('at runtime') - expect($('#lowcard').text()).toBe('at runtime') - expect($('#highcard').text()).toBe('at runtime') - expect($('#page').text()).toBe('at runtime') - expect($('#param-copied-lowcard').text()).toBe('one') - expect($('#param-copied-highcard').text()).toBe('run') - expect($('#param-key-count').text()).toBe('2') - expect(getLines('Route "/params')).toEqual([]) - } else { - expect($('#layout').text()).toBe('at buildtime') - expect($('#lowcard').text()).toBe('at buildtime') - expect($('#highcard').text()).toBe('at buildtime') - expect($('#highcard-fallback').text()).toBe( - 'loading highcard children' - ) - expect($('#page').text()).toBe('at runtime') - expect($('#param-copied-lowcard').text()).toBe('one') - expect($('#param-copied-highcard').text()).toBe('run') - expect($('#param-key-count').text()).toBe('2') - expect(getLines('Route "/params')).toEqual([]) - } - }) - - it('should partially prerender pages that spread `use`ed params in a client component', async () => { + } else { + expect($('#layout').text()).toBe('at buildtime') + expect($('#lowcard').text()).toBe('at buildtime') + expect($('#highcard').text()).toBe('at buildtime') + expect($('#page').text()).toBe('at buildtime') + expect($('#param-copied-lowcard').text()).toBe('one') + expect($('#param-copied-highcard').text()).toBe('build') + expect($('#param-key-count').text()).toBe('2') expect(getLines('Route "/params')).toEqual([]) - let $ = await next.render$( - '/params/semantics/one/build/async/layout-spread/client' - ) - if (isNextDev) { - expect($('#layout').text()).toBe('at runtime') - expect($('#lowcard').text()).toBe('at runtime') - expect($('#highcard').text()).toBe('at runtime') - expect($('#page').text()).toBe('at runtime') - expect($('#param-copied-lowcard').text()).toBe('one') - expect($('#param-copied-highcard').text()).toBe('build') - expect($('#param-key-count').text()).toBe('2') - expect(getLines('Route "/params')).toEqual([]) - } else { - expect($('#layout').text()).toBe('at buildtime') - expect($('#lowcard').text()).toBe('at buildtime') - expect($('#highcard').text()).toBe('at buildtime') - expect($('#page').text()).toBe('at buildtime') - expect($('#param-copied-lowcard').text()).toBe('one') - expect($('#param-copied-highcard').text()).toBe('build') - expect($('#param-key-count').text()).toBe('2') - expect(getLines('Route "/params')).toEqual([]) - } - - $ = await next.render$( - '/params/semantics/one/build/async/page-spread/client' - ) - if (isNextDev) { - expect($('#layout').text()).toBe('at runtime') - expect($('#lowcard').text()).toBe('at runtime') - expect($('#highcard').text()).toBe('at runtime') - expect($('#page').text()).toBe('at runtime') - expect($('#param-copied-lowcard').text()).toBe('one') - expect($('#param-copied-highcard').text()).toBe('build') - expect($('#param-key-count').text()).toBe('2') - expect(getLines('Route "/params')).toEqual([]) - } else { - expect($('#layout').text()).toBe('at buildtime') - expect($('#lowcard').text()).toBe('at buildtime') - expect($('#highcard').text()).toBe('at buildtime') - expect($('#page').text()).toBe('at buildtime') - expect($('#param-copied-lowcard').text()).toBe('one') - expect($('#param-copied-highcard').text()).toBe('build') - expect($('#param-key-count').text()).toBe('2') - expect(getLines('Route "/params')).toEqual([]) - } - - $ = await next.render$( - '/params/semantics/one/run/async/layout-spread/client' - ) - if (isNextDev) { - expect($('#layout').text()).toBe('at runtime') - expect($('#lowcard').text()).toBe('at runtime') - expect($('#highcard').text()).toBe('at runtime') - expect($('#page').text()).toBe('at runtime') - expect($('#param-copied-lowcard').text()).toBe('one') - expect($('#param-copied-highcard').text()).toBe('run') - expect($('#param-key-count').text()).toBe('2') - expect(getLines('Route "/params')).toEqual([]) - } else { - expect($('#layout').text()).toBe('at buildtime') - expect($('#lowcard').text()).toBe('at buildtime') - expect($('#highcard').text()).toBe('at buildtime') - expect($('#highcard-fallback').text()).toBe( - 'loading highcard children' - ) - expect($('#page').text()).toBe('at runtime') - expect($('#param-copied-lowcard').text()).toBe('one') - expect($('#param-copied-highcard').text()).toBe('run') - expect($('#param-key-count').text()).toBe('2') - expect(getLines('Route "/params')).toEqual([]) - } + } - $ = await next.render$( - '/params/semantics/one/run/async/page-spread/client' - ) - if (isNextDev) { - expect($('#layout').text()).toBe('at runtime') - expect($('#lowcard').text()).toBe('at runtime') - expect($('#highcard').text()).toBe('at runtime') - expect($('#page').text()).toBe('at runtime') - expect($('#param-copied-lowcard').text()).toBe('one') - expect($('#param-copied-highcard').text()).toBe('run') - expect($('#param-key-count').text()).toBe('2') - expect(getLines('Route "/params')).toEqual([]) - } else { - expect($('#layout').text()).toBe('at buildtime') - expect($('#lowcard').text()).toBe('at buildtime') - expect($('#highcard').text()).toBe('at buildtime') - expect($('#highcard-fallback').text()).toBe( - 'loading highcard children' - ) - expect($('#page').text()).toBe('at runtime') - expect($('#param-copied-lowcard').text()).toBe('one') - expect($('#param-copied-highcard').text()).toBe('run') - expect($('#param-key-count').text()).toBe('2') - expect(getLines('Route "/params')).toEqual([]) - } - }) - } else { - it('should prerender pages that spread awaited params in a server component when prebuilt', async () => { + $ = await next.render$( + '/params/semantics/one/build/async/page-spread/server' + ) + if (isNextDev) { + expect($('#layout').text()).toBe('at runtime') + expect($('#lowcard').text()).toBe('at runtime') + expect($('#highcard').text()).toBe('at runtime') + expect($('#page').text()).toBe('at runtime') + expect($('#param-copied-lowcard').text()).toBe('one') + expect($('#param-copied-highcard').text()).toBe('build') + expect($('#param-key-count').text()).toBe('2') expect(getLines('Route "/params')).toEqual([]) - let $ = await next.render$( - '/params/semantics/one/build/async/layout-spread/server' - ) - if (isNextDev) { - expect($('#layout').text()).toBe('at runtime') - expect($('#lowcard').text()).toBe('at runtime') - expect($('#highcard').text()).toBe('at runtime') - expect($('#page').text()).toBe('at runtime') - expect($('#param-copied-lowcard').text()).toBe('one') - expect($('#param-copied-highcard').text()).toBe('build') - expect($('#param-key-count').text()).toBe('2') - expect(getLines('Route "/params')).toEqual([]) - } else { - expect($('#layout').text()).toBe('at buildtime') - expect($('#lowcard').text()).toBe('at buildtime') - expect($('#highcard').text()).toBe('at buildtime') - expect($('#page').text()).toBe('at buildtime') - expect($('#param-copied-lowcard').text()).toBe('one') - expect($('#param-copied-highcard').text()).toBe('build') - expect($('#param-key-count').text()).toBe('2') - expect(getLines('Route "/params')).toEqual([]) - } - - $ = await next.render$( - '/params/semantics/one/build/async/page-spread/server' - ) - if (isNextDev) { - expect($('#layout').text()).toBe('at runtime') - expect($('#lowcard').text()).toBe('at runtime') - expect($('#highcard').text()).toBe('at runtime') - expect($('#page').text()).toBe('at runtime') - expect($('#param-copied-lowcard').text()).toBe('one') - expect($('#param-copied-highcard').text()).toBe('build') - expect($('#param-key-count').text()).toBe('2') - expect(getLines('Route "/params')).toEqual([]) - } else { - expect($('#layout').text()).toBe('at buildtime') - expect($('#lowcard').text()).toBe('at buildtime') - expect($('#highcard').text()).toBe('at buildtime') - expect($('#page').text()).toBe('at buildtime') - expect($('#param-copied-lowcard').text()).toBe('one') - expect($('#param-copied-highcard').text()).toBe('build') - expect($('#param-key-count').text()).toBe('2') - expect(getLines('Route "/params')).toEqual([]) - } - }) + } else { + expect($('#layout').text()).toBe('at buildtime') + expect($('#lowcard').text()).toBe('at buildtime') + expect($('#highcard').text()).toBe('at buildtime') + expect($('#page').text()).toBe('at buildtime') + expect($('#param-copied-lowcard').text()).toBe('one') + expect($('#param-copied-highcard').text()).toBe('build') + expect($('#param-key-count').text()).toBe('2') + expect(getLines('Route "/params')).toEqual([]) + } - it('should prerender pages that spread `use`ed params in a client component when prebuilt', async () => { + $ = await next.render$( + '/params/semantics/one/run/async/layout-spread/server' + ) + if (isNextDev) { + expect($('#layout').text()).toBe('at runtime') + expect($('#lowcard').text()).toBe('at runtime') + expect($('#highcard').text()).toBe('at runtime') + expect($('#page').text()).toBe('at runtime') + expect($('#param-copied-lowcard').text()).toBe('one') + expect($('#param-copied-highcard').text()).toBe('run') + expect($('#param-key-count').text()).toBe('2') expect(getLines('Route "/params')).toEqual([]) - let $ = await next.render$( - '/params/semantics/one/build/async/layout-spread/client' - ) - if (isNextDev) { - expect($('#layout').text()).toBe('at runtime') - expect($('#lowcard').text()).toBe('at runtime') - expect($('#highcard').text()).toBe('at runtime') - expect($('#page').text()).toBe('at runtime') - expect($('#param-copied-lowcard').text()).toBe('one') - expect($('#param-copied-highcard').text()).toBe('build') - expect($('#param-key-count').text()).toBe('2') - expect(getLines('Route "/params')).toEqual([]) - } else { - expect($('#layout').text()).toBe('at buildtime') - expect($('#lowcard').text()).toBe('at buildtime') - expect($('#highcard').text()).toBe('at buildtime') - expect($('#page').text()).toBe('at buildtime') - expect($('#param-copied-lowcard').text()).toBe('one') - expect($('#param-copied-highcard').text()).toBe('build') - expect($('#param-key-count').text()).toBe('2') - expect(getLines('Route "/params')).toEqual([]) - } + } else { + expect($('#layout').text()).toBe('at buildtime') + expect($('#lowcard').text()).toBe('at buildtime') + expect($('#highcard').text()).toBe('at buildtime') + expect($('#highcard-fallback').text()).toBe('loading highcard children') + expect($('#page').text()).toBe('at runtime') + expect($('#param-copied-lowcard').text()).toBe('one') + expect($('#param-copied-highcard').text()).toBe('run') + expect($('#param-key-count').text()).toBe('2') + expect(getLines('Route "/params')).toEqual([]) + } - $ = await next.render$( - '/params/semantics/one/build/async/page-spread/client' - ) - if (isNextDev) { - expect($('#layout').text()).toBe('at runtime') - expect($('#lowcard').text()).toBe('at runtime') - expect($('#highcard').text()).toBe('at runtime') - expect($('#page').text()).toBe('at runtime') - expect($('#param-copied-lowcard').text()).toBe('one') - expect($('#param-copied-highcard').text()).toBe('build') - expect($('#param-key-count').text()).toBe('2') - expect(getLines('Route "/params')).toEqual([]) - } else { - // TODO at the moment pages receive searchParams which are not know at build time - // and always dynamic. We have to pessimistically assume they are accessed and thus - // we cannot actually produce a static shell without PPR. - expect($('#layout').text()).toBe('at runtime') - expect($('#lowcard').text()).toBe('at runtime') - expect($('#highcard').text()).toBe('at runtime') - expect($('#page').text()).toBe('at runtime') - expect($('#param-copied-lowcard').text()).toBe('one') - expect($('#param-copied-highcard').text()).toBe('build') - expect($('#param-key-count').text()).toBe('2') - expect(getLines('Route "/params')).toEqual([]) - } - }) + $ = await next.render$( + '/params/semantics/one/run/async/page-spread/server' + ) + if (isNextDev) { + expect($('#layout').text()).toBe('at runtime') + expect($('#lowcard').text()).toBe('at runtime') + expect($('#highcard').text()).toBe('at runtime') + expect($('#page').text()).toBe('at runtime') + expect($('#param-copied-lowcard').text()).toBe('one') + expect($('#param-copied-highcard').text()).toBe('run') + expect($('#param-key-count').text()).toBe('2') + expect(getLines('Route "/params')).toEqual([]) + } else { + expect($('#layout').text()).toBe('at buildtime') + expect($('#lowcard').text()).toBe('at buildtime') + expect($('#highcard').text()).toBe('at buildtime') + expect($('#highcard-fallback').text()).toBe('loading highcard children') + expect($('#page').text()).toBe('at runtime') + expect($('#param-copied-lowcard').text()).toBe('one') + expect($('#param-copied-highcard').text()).toBe('run') + expect($('#param-key-count').text()).toBe('2') + expect(getLines('Route "/params')).toEqual([]) + } + }) - it('should render pages that spread awaited params in a server component when not prebuilt', async () => { - let $ = await next.render$( - '/params/semantics/one/run/async/layout-spread/server' - ) - if (isNextDev) { - expect($('#layout').text()).toBe('at runtime') - expect($('#lowcard').text()).toBe('at runtime') - expect($('#highcard').text()).toBe('at runtime') - expect($('#page').text()).toBe('at runtime') - expect($('#param-copied-lowcard').text()).toBe('one') - expect($('#param-copied-highcard').text()).toBe('run') - expect($('#param-key-count').text()).toBe('2') - } else { - expect($('#layout').text()).toBe('at runtime') - - expect($('#lowcard').text()).toBe('at runtime') - expect($('#highcard').text()).toBe('at runtime') - expect($('#page').text()).toBe('at runtime') - expect($('#param-copied-lowcard').text()).toBe('one') - expect($('#param-copied-highcard').text()).toBe('run') - expect($('#param-key-count').text()).toBe('2') - } + it('should partially prerender pages that spread `use`ed params in a client component', async () => { + expect(getLines('Route "/params')).toEqual([]) + let $ = await next.render$( + '/params/semantics/one/build/async/layout-spread/client' + ) + if (isNextDev) { + expect($('#layout').text()).toBe('at runtime') + expect($('#lowcard').text()).toBe('at runtime') + expect($('#highcard').text()).toBe('at runtime') + expect($('#page').text()).toBe('at runtime') + expect($('#param-copied-lowcard').text()).toBe('one') + expect($('#param-copied-highcard').text()).toBe('build') + expect($('#param-key-count').text()).toBe('2') + expect(getLines('Route "/params')).toEqual([]) + } else { + expect($('#layout').text()).toBe('at buildtime') + expect($('#lowcard').text()).toBe('at buildtime') + expect($('#highcard').text()).toBe('at buildtime') + expect($('#page').text()).toBe('at buildtime') + expect($('#param-copied-lowcard').text()).toBe('one') + expect($('#param-copied-highcard').text()).toBe('build') + expect($('#param-key-count').text()).toBe('2') + expect(getLines('Route "/params')).toEqual([]) + } - $ = await next.render$( - '/params/semantics/one/run/async/page-spread/server' - ) - if (isNextDev) { - expect($('#layout').text()).toBe('at runtime') - expect($('#lowcard').text()).toBe('at runtime') - expect($('#highcard').text()).toBe('at runtime') - expect($('#page').text()).toBe('at runtime') - expect($('#param-copied-lowcard').text()).toBe('one') - expect($('#param-copied-highcard').text()).toBe('run') - expect($('#param-key-count').text()).toBe('2') - } else { - expect($('#layout').text()).toBe('at runtime') - - expect($('#lowcard').text()).toBe('at runtime') - expect($('#highcard').text()).toBe('at runtime') - expect($('#page').text()).toBe('at runtime') - expect($('#param-copied-lowcard').text()).toBe('one') - expect($('#param-copied-highcard').text()).toBe('run') - expect($('#param-key-count').text()).toBe('2') - } - }) + $ = await next.render$( + '/params/semantics/one/build/async/page-spread/client' + ) + if (isNextDev) { + expect($('#layout').text()).toBe('at runtime') + expect($('#lowcard').text()).toBe('at runtime') + expect($('#highcard').text()).toBe('at runtime') + expect($('#page').text()).toBe('at runtime') + expect($('#param-copied-lowcard').text()).toBe('one') + expect($('#param-copied-highcard').text()).toBe('build') + expect($('#param-key-count').text()).toBe('2') + expect(getLines('Route "/params')).toEqual([]) + } else { + expect($('#layout').text()).toBe('at buildtime') + expect($('#lowcard').text()).toBe('at buildtime') + expect($('#highcard').text()).toBe('at buildtime') + expect($('#page').text()).toBe('at buildtime') + expect($('#param-copied-lowcard').text()).toBe('one') + expect($('#param-copied-highcard').text()).toBe('build') + expect($('#param-key-count').text()).toBe('2') + expect(getLines('Route "/params')).toEqual([]) + } - it('should render pages that spread `use`ed params in a client component when not prebuilt', async () => { - let $ = await next.render$( - '/params/semantics/one/run/async/layout-spread/client' - ) - if (isNextDev) { - expect($('#layout').text()).toBe('at runtime') - expect($('#lowcard').text()).toBe('at runtime') - expect($('#highcard').text()).toBe('at runtime') - expect($('#page').text()).toBe('at runtime') - expect($('#param-copied-lowcard').text()).toBe('one') - expect($('#param-copied-highcard').text()).toBe('run') - expect($('#param-key-count').text()).toBe('2') - } else { - expect($('#layout').text()).toBe('at runtime') - expect($('#lowcard').text()).toBe('at runtime') - expect($('#highcard').text()).toBe('at runtime') - expect($('#page').text()).toBe('at runtime') - expect($('#param-copied-lowcard').text()).toBe('one') - expect($('#param-copied-highcard').text()).toBe('run') - expect($('#param-key-count').text()).toBe('2') - } + $ = await next.render$( + '/params/semantics/one/run/async/layout-spread/client' + ) + if (isNextDev) { + expect($('#layout').text()).toBe('at runtime') + expect($('#lowcard').text()).toBe('at runtime') + expect($('#highcard').text()).toBe('at runtime') + expect($('#page').text()).toBe('at runtime') + expect($('#param-copied-lowcard').text()).toBe('one') + expect($('#param-copied-highcard').text()).toBe('run') + expect($('#param-key-count').text()).toBe('2') + expect(getLines('Route "/params')).toEqual([]) + } else { + expect($('#layout').text()).toBe('at buildtime') + expect($('#lowcard').text()).toBe('at buildtime') + expect($('#highcard').text()).toBe('at buildtime') + expect($('#highcard-fallback').text()).toBe('loading highcard children') + expect($('#page').text()).toBe('at runtime') + expect($('#param-copied-lowcard').text()).toBe('one') + expect($('#param-copied-highcard').text()).toBe('run') + expect($('#param-key-count').text()).toBe('2') + expect(getLines('Route "/params')).toEqual([]) + } - $ = await next.render$( - '/params/semantics/one/run/async/page-spread/client' - ) - if (isNextDev) { - expect($('#layout').text()).toBe('at runtime') - expect($('#lowcard').text()).toBe('at runtime') - expect($('#highcard').text()).toBe('at runtime') - expect($('#page').text()).toBe('at runtime') - expect($('#param-copied-lowcard').text()).toBe('one') - expect($('#param-copied-highcard').text()).toBe('run') - expect($('#param-key-count').text()).toBe('2') - } else { - expect($('#layout').text()).toBe('at runtime') - expect($('#lowcard').text()).toBe('at runtime') - expect($('#highcard').text()).toBe('at runtime') - expect($('#page').text()).toBe('at runtime') - expect($('#param-copied-lowcard').text()).toBe('one') - expect($('#param-copied-highcard').text()).toBe('run') - expect($('#param-key-count').text()).toBe('2') - } - }) - } + $ = await next.render$( + '/params/semantics/one/run/async/page-spread/client' + ) + if (isNextDev) { + expect($('#layout').text()).toBe('at runtime') + expect($('#lowcard').text()).toBe('at runtime') + expect($('#highcard').text()).toBe('at runtime') + expect($('#page').text()).toBe('at runtime') + expect($('#param-copied-lowcard').text()).toBe('one') + expect($('#param-copied-highcard').text()).toBe('run') + expect($('#param-key-count').text()).toBe('2') + expect(getLines('Route "/params')).toEqual([]) + } else { + expect($('#layout').text()).toBe('at buildtime') + expect($('#lowcard').text()).toBe('at buildtime') + expect($('#highcard').text()).toBe('at buildtime') + expect($('#highcard-fallback').text()).toBe('loading highcard children') + expect($('#page').text()).toBe('at runtime') + expect($('#param-copied-lowcard').text()).toBe('one') + expect($('#param-copied-highcard').text()).toBe('run') + expect($('#param-key-count').text()).toBe('2') + expect(getLines('Route "/params')).toEqual([]) + } + }) }) describe('Synchronous Params access', () => { - if (WITH_PPR) { - it('should partially prerender pages that access params synchronously in a server components', async () => { - expect(getLines('Route "/params')).toEqual([]) - - let $ = await next.render$( - '/params/semantics/one/build/sync/layout-access/server' - ) - if (isNextDev) { - expect($('#layout').text()).toBe('at runtime') - expect($('#lowcard').text()).toBe('at runtime') - expect($('#highcard').text()).toBe('at runtime') - expect($('#page').text()).toBe('at runtime') - expect($('#param-lowcard').text()).toBe('one') - expect($('#param-highcard').text()).toBe('build') - expect(getLines('Route "/params')).toEqual([ - expect.stringContaining('`params.lowcard`'), - expect.stringContaining('`params.highcard`'), - ]) - } else { - expect($('#layout').text()).toBe('at buildtime') - expect($('#lowcard').text()).toBe('at buildtime') - expect($('#highcard').text()).toBe('at buildtime') - expect($('#page').text()).toBe('at buildtime') - expect($('#param-lowcard').text()).toBe('one') - expect($('#param-highcard').text()).toBe('build') - } + it('should partially prerender pages that access params synchronously in a server components', async () => { + expect(getLines('Route "/params')).toEqual([]) - $ = await next.render$( - '/params/semantics/one/run/sync/layout-access/server' - ) - if (isNextDev) { - expect($('#layout').text()).toBe('at runtime') - - expect($('#lowcard').text()).toBe('at runtime') - expect($('#highcard').text()).toBe('at runtime') - expect($('#page').text()).toBe('at runtime') - expect($('#param-lowcard').text()).toBe('one') - expect($('#param-highcard').text()).toBe('run') - expect(getLines('Route "/params')).toEqual([ - expect.stringContaining('`params.lowcard`'), - expect.stringContaining('`params.highcard`'), - ]) - } else { - expect($('#layout').text()).toBe('at buildtime') - expect($('#lowcard').text()).toBe('at buildtime') - expect($('#highcard').text()).toBe('at buildtime') - expect($('#highcard-fallback').text()).toBe( - 'loading highcard children' - ) - expect($('#page').text()).toBe('at runtime') - expect($('#param-lowcard').text()).toBe('one') - expect($('#param-highcard').text()).toBe('run') - expect(getLines('Route "/params')).toEqual([]) - } + let $ = await next.render$( + '/params/semantics/one/build/sync/layout-access/server' + ) + if (isNextDev) { + expect($('#layout').text()).toBe('at runtime') + expect($('#lowcard').text()).toBe('at runtime') + expect($('#highcard').text()).toBe('at runtime') + expect($('#page').text()).toBe('at runtime') + expect($('#param-lowcard').text()).toBe('one') + expect($('#param-highcard').text()).toBe('build') + expect(getLines('Route "/params')).toEqual([ + expect.stringContaining('`params.lowcard`'), + expect.stringContaining('`params.highcard`'), + ]) + } else { + expect($('#layout').text()).toBe('at buildtime') + expect($('#lowcard').text()).toBe('at buildtime') + expect($('#highcard').text()).toBe('at buildtime') + expect($('#page').text()).toBe('at buildtime') + expect($('#param-lowcard').text()).toBe('one') + expect($('#param-highcard').text()).toBe('build') + } - $ = await next.render$( - '/params/semantics/one/build/sync/page-access/server' - ) - if (isNextDev) { - expect($('#layout').text()).toBe('at runtime') - expect($('#lowcard').text()).toBe('at runtime') - expect($('#highcard').text()).toBe('at runtime') - expect($('#page').text()).toBe('at runtime') - expect($('#param-lowcard').text()).toBe('one') - expect($('#param-highcard').text()).toBe('build') - expect(getLines('Route "/params')).toEqual([ - expect.stringContaining('`params.lowcard`'), - expect.stringContaining('`params.highcard`'), - ]) - } else { - expect($('#layout').text()).toBe('at buildtime') - expect($('#lowcard').text()).toBe('at buildtime') - expect($('#highcard').text()).toBe('at buildtime') - expect($('#page').text()).toBe('at buildtime') - expect($('#param-lowcard').text()).toBe('one') - expect($('#param-highcard').text()).toBe('build') - expect(getLines('Route "/params')).toEqual([]) - } + $ = await next.render$( + '/params/semantics/one/run/sync/layout-access/server' + ) + if (isNextDev) { + expect($('#layout').text()).toBe('at runtime') - $ = await next.render$( - '/params/semantics/one/run/sync/page-access/server' - ) - if (isNextDev) { - expect($('#layout').text()).toBe('at runtime') - - expect($('#lowcard').text()).toBe('at runtime') - expect($('#highcard').text()).toBe('at runtime') - expect($('#page').text()).toBe('at runtime') - expect($('#param-lowcard').text()).toBe('one') - expect($('#param-highcard').text()).toBe('run') - expect(getLines('Route "/params')).toEqual([ - expect.stringContaining('`params.lowcard`'), - expect.stringContaining('`params.highcard`'), - ]) - } else { - expect($('#layout').text()).toBe('at buildtime') - expect($('#lowcard').text()).toBe('at buildtime') - expect($('#highcard').text()).toBe('at buildtime') - expect($('#highcard-fallback').text()).toBe( - 'loading highcard children' - ) - expect($('#page').text()).toBe('at runtime') - expect($('#param-lowcard').text()).toBe('one') - expect($('#param-highcard').text()).toBe('run') - expect(getLines('Route "/params')).toEqual([]) - } - }) + expect($('#lowcard').text()).toBe('at runtime') + expect($('#highcard').text()).toBe('at runtime') + expect($('#page').text()).toBe('at runtime') + expect($('#param-lowcard').text()).toBe('one') + expect($('#param-highcard').text()).toBe('run') + expect(getLines('Route "/params')).toEqual([ + expect.stringContaining('`params.lowcard`'), + expect.stringContaining('`params.highcard`'), + ]) + } else { + expect($('#layout').text()).toBe('at buildtime') + expect($('#lowcard').text()).toBe('at buildtime') + expect($('#highcard').text()).toBe('at buildtime') + expect($('#highcard-fallback').text()).toBe('loading highcard children') + expect($('#page').text()).toBe('at runtime') + expect($('#param-lowcard').text()).toBe('one') + expect($('#param-highcard').text()).toBe('run') + expect(getLines('Route "/params')).toEqual([]) + } - it('should partially prerender pages that access params synchronously in a client components', async () => { + $ = await next.render$( + '/params/semantics/one/build/sync/page-access/server' + ) + if (isNextDev) { + expect($('#layout').text()).toBe('at runtime') + expect($('#lowcard').text()).toBe('at runtime') + expect($('#highcard').text()).toBe('at runtime') + expect($('#page').text()).toBe('at runtime') + expect($('#param-lowcard').text()).toBe('one') + expect($('#param-highcard').text()).toBe('build') + expect(getLines('Route "/params')).toEqual([ + expect.stringContaining('`params.lowcard`'), + expect.stringContaining('`params.highcard`'), + ]) + } else { + expect($('#layout').text()).toBe('at buildtime') + expect($('#lowcard').text()).toBe('at buildtime') + expect($('#highcard').text()).toBe('at buildtime') + expect($('#page').text()).toBe('at buildtime') + expect($('#param-lowcard').text()).toBe('one') + expect($('#param-highcard').text()).toBe('build') expect(getLines('Route "/params')).toEqual([]) + } - let $ = await next.render$( - '/params/semantics/one/build/sync/layout-access/client' - ) - if (isNextDev) { - expect($('#layout').text()).toBe('at runtime') - expect($('#lowcard').text()).toBe('at runtime') - expect($('#highcard').text()).toBe('at runtime') - expect($('#page').text()).toBe('at runtime') - expect($('#param-lowcard').text()).toBe('one') - expect($('#param-highcard').text()).toBe('build') - expect(getLines('Route "/params')).toEqual([ - expect.stringContaining('`params.lowcard`'), - expect.stringContaining('`params.highcard`'), - ]) - } else { - expect($('#layout').text()).toBe('at buildtime') - expect($('#lowcard').text()).toBe('at buildtime') - expect($('#highcard').text()).toBe('at buildtime') - expect($('#page').text()).toBe('at buildtime') - expect($('#param-lowcard').text()).toBe('one') - expect($('#param-highcard').text()).toBe('build') - expect(getLines('Route "/params')).toEqual([]) - } + $ = await next.render$( + '/params/semantics/one/run/sync/page-access/server' + ) + if (isNextDev) { + expect($('#layout').text()).toBe('at runtime') - $ = await next.render$( - '/params/semantics/one/run/sync/layout-access/client' - ) - if (isNextDev) { - expect($('#layout').text()).toBe('at runtime') - - expect($('#lowcard').text()).toBe('at runtime') - expect($('#highcard').text()).toBe('at runtime') - expect($('#page').text()).toBe('at runtime') - expect($('#param-lowcard').text()).toBe('one') - expect($('#param-highcard').text()).toBe('run') - expect(getLines('Route "/params')).toEqual([ - expect.stringContaining('`params.lowcard`'), - expect.stringContaining('`params.highcard`'), - ]) - } else { - expect($('#layout').text()).toBe('at buildtime') - expect($('#lowcard').text()).toBe('at buildtime') - expect($('#highcard').text()).toBe('at buildtime') - expect($('#highcard-fallback').text()).toBe( - 'loading highcard children' - ) - expect($('#page').text()).toBe('at runtime') - expect($('#param-lowcard').text()).toBe('one') - expect($('#param-highcard').text()).toBe('run') - expect(getLines('Route "/params')).toEqual([]) - } + expect($('#lowcard').text()).toBe('at runtime') + expect($('#highcard').text()).toBe('at runtime') + expect($('#page').text()).toBe('at runtime') + expect($('#param-lowcard').text()).toBe('one') + expect($('#param-highcard').text()).toBe('run') + expect(getLines('Route "/params')).toEqual([ + expect.stringContaining('`params.lowcard`'), + expect.stringContaining('`params.highcard`'), + ]) + } else { + expect($('#layout').text()).toBe('at buildtime') + expect($('#lowcard').text()).toBe('at buildtime') + expect($('#highcard').text()).toBe('at buildtime') + expect($('#highcard-fallback').text()).toBe('loading highcard children') + expect($('#page').text()).toBe('at runtime') + expect($('#param-lowcard').text()).toBe('one') + expect($('#param-highcard').text()).toBe('run') + expect(getLines('Route "/params')).toEqual([]) + } + }) - $ = await next.render$( - '/params/semantics/one/build/sync/page-access/client' - ) - if (isNextDev) { - expect($('#layout').text()).toBe('at runtime') - - expect($('#lowcard').text()).toBe('at runtime') - expect($('#highcard').text()).toBe('at runtime') - expect($('#page').text()).toBe('at runtime') - expect($('#param-lowcard').text()).toBe('one') - expect($('#param-highcard').text()).toBe('build') - expect(getLines('Route "/params')).toEqual([ - expect.stringContaining('`params.lowcard`'), - expect.stringContaining('`params.highcard`'), - ]) - } else { - expect($('#layout').text()).toBe('at buildtime') - expect($('#lowcard').text()).toBe('at buildtime') - expect($('#highcard').text()).toBe('at buildtime') - expect($('#page').text()).toBe('at buildtime') - expect($('#param-lowcard').text()).toBe('one') - expect($('#param-highcard').text()).toBe('build') - expect(getLines('Route "/params')).toEqual([]) - } + it('should partially prerender pages that access params synchronously in a client components', async () => { + expect(getLines('Route "/params')).toEqual([]) - $ = await next.render$( - '/params/semantics/one/run/sync/page-access/client' - ) - if (isNextDev) { - expect($('#layout').text()).toBe('at runtime') - - expect($('#lowcard').text()).toBe('at runtime') - expect($('#highcard').text()).toBe('at runtime') - expect($('#page').text()).toBe('at runtime') - expect($('#param-lowcard').text()).toBe('one') - expect($('#param-highcard').text()).toBe('run') - expect(getLines('Route "/params')).toEqual([ - expect.stringContaining('`params.lowcard`'), - expect.stringContaining('`params.highcard`'), - ]) - } else { - expect($('#layout').text()).toBe('at buildtime') - expect($('#lowcard').text()).toBe('at buildtime') - expect($('#highcard').text()).toBe('at buildtime') - expect($('#highcard-fallback').text()).toBe( - 'loading highcard children' - ) - expect($('#page').text()).toBe('at runtime') - expect($('#param-lowcard').text()).toBe('one') - expect($('#param-highcard').text()).toBe('run') - expect(getLines('Route "/params')).toEqual([]) - } - }) - } else { - it('should prerender pages that access params synchronously in a server component when prebuilt', async () => { + let $ = await next.render$( + '/params/semantics/one/build/sync/layout-access/client' + ) + if (isNextDev) { + expect($('#layout').text()).toBe('at runtime') + expect($('#lowcard').text()).toBe('at runtime') + expect($('#highcard').text()).toBe('at runtime') + expect($('#page').text()).toBe('at runtime') + expect($('#param-lowcard').text()).toBe('one') + expect($('#param-highcard').text()).toBe('build') + expect(getLines('Route "/params')).toEqual([ + expect.stringContaining('`params.lowcard`'), + expect.stringContaining('`params.highcard`'), + ]) + } else { + expect($('#layout').text()).toBe('at buildtime') + expect($('#lowcard').text()).toBe('at buildtime') + expect($('#highcard').text()).toBe('at buildtime') + expect($('#page').text()).toBe('at buildtime') + expect($('#param-lowcard').text()).toBe('one') + expect($('#param-highcard').text()).toBe('build') expect(getLines('Route "/params')).toEqual([]) - let $ = await next.render$( - '/params/semantics/one/build/sync/layout-access/server' - ) - if (isNextDev) { - expect($('#layout').text()).toBe('at runtime') - expect($('#lowcard').text()).toBe('at runtime') - expect($('#highcard').text()).toBe('at runtime') - expect($('#page').text()).toBe('at runtime') - expect($('#param-lowcard').text()).toBe('one') - expect($('#param-highcard').text()).toBe('build') - expect(getLines('Route "/params')).toEqual([ - expect.stringContaining('`params.lowcard`'), - expect.stringContaining('`params.highcard`'), - ]) - } else { - expect($('#layout').text()).toBe('at buildtime') - expect($('#lowcard').text()).toBe('at buildtime') - expect($('#highcard').text()).toBe('at buildtime') - expect($('#page').text()).toBe('at buildtime') - expect($('#param-lowcard').text()).toBe('one') - expect($('#param-highcard').text()).toBe('build') - expect(getLines('Route "/params')).toEqual([]) - } + } - $ = await next.render$( - '/params/semantics/one/build/sync/page-access/server' - ) - if (isNextDev) { - expect($('#layout').text()).toBe('at runtime') - expect($('#lowcard').text()).toBe('at runtime') - expect($('#highcard').text()).toBe('at runtime') - expect($('#page').text()).toBe('at runtime') - expect($('#param-lowcard').text()).toBe('one') - expect($('#param-highcard').text()).toBe('build') - expect(getLines('Route "/params')).toEqual([ - expect.stringContaining('`params.lowcard`'), - expect.stringContaining('`params.highcard`'), - ]) - } else { - expect($('#layout').text()).toBe('at buildtime') - expect($('#lowcard').text()).toBe('at buildtime') - expect($('#highcard').text()).toBe('at buildtime') - expect($('#page').text()).toBe('at buildtime') - expect($('#param-lowcard').text()).toBe('one') - expect($('#param-highcard').text()).toBe('build') - expect(getLines('Route "/params')).toEqual([]) - } - }) + $ = await next.render$( + '/params/semantics/one/run/sync/layout-access/client' + ) + if (isNextDev) { + expect($('#layout').text()).toBe('at runtime') - it('should prerender pages that access params synchronously in a client component when prebuilt', async () => { + expect($('#lowcard').text()).toBe('at runtime') + expect($('#highcard').text()).toBe('at runtime') + expect($('#page').text()).toBe('at runtime') + expect($('#param-lowcard').text()).toBe('one') + expect($('#param-highcard').text()).toBe('run') + expect(getLines('Route "/params')).toEqual([ + expect.stringContaining('`params.lowcard`'), + expect.stringContaining('`params.highcard`'), + ]) + } else { + expect($('#layout').text()).toBe('at buildtime') + expect($('#lowcard').text()).toBe('at buildtime') + expect($('#highcard').text()).toBe('at buildtime') + expect($('#highcard-fallback').text()).toBe('loading highcard children') + expect($('#page').text()).toBe('at runtime') + expect($('#param-lowcard').text()).toBe('one') + expect($('#param-highcard').text()).toBe('run') expect(getLines('Route "/params')).toEqual([]) - let $ = await next.render$( - '/params/semantics/one/build/sync/layout-access/client' - ) - if (isNextDev) { - expect($('#layout').text()).toBe('at runtime') - expect($('#lowcard').text()).toBe('at runtime') - expect($('#highcard').text()).toBe('at runtime') - expect($('#page').text()).toBe('at runtime') - expect($('#param-lowcard').text()).toBe('one') - expect($('#param-highcard').text()).toBe('build') - expect(getLines('Route "/params')).toEqual([ - expect.stringContaining('`params.lowcard`'), - expect.stringContaining('`params.highcard`'), - ]) - } else { - expect($('#layout').text()).toBe('at buildtime') - expect($('#lowcard').text()).toBe('at buildtime') - expect($('#highcard').text()).toBe('at buildtime') - expect($('#page').text()).toBe('at buildtime') - expect($('#param-lowcard').text()).toBe('one') - expect($('#param-highcard').text()).toBe('build') - expect(getLines('Route "/params')).toEqual([]) - } + } - $ = await next.render$( - '/params/semantics/one/build/sync/page-access/client' - ) - if (isNextDev) { - expect($('#layout').text()).toBe('at runtime') - expect($('#lowcard').text()).toBe('at runtime') - expect($('#highcard').text()).toBe('at runtime') - expect($('#page').text()).toBe('at runtime') - expect($('#param-lowcard').text()).toBe('one') - expect($('#param-highcard').text()).toBe('build') - expect(getLines('Route "/params')).toEqual([ - expect.stringContaining('`params.lowcard`'), - expect.stringContaining('`params.highcard`'), - ]) - } else { - // TODO at the moment pages receive searchParams which are not know at build time - // and always dynamic. We have to pessimistically assume they are accessed and thus - // we cannot actually produce a static shell without PPR. - expect($('#layout').text()).toBe('at runtime') - expect($('#lowcard').text()).toBe('at runtime') - expect($('#highcard').text()).toBe('at runtime') - expect($('#page').text()).toBe('at runtime') - expect($('#param-lowcard').text()).toBe('one') - expect($('#param-highcard').text()).toBe('build') - expect(getLines('Route "/params')).toEqual([]) - } - }) + $ = await next.render$( + '/params/semantics/one/build/sync/page-access/client' + ) + if (isNextDev) { + expect($('#layout').text()).toBe('at runtime') - it('should render pages that access params synchronously in a server component when not prebuilt', async () => { + expect($('#lowcard').text()).toBe('at runtime') + expect($('#highcard').text()).toBe('at runtime') + expect($('#page').text()).toBe('at runtime') + expect($('#param-lowcard').text()).toBe('one') + expect($('#param-highcard').text()).toBe('build') + expect(getLines('Route "/params')).toEqual([ + expect.stringContaining('`params.lowcard`'), + expect.stringContaining('`params.highcard`'), + ]) + } else { + expect($('#layout').text()).toBe('at buildtime') + expect($('#lowcard').text()).toBe('at buildtime') + expect($('#highcard').text()).toBe('at buildtime') + expect($('#page').text()).toBe('at buildtime') + expect($('#param-lowcard').text()).toBe('one') + expect($('#param-highcard').text()).toBe('build') expect(getLines('Route "/params')).toEqual([]) - let $ = await next.render$( - '/params/semantics/one/run/sync/layout-access/server' - ) - if (isNextDev) { - expect($('#layout').text()).toBe('at runtime') - expect($('#lowcard').text()).toBe('at runtime') - expect($('#highcard').text()).toBe('at runtime') - expect($('#page').text()).toBe('at runtime') - expect($('#param-lowcard').text()).toBe('one') - expect($('#param-highcard').text()).toBe('run') - expect(getLines('Route "/params')).toEqual([ - expect.stringContaining('`params.lowcard`'), - expect.stringContaining('`params.highcard`'), - ]) - } else { - expect($('#layout').text()).toBe('at runtime') - expect($('#lowcard').text()).toBe('at runtime') - expect($('#highcard').text()).toBe('at runtime') - expect($('#page').text()).toBe('at runtime') - expect($('#param-lowcard').text()).toBe('one') - expect($('#param-highcard').text()).toBe('run') - expect(getLines('Route "/params')).toEqual([]) - } + } - $ = await next.render$( - '/params/semantics/one/run/sync/page-access/server' - ) - if (isNextDev) { - expect($('#layout').text()).toBe('at runtime') - expect($('#lowcard').text()).toBe('at runtime') - expect($('#highcard').text()).toBe('at runtime') - expect($('#page').text()).toBe('at runtime') - expect($('#param-lowcard').text()).toBe('one') - expect($('#param-highcard').text()).toBe('run') - expect(getLines('Route "/params')).toEqual([ - expect.stringContaining('`params.lowcard`'), - expect.stringContaining('`params.highcard`'), - ]) - } else { - expect($('#layout').text()).toBe('at runtime') - expect($('#lowcard').text()).toBe('at runtime') - expect($('#highcard').text()).toBe('at runtime') - expect($('#page').text()).toBe('at runtime') - expect($('#param-lowcard').text()).toBe('one') - expect($('#param-highcard').text()).toBe('run') - expect(getLines('Route "/params')).toEqual([]) - } - }) + $ = await next.render$( + '/params/semantics/one/run/sync/page-access/client' + ) + if (isNextDev) { + expect($('#layout').text()).toBe('at runtime') - it('should render pages that access params synchronously in a client component when not prebuilt', async () => { + expect($('#lowcard').text()).toBe('at runtime') + expect($('#highcard').text()).toBe('at runtime') + expect($('#page').text()).toBe('at runtime') + expect($('#param-lowcard').text()).toBe('one') + expect($('#param-highcard').text()).toBe('run') + expect(getLines('Route "/params')).toEqual([ + expect.stringContaining('`params.lowcard`'), + expect.stringContaining('`params.highcard`'), + ]) + } else { + expect($('#layout').text()).toBe('at buildtime') + expect($('#lowcard').text()).toBe('at buildtime') + expect($('#highcard').text()).toBe('at buildtime') + expect($('#highcard-fallback').text()).toBe('loading highcard children') + expect($('#page').text()).toBe('at runtime') + expect($('#param-lowcard').text()).toBe('one') + expect($('#param-highcard').text()).toBe('run') expect(getLines('Route "/params')).toEqual([]) - let $ = await next.render$( - '/params/semantics/one/run/sync/layout-access/client' - ) - if (isNextDev) { - expect($('#layout').text()).toBe('at runtime') - expect($('#lowcard').text()).toBe('at runtime') - expect($('#highcard').text()).toBe('at runtime') - expect($('#page').text()).toBe('at runtime') - expect($('#param-lowcard').text()).toBe('one') - expect($('#param-highcard').text()).toBe('run') - expect(getLines('Route "/params')).toEqual([ - expect.stringContaining('`params.lowcard`'), - expect.stringContaining('`params.highcard`'), - ]) - } else { - expect($('#layout').text()).toBe('at runtime') - expect($('#lowcard').text()).toBe('at runtime') - expect($('#highcard').text()).toBe('at runtime') - expect($('#page').text()).toBe('at runtime') - expect($('#param-lowcard').text()).toBe('one') - expect($('#param-highcard').text()).toBe('run') - expect(getLines('Route "/params')).toEqual([]) - } - - $ = await next.render$( - '/params/semantics/one/run/sync/page-access/client' - ) - if (isNextDev) { - expect($('#layout').text()).toBe('at runtime') - expect($('#lowcard').text()).toBe('at runtime') - expect($('#highcard').text()).toBe('at runtime') - expect($('#page').text()).toBe('at runtime') - expect($('#param-lowcard').text()).toBe('one') - expect($('#param-highcard').text()).toBe('run') - expect(getLines('Route "/params')).toEqual([ - expect.stringContaining('`params.lowcard`'), - expect.stringContaining('`params.highcard`'), - ]) - } else { - expect($('#layout').text()).toBe('at runtime') - expect($('#lowcard').text()).toBe('at runtime') - expect($('#highcard').text()).toBe('at runtime') - expect($('#page').text()).toBe('at runtime') - expect($('#param-lowcard').text()).toBe('one') - expect($('#param-highcard').text()).toBe('run') - expect(getLines('Route "/params')).toEqual([]) - } - }) - } + } + }) it('should fully prerender pages that check individual param keys directly on the params prop in a server component', async () => { expect(getLines('Route "/params')).toEqual([]) @@ -1556,28 +865,16 @@ describe('dynamic-io', () => { expect($('#param-has-foo').text()).toBe('false') expect(getLines('Route "/params')).toEqual([]) } else { - if (WITH_PPR) { - // With PPR fallbacks the first visit is fully prerendered - // because has-checking doesn't postpone even with ppr fallbacks - expect($('#layout').text()).toBe('at buildtime') - expect($('#lowcard').text()).toBe('at buildtime') - expect($('#highcard').text()).toBe('at buildtime') - expect($('#page').text()).toBe('at buildtime') - expect($('#param-has-lowcard').text()).toBe('true') - expect($('#param-has-highcard').text()).toBe('true') - expect($('#param-has-foo').text()).toBe('false') - expect(getLines('Route "/params')).toEqual([]) - } else { - // without PPR the first visit is dynamic - expect($('#layout').text()).toBe('at runtime') - expect($('#lowcard').text()).toBe('at runtime') - expect($('#highcard').text()).toBe('at runtime') - expect($('#page').text()).toBe('at runtime') - expect($('#param-has-lowcard').text()).toBe('true') - expect($('#param-has-highcard').text()).toBe('true') - expect($('#param-has-foo').text()).toBe('false') - expect(getLines('Route "/params')).toEqual([]) - } + // With PPR fallbacks the first visit is fully prerendered + // because has-checking doesn't postpone even with ppr fallbacks + expect($('#layout').text()).toBe('at buildtime') + expect($('#lowcard').text()).toBe('at buildtime') + expect($('#highcard').text()).toBe('at buildtime') + expect($('#page').text()).toBe('at buildtime') + expect($('#param-has-lowcard').text()).toBe('true') + expect($('#param-has-highcard').text()).toBe('true') + expect($('#param-has-foo').text()).toBe('false') + expect(getLines('Route "/params')).toEqual([]) } $ = await next.render$('/params/semantics/one/run/sync/page-has/server') @@ -1591,28 +888,16 @@ describe('dynamic-io', () => { expect($('#param-has-foo').text()).toBe('false') expect(getLines('Route "/params')).toEqual([]) } else { - if (WITH_PPR) { - // With PPR fallbacks the first visit is fully prerendered - // because has-checking doesn't postpone even with ppr fallbacks - expect($('#layout').text()).toBe('at buildtime') - expect($('#lowcard').text()).toBe('at buildtime') - expect($('#highcard').text()).toBe('at buildtime') - expect($('#page').text()).toBe('at buildtime') - expect($('#param-has-lowcard').text()).toBe('true') - expect($('#param-has-highcard').text()).toBe('true') - expect($('#param-has-foo').text()).toBe('false') - expect(getLines('Route "/params')).toEqual([]) - } else { - // without PPR the first visit is dynamic - expect($('#layout').text()).toBe('at runtime') - expect($('#lowcard').text()).toBe('at runtime') - expect($('#highcard').text()).toBe('at runtime') - expect($('#page').text()).toBe('at runtime') - expect($('#param-has-lowcard').text()).toBe('true') - expect($('#param-has-highcard').text()).toBe('true') - expect($('#param-has-foo').text()).toBe('false') - expect(getLines('Route "/params')).toEqual([]) - } + // With PPR fallbacks the first visit is fully prerendered + // because has-checking doesn't postpone even with ppr fallbacks + expect($('#layout').text()).toBe('at buildtime') + expect($('#lowcard').text()).toBe('at buildtime') + expect($('#highcard').text()).toBe('at buildtime') + expect($('#page').text()).toBe('at buildtime') + expect($('#param-has-lowcard').text()).toBe('true') + expect($('#param-has-highcard').text()).toBe('true') + expect($('#param-has-foo').text()).toBe('false') + expect(getLines('Route "/params')).toEqual([]) } }) @@ -1652,31 +937,14 @@ describe('dynamic-io', () => { expect($('#param-has-foo').text()).toBe('false') expect(getLines('Route "/params')).toEqual([]) } else { - if (WITH_PPR) { - expect($('#layout').text()).toBe('at buildtime') - expect($('#lowcard').text()).toBe('at buildtime') - expect($('#highcard').text()).toBe('at buildtime') - expect($('#page').text()).toBe('at buildtime') - expect($('#param-has-lowcard').text()).toBe('true') - expect($('#param-has-highcard').text()).toBe('true') - expect($('#param-has-foo').text()).toBe('false') - expect(getLines('Route "/params')).toEqual([]) - } else { - // When dynamicIO is on and PPR is off the search params passed to a client page - // are enough to mark the whole route as dynamic. This is because we can't know if - // you are going to use those searchParams in an update on the client so we can't infer - // anything about your lack of use during SSR. In the future we will update searchParams - // written to the client to actually derive those params from location and thus not - // require dynamic - expect($('#layout').text()).toBe('at runtime') - expect($('#lowcard').text()).toBe('at runtime') - expect($('#highcard').text()).toBe('at runtime') - expect($('#page').text()).toBe('at runtime') - expect($('#param-has-lowcard').text()).toBe('true') - expect($('#param-has-highcard').text()).toBe('true') - expect($('#param-has-foo').text()).toBe('false') - expect(getLines('Route "/params')).toEqual([]) - } + expect($('#layout').text()).toBe('at buildtime') + expect($('#lowcard').text()).toBe('at buildtime') + expect($('#highcard').text()).toBe('at buildtime') + expect($('#page').text()).toBe('at buildtime') + expect($('#param-has-lowcard').text()).toBe('true') + expect($('#param-has-highcard').text()).toBe('true') + expect($('#param-has-foo').text()).toBe('false') + expect(getLines('Route "/params')).toEqual([]) } $ = await next.render$('/params/semantics/one/run/sync/layout-has/client') @@ -1690,28 +958,16 @@ describe('dynamic-io', () => { expect($('#param-has-foo').text()).toBe('false') expect(getLines('Route "/params')).toEqual([]) } else { - if (WITH_PPR) { - // With PPR fallbacks the first visit is fully prerendered - // because has-checking doesn't postpone even with ppr fallbacks - expect($('#layout').text()).toBe('at buildtime') - expect($('#lowcard').text()).toBe('at buildtime') - expect($('#highcard').text()).toBe('at buildtime') - expect($('#page').text()).toBe('at buildtime') - expect($('#param-has-lowcard').text()).toBe('true') - expect($('#param-has-highcard').text()).toBe('true') - expect($('#param-has-foo').text()).toBe('false') - expect(getLines('Route "/params')).toEqual([]) - } else { - // without PPR the first visit is dynamic - expect($('#layout').text()).toBe('at runtime') - expect($('#lowcard').text()).toBe('at runtime') - expect($('#highcard').text()).toBe('at runtime') - expect($('#page').text()).toBe('at runtime') - expect($('#param-has-lowcard').text()).toBe('true') - expect($('#param-has-highcard').text()).toBe('true') - expect($('#param-has-foo').text()).toBe('false') - expect(getLines('Route "/params')).toEqual([]) - } + // With PPR fallbacks the first visit is fully prerendered + // because has-checking doesn't postpone even with ppr fallbacks + expect($('#layout').text()).toBe('at buildtime') + expect($('#lowcard').text()).toBe('at buildtime') + expect($('#highcard').text()).toBe('at buildtime') + expect($('#page').text()).toBe('at buildtime') + expect($('#param-has-lowcard').text()).toBe('true') + expect($('#param-has-highcard').text()).toBe('true') + expect($('#param-has-foo').text()).toBe('false') + expect(getLines('Route "/params')).toEqual([]) } $ = await next.render$('/params/semantics/one/run/sync/page-has/client') @@ -1725,491 +981,238 @@ describe('dynamic-io', () => { expect($('#param-has-foo').text()).toBe('false') expect(getLines('Route "/params')).toEqual([]) } else { - if (WITH_PPR) { - // With PPR fallbacks the first visit is still fully prerendered - // has-checking keys isn't dynamic and since we aren't awaiting the - // whole params object we end up with a complete prerender even - // for the fallback page. - expect($('#layout').text()).toBe('at buildtime') - expect($('#lowcard').text()).toBe('at buildtime') - expect($('#highcard').text()).toBe('at buildtime') - expect($('#page').text()).toBe('at buildtime') - expect($('#param-has-lowcard').text()).toBe('true') - expect($('#param-has-highcard').text()).toBe('true') - expect($('#param-has-foo').text()).toBe('false') - expect(getLines('Route "/params')).toEqual([]) - } else { - // When dynamicIO is on and PPR is off the search params passed to a client page - // are enough to mark the whole route as dynamic. This is because we can't know if - // you are going to use those searchParams in an update on the client so we can't infer - // anything about your lack of use during SSR. In the future we will update searchParams - // written to the client to actually derive those params from location and thus not - // require dynamic - expect($('#layout').text()).toBe('at runtime') - expect($('#lowcard').text()).toBe('at runtime') - expect($('#highcard').text()).toBe('at runtime') - expect($('#page').text()).toBe('at runtime') - expect($('#param-has-lowcard').text()).toBe('true') - expect($('#param-has-highcard').text()).toBe('true') - expect($('#param-has-foo').text()).toBe('false') - expect(getLines('Route "/params')).toEqual([]) - } + // With PPR fallbacks the first visit is still fully prerendered + // has-checking keys isn't dynamic and since we aren't awaiting the + // whole params object we end up with a complete prerender even + // for the fallback page. + expect($('#layout').text()).toBe('at buildtime') + expect($('#lowcard').text()).toBe('at buildtime') + expect($('#highcard').text()).toBe('at buildtime') + expect($('#page').text()).toBe('at buildtime') + expect($('#param-has-lowcard').text()).toBe('true') + expect($('#param-has-highcard').text()).toBe('true') + expect($('#param-has-foo').text()).toBe('false') + expect(getLines('Route "/params')).toEqual([]) } }) - if (WITH_PPR) { - it('should partially prerender pages that spread params without awaiting first in a server component', async () => { + it('should partially prerender pages that spread params without awaiting first in a server component', async () => { + expect(getLines('Route "/params')).toEqual([]) + let $ = await next.render$( + '/params/semantics/one/build/sync/layout-spread/server' + ) + if (isNextDev) { + expect($('#layout').text()).toBe('at runtime') + expect($('#lowcard').text()).toBe('at runtime') + expect($('#highcard').text()).toBe('at runtime') + expect($('#page').text()).toBe('at runtime') + expect($('#param-copied-lowcard').text()).toBe('one') + expect($('#param-copied-highcard').text()).toBe('build') + expect($('#param-key-count').text()).toBe('2') + expect(getLines('Route "/params')).toEqual([ + expect.stringContaining('`...params` or similar expression'), + ]) + } else { + expect($('#layout').text()).toBe('at buildtime') + expect($('#lowcard').text()).toBe('at buildtime') + expect($('#highcard').text()).toBe('at buildtime') + expect($('#page').text()).toBe('at buildtime') + expect($('#param-copied-lowcard').text()).toBe('one') + expect($('#param-copied-highcard').text()).toBe('build') + expect($('#param-key-count').text()).toBe('2') expect(getLines('Route "/params')).toEqual([]) - let $ = await next.render$( - '/params/semantics/one/build/sync/layout-spread/server' - ) - if (isNextDev) { - expect($('#layout').text()).toBe('at runtime') - expect($('#lowcard').text()).toBe('at runtime') - expect($('#highcard').text()).toBe('at runtime') - expect($('#page').text()).toBe('at runtime') - expect($('#param-copied-lowcard').text()).toBe('one') - expect($('#param-copied-highcard').text()).toBe('build') - expect($('#param-key-count').text()).toBe('2') - expect(getLines('Route "/params')).toEqual([ - expect.stringContaining('`...params` or similar expression'), - ]) - } else { - expect($('#layout').text()).toBe('at buildtime') - expect($('#lowcard').text()).toBe('at buildtime') - expect($('#highcard').text()).toBe('at buildtime') - expect($('#page').text()).toBe('at buildtime') - expect($('#param-copied-lowcard').text()).toBe('one') - expect($('#param-copied-highcard').text()).toBe('build') - expect($('#param-key-count').text()).toBe('2') - expect(getLines('Route "/params')).toEqual([]) - } - - $ = await next.render$( - '/params/semantics/one/build/sync/page-spread/server' - ) - if (isNextDev) { - expect($('#layout').text()).toBe('at runtime') - expect($('#lowcard').text()).toBe('at runtime') - expect($('#highcard').text()).toBe('at runtime') - expect($('#page').text()).toBe('at runtime') - expect($('#param-copied-lowcard').text()).toBe('one') - expect($('#param-copied-highcard').text()).toBe('build') - expect($('#param-key-count').text()).toBe('2') - expect(getLines('Route "/params')).toEqual([ - expect.stringContaining('`...params` or similar expression'), - ]) - } else { - expect($('#layout').text()).toBe('at buildtime') - expect($('#lowcard').text()).toBe('at buildtime') - expect($('#highcard').text()).toBe('at buildtime') - expect($('#page').text()).toBe('at buildtime') - expect($('#param-copied-lowcard').text()).toBe('one') - expect($('#param-copied-highcard').text()).toBe('build') - expect($('#param-key-count').text()).toBe('2') - expect(getLines('Route "/params')).toEqual([]) - } - - $ = await next.render$( - '/params/semantics/one/run/sync/layout-spread/server' - ) - if (isNextDev) { - expect($('#layout').text()).toBe('at runtime') - expect($('#lowcard').text()).toBe('at runtime') - expect($('#highcard').text()).toBe('at runtime') - expect($('#page').text()).toBe('at runtime') - expect($('#param-copied-lowcard').text()).toBe('one') - expect($('#param-copied-highcard').text()).toBe('run') - expect($('#param-key-count').text()).toBe('2') - expect(getLines('Route "/params')).toEqual([ - expect.stringContaining('`...params` or similar expression'), - ]) - } else { - expect($('#layout').text()).toBe('at buildtime') - expect($('#lowcard').text()).toBe('at buildtime') - expect($('#highcard').text()).toBe('at buildtime') - expect($('#highcard-fallback').text()).toBe( - 'loading highcard children' - ) - expect($('#page').text()).toBe('at runtime') - expect($('#param-copied-lowcard').text()).toBe('one') - expect($('#param-copied-highcard').text()).toBe('run') - expect($('#param-key-count').text()).toBe('2') - expect(getLines('Route "/params')).toEqual([]) - } - - $ = await next.render$( - '/params/semantics/one/run/sync/page-spread/server' - ) - if (isNextDev) { - expect($('#layout').text()).toBe('at runtime') - expect($('#lowcard').text()).toBe('at runtime') - expect($('#highcard').text()).toBe('at runtime') - expect($('#page').text()).toBe('at runtime') - expect($('#param-copied-lowcard').text()).toBe('one') - expect($('#param-copied-highcard').text()).toBe('run') - expect($('#param-key-count').text()).toBe('2') - expect(getLines('Route "/params')).toEqual([ - expect.stringContaining('`...params` or similar expression'), - ]) - } else { - expect($('#layout').text()).toBe('at buildtime') - expect($('#lowcard').text()).toBe('at buildtime') - expect($('#highcard').text()).toBe('at buildtime') - expect($('#highcard-fallback').text()).toBe( - 'loading highcard children' - ) - expect($('#page').text()).toBe('at runtime') - expect($('#param-copied-lowcard').text()).toBe('one') - expect($('#param-copied-highcard').text()).toBe('run') - expect($('#param-key-count').text()).toBe('2') - expect(getLines('Route "/params')).toEqual([]) - } - }) + } - it('should partially prerender pages that spread params without `use`ing them first in a client component', async () => { + $ = await next.render$( + '/params/semantics/one/build/sync/page-spread/server' + ) + if (isNextDev) { + expect($('#layout').text()).toBe('at runtime') + expect($('#lowcard').text()).toBe('at runtime') + expect($('#highcard').text()).toBe('at runtime') + expect($('#page').text()).toBe('at runtime') + expect($('#param-copied-lowcard').text()).toBe('one') + expect($('#param-copied-highcard').text()).toBe('build') + expect($('#param-key-count').text()).toBe('2') + expect(getLines('Route "/params')).toEqual([ + expect.stringContaining('`...params` or similar expression'), + ]) + } else { + expect($('#layout').text()).toBe('at buildtime') + expect($('#lowcard').text()).toBe('at buildtime') + expect($('#highcard').text()).toBe('at buildtime') + expect($('#page').text()).toBe('at buildtime') + expect($('#param-copied-lowcard').text()).toBe('one') + expect($('#param-copied-highcard').text()).toBe('build') + expect($('#param-key-count').text()).toBe('2') expect(getLines('Route "/params')).toEqual([]) - let $ = await next.render$( - '/params/semantics/one/build/sync/layout-spread/client' - ) - if (isNextDev) { - expect($('#layout').text()).toBe('at runtime') - expect($('#lowcard').text()).toBe('at runtime') - expect($('#highcard').text()).toBe('at runtime') - expect($('#page').text()).toBe('at runtime') - expect($('#param-copied-lowcard').text()).toBe('one') - expect($('#param-copied-highcard').text()).toBe('build') - expect($('#param-key-count').text()).toBe('2') - expect(getLines('Route "/params')).toEqual([ - expect.stringContaining('`...params` or similar expression'), - expect.stringContaining('`params.lowcard`'), - expect.stringContaining('`params.highcard`'), - ]) - } else { - expect($('#layout').text()).toBe('at buildtime') - expect($('#lowcard').text()).toBe('at buildtime') - expect($('#highcard').text()).toBe('at buildtime') - expect($('#page').text()).toBe('at buildtime') - expect($('#param-copied-lowcard').text()).toBe('one') - expect($('#param-copied-highcard').text()).toBe('build') - expect($('#param-key-count').text()).toBe('2') - expect(getLines('Route "/params')).toEqual([]) - } - - $ = await next.render$( - '/params/semantics/one/build/sync/page-spread/client' - ) - if (isNextDev) { - expect($('#layout').text()).toBe('at runtime') - expect($('#lowcard').text()).toBe('at runtime') - expect($('#highcard').text()).toBe('at runtime') - expect($('#page').text()).toBe('at runtime') - expect($('#param-copied-lowcard').text()).toBe('one') - expect($('#param-copied-highcard').text()).toBe('build') - expect($('#param-key-count').text()).toBe('2') - expect(getLines('Route "/params')).toEqual([ - expect.stringContaining('`...params` or similar expression'), - expect.stringContaining('`params.lowcard`'), - expect.stringContaining('`params.highcard`'), - ]) - } else { - expect($('#layout').text()).toBe('at buildtime') - expect($('#lowcard').text()).toBe('at buildtime') - expect($('#highcard').text()).toBe('at buildtime') - expect($('#page').text()).toBe('at buildtime') - expect($('#param-copied-lowcard').text()).toBe('one') - expect($('#param-copied-highcard').text()).toBe('build') - expect($('#param-key-count').text()).toBe('2') - expect(getLines('Route "/params')).toEqual([]) - } - - $ = await next.render$( - '/params/semantics/one/run/sync/layout-spread/client' - ) - if (isNextDev) { - expect($('#layout').text()).toBe('at runtime') - expect($('#lowcard').text()).toBe('at runtime') - expect($('#highcard').text()).toBe('at runtime') - expect($('#page').text()).toBe('at runtime') - expect($('#param-copied-lowcard').text()).toBe('one') - expect($('#param-copied-highcard').text()).toBe('run') - expect($('#param-key-count').text()).toBe('2') - expect(getLines('Route "/params')).toEqual([ - expect.stringContaining('`...params` or similar expression'), - expect.stringContaining('`params.lowcard`'), - expect.stringContaining('`params.highcard`'), - ]) - } else { - expect($('#layout').text()).toBe('at buildtime') - expect($('#lowcard').text()).toBe('at buildtime') - expect($('#highcard').text()).toBe('at buildtime') - expect($('#highcard-fallback').text()).toBe( - 'loading highcard children' - ) - expect($('#page').text()).toBe('at runtime') - expect($('#param-copied-lowcard').text()).toBe('one') - expect($('#param-copied-highcard').text()).toBe('run') - expect($('#param-key-count').text()).toBe('2') - expect(getLines('Route "/params')).toEqual([]) - } + } - $ = await next.render$( - '/params/semantics/one/run/sync/page-spread/client' - ) - if (isNextDev) { - expect($('#layout').text()).toBe('at runtime') - expect($('#lowcard').text()).toBe('at runtime') - expect($('#highcard').text()).toBe('at runtime') - expect($('#page').text()).toBe('at runtime') - expect($('#param-copied-lowcard').text()).toBe('one') - expect($('#param-copied-highcard').text()).toBe('run') - expect($('#param-key-count').text()).toBe('2') - expect(getLines('Route "/params')).toEqual([ - expect.stringContaining('`...params` or similar expression'), - expect.stringContaining('`params.lowcard`'), - expect.stringContaining('`params.highcard`'), - ]) - } else { - expect($('#layout').text()).toBe('at buildtime') - expect($('#lowcard').text()).toBe('at buildtime') - expect($('#highcard').text()).toBe('at buildtime') - expect($('#highcard-fallback').text()).toBe( - 'loading highcard children' - ) - expect($('#page').text()).toBe('at runtime') - expect($('#param-copied-lowcard').text()).toBe('one') - expect($('#param-copied-highcard').text()).toBe('run') - expect($('#param-key-count').text()).toBe('2') - expect(getLines('Route "/params')).toEqual([]) - } - }) - } else { - it('should prerender pages that spread params without awaiting first in a server component when prebuilt', async () => { + $ = await next.render$( + '/params/semantics/one/run/sync/layout-spread/server' + ) + if (isNextDev) { + expect($('#layout').text()).toBe('at runtime') + expect($('#lowcard').text()).toBe('at runtime') + expect($('#highcard').text()).toBe('at runtime') + expect($('#page').text()).toBe('at runtime') + expect($('#param-copied-lowcard').text()).toBe('one') + expect($('#param-copied-highcard').text()).toBe('run') + expect($('#param-key-count').text()).toBe('2') + expect(getLines('Route "/params')).toEqual([ + expect.stringContaining('`...params` or similar expression'), + ]) + } else { + expect($('#layout').text()).toBe('at buildtime') + expect($('#lowcard').text()).toBe('at buildtime') + expect($('#highcard').text()).toBe('at buildtime') + expect($('#highcard-fallback').text()).toBe('loading highcard children') + expect($('#page').text()).toBe('at runtime') + expect($('#param-copied-lowcard').text()).toBe('one') + expect($('#param-copied-highcard').text()).toBe('run') + expect($('#param-key-count').text()).toBe('2') expect(getLines('Route "/params')).toEqual([]) - let $ = await next.render$( - '/params/semantics/one/build/sync/layout-spread/server' - ) - if (isNextDev) { - expect($('#layout').text()).toBe('at runtime') - expect($('#lowcard').text()).toBe('at runtime') - expect($('#highcard').text()).toBe('at runtime') - expect($('#page').text()).toBe('at runtime') - expect($('#param-copied-lowcard').text()).toBe('one') - expect($('#param-copied-highcard').text()).toBe('build') - expect($('#param-key-count').text()).toBe('2') - expect(getLines('Route "/params')).toEqual([ - expect.stringContaining('`...params` or similar expression'), - ]) - } else { - expect($('#layout').text()).toBe('at buildtime') - expect($('#lowcard').text()).toBe('at buildtime') - expect($('#highcard').text()).toBe('at buildtime') - expect($('#page').text()).toBe('at buildtime') - expect($('#param-copied-lowcard').text()).toBe('one') - expect($('#param-copied-highcard').text()).toBe('build') - expect($('#param-key-count').text()).toBe('2') - expect(getLines('Route "/params')).toEqual([]) - } - - $ = await next.render$( - '/params/semantics/one/build/sync/page-spread/server' - ) - if (isNextDev) { - expect($('#layout').text()).toBe('at runtime') - expect($('#lowcard').text()).toBe('at runtime') - expect($('#highcard').text()).toBe('at runtime') - expect($('#page').text()).toBe('at runtime') - expect($('#param-copied-lowcard').text()).toBe('one') - expect($('#param-copied-highcard').text()).toBe('build') - expect($('#param-key-count').text()).toBe('2') - expect(getLines('Route "/params')).toEqual([ - expect.stringContaining('`...params` or similar expression'), - ]) - } else { - expect($('#layout').text()).toBe('at buildtime') - expect($('#lowcard').text()).toBe('at buildtime') - expect($('#highcard').text()).toBe('at buildtime') - expect($('#page').text()).toBe('at buildtime') - expect($('#param-copied-lowcard').text()).toBe('one') - expect($('#param-copied-highcard').text()).toBe('build') - expect($('#param-key-count').text()).toBe('2') - expect(getLines('Route "/params')).toEqual([]) - } - }) + } - it('should prerender pages that spread params without `use`ing first in a client component when prebuilt', async () => { + $ = await next.render$( + '/params/semantics/one/run/sync/page-spread/server' + ) + if (isNextDev) { + expect($('#layout').text()).toBe('at runtime') + expect($('#lowcard').text()).toBe('at runtime') + expect($('#highcard').text()).toBe('at runtime') + expect($('#page').text()).toBe('at runtime') + expect($('#param-copied-lowcard').text()).toBe('one') + expect($('#param-copied-highcard').text()).toBe('run') + expect($('#param-key-count').text()).toBe('2') + expect(getLines('Route "/params')).toEqual([ + expect.stringContaining('`...params` or similar expression'), + ]) + } else { + expect($('#layout').text()).toBe('at buildtime') + expect($('#lowcard').text()).toBe('at buildtime') + expect($('#highcard').text()).toBe('at buildtime') + expect($('#highcard-fallback').text()).toBe('loading highcard children') + expect($('#page').text()).toBe('at runtime') + expect($('#param-copied-lowcard').text()).toBe('one') + expect($('#param-copied-highcard').text()).toBe('run') + expect($('#param-key-count').text()).toBe('2') expect(getLines('Route "/params')).toEqual([]) - let $ = await next.render$( - '/params/semantics/one/build/sync/layout-spread/client' - ) - if (isNextDev) { - expect($('#layout').text()).toBe('at runtime') - expect($('#lowcard').text()).toBe('at runtime') - expect($('#highcard').text()).toBe('at runtime') - expect($('#page').text()).toBe('at runtime') - expect($('#param-copied-lowcard').text()).toBe('one') - expect($('#param-copied-highcard').text()).toBe('build') - expect($('#param-key-count').text()).toBe('2') - expect(getLines('Route "/params')).toEqual([ - expect.stringContaining('`...params` or similar expression'), - expect.stringContaining('`params.lowcard`'), - expect.stringContaining('`params.highcard`'), - ]) - } else { - expect($('#layout').text()).toBe('at buildtime') - expect($('#lowcard').text()).toBe('at buildtime') - expect($('#highcard').text()).toBe('at buildtime') - expect($('#page').text()).toBe('at buildtime') - expect($('#param-copied-lowcard').text()).toBe('one') - expect($('#param-copied-highcard').text()).toBe('build') - expect($('#param-key-count').text()).toBe('2') - expect(getLines('Route "/params')).toEqual([]) - } - - $ = await next.render$( - '/params/semantics/one/build/sync/page-spread/client' - ) - if (isNextDev) { - expect($('#layout').text()).toBe('at runtime') - expect($('#lowcard').text()).toBe('at runtime') - expect($('#highcard').text()).toBe('at runtime') - expect($('#page').text()).toBe('at runtime') - expect($('#param-copied-lowcard').text()).toBe('one') - expect($('#param-copied-highcard').text()).toBe('build') - expect($('#param-key-count').text()).toBe('2') - expect(getLines('Route "/params')).toEqual([ - expect.stringContaining('`...params` or similar expression'), - expect.stringContaining('`params.lowcard`'), - expect.stringContaining('`params.highcard`'), - ]) - } else { - // TODO at the moment pages receive searchParams which are not know at build time - // and always dynamic. We have to pessimistically assume they are accessed and thus - // we cannot actually produce a static shell without PPR. - expect($('#layout').text()).toBe('at runtime') - expect($('#lowcard').text()).toBe('at runtime') - expect($('#highcard').text()).toBe('at runtime') - expect($('#page').text()).toBe('at runtime') - expect($('#param-copied-lowcard').text()).toBe('one') - expect($('#param-copied-highcard').text()).toBe('build') - expect($('#param-key-count').text()).toBe('2') - expect(getLines('Route "/params')).toEqual([]) - } - }) + } + }) - it('should prerender pages that spread params without awaiting first in a server component when not prebuilt', async () => { + it('should partially prerender pages that spread params without `use`ing them first in a client component', async () => { + expect(getLines('Route "/params')).toEqual([]) + let $ = await next.render$( + '/params/semantics/one/build/sync/layout-spread/client' + ) + if (isNextDev) { + expect($('#layout').text()).toBe('at runtime') + expect($('#lowcard').text()).toBe('at runtime') + expect($('#highcard').text()).toBe('at runtime') + expect($('#page').text()).toBe('at runtime') + expect($('#param-copied-lowcard').text()).toBe('one') + expect($('#param-copied-highcard').text()).toBe('build') + expect($('#param-key-count').text()).toBe('2') + expect(getLines('Route "/params')).toEqual([ + expect.stringContaining('`...params` or similar expression'), + expect.stringContaining('`params.lowcard`'), + expect.stringContaining('`params.highcard`'), + ]) + } else { + expect($('#layout').text()).toBe('at buildtime') + expect($('#lowcard').text()).toBe('at buildtime') + expect($('#highcard').text()).toBe('at buildtime') + expect($('#page').text()).toBe('at buildtime') + expect($('#param-copied-lowcard').text()).toBe('one') + expect($('#param-copied-highcard').text()).toBe('build') + expect($('#param-key-count').text()).toBe('2') expect(getLines('Route "/params')).toEqual([]) - let $ = await next.render$( - '/params/semantics/one/run/sync/layout-spread/server' - ) - if (isNextDev) { - expect($('#layout').text()).toBe('at runtime') - expect($('#lowcard').text()).toBe('at runtime') - expect($('#highcard').text()).toBe('at runtime') - expect($('#page').text()).toBe('at runtime') - expect($('#param-copied-lowcard').text()).toBe('one') - expect($('#param-copied-highcard').text()).toBe('run') - expect($('#param-key-count').text()).toBe('2') - expect(getLines('Route "/params')).toEqual([ - expect.stringContaining('`...params` or similar expression'), - ]) - } else { - expect($('#layout').text()).toBe('at runtime') - - expect($('#lowcard').text()).toBe('at runtime') - expect($('#highcard').text()).toBe('at runtime') - expect($('#page').text()).toBe('at runtime') - expect($('#param-copied-lowcard').text()).toBe('one') - expect($('#param-copied-highcard').text()).toBe('run') - expect($('#param-key-count').text()).toBe('2') - expect(getLines('Route "/params')).toEqual([]) - } + } - $ = await next.render$( - '/params/semantics/one/run/sync/page-spread/server' - ) - if (isNextDev) { - expect($('#layout').text()).toBe('at runtime') - expect($('#lowcard').text()).toBe('at runtime') - expect($('#highcard').text()).toBe('at runtime') - expect($('#page').text()).toBe('at runtime') - expect($('#param-copied-lowcard').text()).toBe('one') - expect($('#param-copied-highcard').text()).toBe('run') - expect($('#param-key-count').text()).toBe('2') - expect(getLines('Route "/params')).toEqual([ - expect.stringContaining('`...params` or similar expression'), - ]) - } else { - expect($('#layout').text()).toBe('at runtime') - - expect($('#lowcard').text()).toBe('at runtime') - expect($('#highcard').text()).toBe('at runtime') - expect($('#page').text()).toBe('at runtime') - expect($('#param-copied-lowcard').text()).toBe('one') - expect($('#param-copied-highcard').text()).toBe('run') - expect($('#param-key-count').text()).toBe('2') - expect(getLines('Route "/params')).toEqual([]) - } - }) + $ = await next.render$( + '/params/semantics/one/build/sync/page-spread/client' + ) + if (isNextDev) { + expect($('#layout').text()).toBe('at runtime') + expect($('#lowcard').text()).toBe('at runtime') + expect($('#highcard').text()).toBe('at runtime') + expect($('#page').text()).toBe('at runtime') + expect($('#param-copied-lowcard').text()).toBe('one') + expect($('#param-copied-highcard').text()).toBe('build') + expect($('#param-key-count').text()).toBe('2') + expect(getLines('Route "/params')).toEqual([ + expect.stringContaining('`...params` or similar expression'), + expect.stringContaining('`params.lowcard`'), + expect.stringContaining('`params.highcard`'), + ]) + } else { + expect($('#layout').text()).toBe('at buildtime') + expect($('#lowcard').text()).toBe('at buildtime') + expect($('#highcard').text()).toBe('at buildtime') + expect($('#page').text()).toBe('at buildtime') + expect($('#param-copied-lowcard').text()).toBe('one') + expect($('#param-copied-highcard').text()).toBe('build') + expect($('#param-key-count').text()).toBe('2') + expect(getLines('Route "/params')).toEqual([]) + } - it('should prerender pages that spread params without `use`ing first in a client component when not prebuilt', async () => { + $ = await next.render$( + '/params/semantics/one/run/sync/layout-spread/client' + ) + if (isNextDev) { + expect($('#layout').text()).toBe('at runtime') + expect($('#lowcard').text()).toBe('at runtime') + expect($('#highcard').text()).toBe('at runtime') + expect($('#page').text()).toBe('at runtime') + expect($('#param-copied-lowcard').text()).toBe('one') + expect($('#param-copied-highcard').text()).toBe('run') + expect($('#param-key-count').text()).toBe('2') + expect(getLines('Route "/params')).toEqual([ + expect.stringContaining('`...params` or similar expression'), + expect.stringContaining('`params.lowcard`'), + expect.stringContaining('`params.highcard`'), + ]) + } else { + expect($('#layout').text()).toBe('at buildtime') + expect($('#lowcard').text()).toBe('at buildtime') + expect($('#highcard').text()).toBe('at buildtime') + expect($('#highcard-fallback').text()).toBe('loading highcard children') + expect($('#page').text()).toBe('at runtime') + expect($('#param-copied-lowcard').text()).toBe('one') + expect($('#param-copied-highcard').text()).toBe('run') + expect($('#param-key-count').text()).toBe('2') expect(getLines('Route "/params')).toEqual([]) - let $ = await next.render$( - '/params/semantics/one/run/sync/layout-spread/client' - ) - if (isNextDev) { - expect($('#layout').text()).toBe('at runtime') - expect($('#lowcard').text()).toBe('at runtime') - expect($('#highcard').text()).toBe('at runtime') - expect($('#page').text()).toBe('at runtime') - expect($('#param-copied-lowcard').text()).toBe('one') - expect($('#param-copied-highcard').text()).toBe('run') - expect($('#param-key-count').text()).toBe('2') - expect(getLines('Route "/params')).toEqual([ - expect.stringContaining('`...params` or similar expression'), - expect.stringContaining('`params.lowcard`'), - expect.stringContaining('`params.highcard`'), - ]) - } else { - expect($('#layout').text()).toBe('at runtime') - - expect($('#lowcard').text()).toBe('at runtime') - expect($('#highcard').text()).toBe('at runtime') - expect($('#page').text()).toBe('at runtime') - expect($('#param-copied-lowcard').text()).toBe('one') - expect($('#param-copied-highcard').text()).toBe('run') - expect($('#param-key-count').text()).toBe('2') - expect(getLines('Route "/params')).toEqual([]) - } + } - $ = await next.render$( - '/params/semantics/one/run/sync/page-spread/client' - ) - if (isNextDev) { - expect($('#layout').text()).toBe('at runtime') - expect($('#lowcard').text()).toBe('at runtime') - expect($('#highcard').text()).toBe('at runtime') - expect($('#page').text()).toBe('at runtime') - expect($('#param-copied-lowcard').text()).toBe('one') - expect($('#param-copied-highcard').text()).toBe('run') - expect($('#param-key-count').text()).toBe('2') - expect(getLines('Route "/params')).toEqual([ - expect.stringContaining('`...params` or similar expression'), - expect.stringContaining('`params.lowcard`'), - expect.stringContaining('`params.highcard`'), - ]) - } else { - expect($('#layout').text()).toBe('at runtime') - expect($('#lowcard').text()).toBe('at runtime') - expect($('#highcard').text()).toBe('at runtime') - expect($('#page').text()).toBe('at runtime') - expect($('#param-copied-lowcard').text()).toBe('one') - expect($('#param-copied-highcard').text()).toBe('run') - expect($('#param-key-count').text()).toBe('2') - expect(getLines('Route "/params')).toEqual([]) - } - }) - } + $ = await next.render$( + '/params/semantics/one/run/sync/page-spread/client' + ) + if (isNextDev) { + expect($('#layout').text()).toBe('at runtime') + expect($('#lowcard').text()).toBe('at runtime') + expect($('#highcard').text()).toBe('at runtime') + expect($('#page').text()).toBe('at runtime') + expect($('#param-copied-lowcard').text()).toBe('one') + expect($('#param-copied-highcard').text()).toBe('run') + expect($('#param-key-count').text()).toBe('2') + expect(getLines('Route "/params')).toEqual([ + expect.stringContaining('`...params` or similar expression'), + expect.stringContaining('`params.lowcard`'), + expect.stringContaining('`params.highcard`'), + ]) + } else { + expect($('#layout').text()).toBe('at buildtime') + expect($('#lowcard').text()).toBe('at buildtime') + expect($('#highcard').text()).toBe('at buildtime') + expect($('#highcard-fallback').text()).toBe('loading highcard children') + expect($('#page').text()).toBe('at runtime') + expect($('#param-copied-lowcard').text()).toBe('one') + expect($('#param-copied-highcard').text()).toBe('run') + expect($('#param-key-count').text()).toBe('2') + expect(getLines('Route "/params')).toEqual([]) + } + }) }) describe('Param Shadowing', () => { @@ -2227,11 +1230,7 @@ describe('dynamic-io', () => { expect($('#param-status').text()).toBe('qux') expect(getLines('Route "/params')).toEqual([]) } else { - if (WITH_PPR) { - expect($('#layout').text()).toBe('at buildtime') - } else { - expect($('#layout').text()).toBe('at runtime') - } + expect($('#layout').text()).toBe('at buildtime') expect($('#page').text()).toBe('at runtime') expect($('#param-dyn').text()).toBe('foo') expect($('#param-then').text()).toBe('bar') @@ -2252,11 +1251,7 @@ describe('dynamic-io', () => { expect($('#param-status').text()).toBe('qux') expect(getLines('Route "/params')).toEqual([]) } else { - if (WITH_PPR) { - expect($('#layout').text()).toBe('at buildtime') - } else { - expect($('#layout').text()).toBe('at runtime') - } + expect($('#layout').text()).toBe('at buildtime') expect($('#page').text()).toBe('at runtime') expect($('#param-dyn').text()).toBe('foo') expect($('#param-then').text()).toBe('bar') @@ -2280,11 +1275,7 @@ describe('dynamic-io', () => { expect($('#param-status').text()).toBe('qux') expect(getLines('Route "/params')).toEqual([]) } else { - if (WITH_PPR) { - expect($('#layout').text()).toBe('at buildtime') - } else { - expect($('#layout').text()).toBe('at runtime') - } + expect($('#layout').text()).toBe('at buildtime') expect($('#page').text()).toBe('at runtime') expect($('#param-dyn').text()).toBe('foo') expect($('#param-then').text()).toBe('bar') @@ -2305,11 +1296,7 @@ describe('dynamic-io', () => { expect($('#param-status').text()).toBe('qux') expect(getLines('Route "/params')).toEqual([]) } else { - if (WITH_PPR) { - expect($('#layout').text()).toBe('at buildtime') - } else { - expect($('#layout').text()).toBe('at runtime') - } + expect($('#layout').text()).toBe('at buildtime') expect($('#page').text()).toBe('at runtime') expect($('#param-dyn').text()).toBe('foo') expect($('#param-then').text()).toBe('bar') @@ -2339,11 +1326,7 @@ describe('dynamic-io', () => { expect.stringContaining('`params.value`'), ]) } else { - if (WITH_PPR) { - expect($('#layout').text()).toBe('at buildtime') - } else { - expect($('#layout').text()).toBe('at runtime') - } + expect($('#layout').text()).toBe('at buildtime') expect($('#page').text()).toBe('at runtime') expect($('#param-dyn').text()).toBe('foo') expect($('#param-then').text()).toEqual( @@ -2372,11 +1355,7 @@ describe('dynamic-io', () => { expect.stringContaining('`params.value`'), ]) } else { - if (WITH_PPR) { - expect($('#layout').text()).toBe('at buildtime') - } else { - expect($('#layout').text()).toBe('at runtime') - } + expect($('#layout').text()).toBe('at buildtime') expect($('#page').text()).toBe('at runtime') expect($('#param-dyn').text()).toBe('foo') expect($('#param-then').text()).toEqual( @@ -2410,11 +1389,7 @@ describe('dynamic-io', () => { expect.stringContaining('`params.value`'), ]) } else { - if (WITH_PPR) { - expect($('#layout').text()).toBe('at buildtime') - } else { - expect($('#layout').text()).toBe('at runtime') - } + expect($('#layout').text()).toBe('at buildtime') expect($('#page').text()).toBe('at runtime') expect($('#param-dyn').text()).toBe('foo') expect($('#param-then').text()).toEqual( @@ -2445,11 +1420,7 @@ describe('dynamic-io', () => { expect.stringContaining('`params.value`'), ]) } else { - if (WITH_PPR) { - expect($('#layout').text()).toBe('at buildtime') - } else { - expect($('#layout').text()).toBe('at runtime') - } + expect($('#layout').text()).toBe('at buildtime') expect($('#page').text()).toBe('at runtime') expect($('#param-dyn').text()).toBe('foo') expect($('#param-then').text()).toEqual( @@ -2484,14 +1455,10 @@ describe('dynamic-io', () => { line.includes('/params/generate-static-params/[slug]') ) { let nextLine = lines[i++] - if (WITH_PPR) { - // when PPR is on (in this test suite) we also turn on fallbacks. - // we expect the fallback shell first - expect(nextLine).toContain( - '/params/generate-static-params/[slug]' - ) - nextLine = lines[i++] - } + // we expect the fallback shell first + expect(nextLine).toContain('/params/generate-static-params/[slug]') + nextLine = lines[i++] + expect(nextLine).toMatch( /\/params\/generate-static-params\/\d+\/page/ ) diff --git a/test/e2e/app-dir/dynamic-io/dynamic-io.random.test.ts b/test/e2e/app-dir/dynamic-io/dynamic-io.random.test.ts index 679bece90840..19e856d9e778 100644 --- a/test/e2e/app-dir/dynamic-io/dynamic-io.random.test.ts +++ b/test/e2e/app-dir/dynamic-io/dynamic-io.random.test.ts @@ -1,7 +1,5 @@ import { nextTestSetup } from 'e2e-utils' -const WITH_PPR = !!process.env.__NEXT_EXPERIMENTAL_PPR - describe('dynamic-io', () => { const { next, isNextDev, skipped } = nextTestSetup({ files: __dirname, @@ -33,11 +31,8 @@ describe('dynamic-io', () => { if (isNextDev) { expect($('#layout').text()).toBe('at runtime') expect($('#page').text()).toBe('at runtime') - } else if (WITH_PPR) { - expect($('#layout').text()).toBe('at buildtime') - expect($('#page').text()).toBe('at runtime') } else { - expect($('#layout').text()).toBe('at runtime') + expect($('#layout').text()).toBe('at buildtime') expect($('#page').text()).toBe('at runtime') } }) diff --git a/test/e2e/app-dir/dynamic-io/dynamic-io.search.test.ts b/test/e2e/app-dir/dynamic-io/dynamic-io.search.test.ts index a2fd659d9fb6..42ce81d1ce6a 100644 --- a/test/e2e/app-dir/dynamic-io/dynamic-io.search.test.ts +++ b/test/e2e/app-dir/dynamic-io/dynamic-io.search.test.ts @@ -1,7 +1,5 @@ import { nextTestSetup } from 'e2e-utils' -const WITH_PPR = !!process.env.__NEXT_EXPERIMENTAL_PPR - describe('dynamic-io', () => { const { next, isNextDev, skipped } = nextTestSetup({ files: __dirname, @@ -26,399 +24,211 @@ describe('dynamic-io', () => { return warnings } - if (WITH_PPR) { - it('should partially prerender pages that await searchParams in a server component', async () => { - let $ = await next.render$('/search/async/server/await?sentinel=hello') - if (isNextDev) { - expect($('#layout').text()).toBe('at runtime') - expect($('#value').text()).toBe('hello') - expect($('#page').text()).toBe('at runtime') - } else { - expect($('#layout').text()).toBe('at buildtime') - expect($('main').text()).toContain('inner loading...') - expect($('main').text()).not.toContain('outer loading...') - expect($('#value').text()).toBe('hello') - expect($('#page').text()).toBe('at runtime') - } - }) - - it('should partially prerender pages that `use` searchParams in a server component', async () => { - let $ = await next.render$('/search/async/server/use?sentinel=hello') - if (isNextDev) { - expect($('#layout').text()).toBe('at runtime') - expect($('#value').text()).toBe('hello') - expect($('#page').text()).toBe('at runtime') - } else { - expect($('#layout').text()).toBe('at buildtime') - expect($('main').text()).toContain('inner loading...') - expect($('main').text()).not.toContain('outer loading...') - expect($('#value').text()).toBe('hello') - expect($('#page').text()).toBe('at runtime') - } - }) - - it('should partially prerender pages that `use` searchParams in a client component', async () => { - let $ = await next.render$('/search/async/client/use?sentinel=hello') - if (isNextDev) { - expect($('#layout').text()).toBe('at runtime') - expect($('#value').text()).toBe('hello') - expect($('#page').text()).toBe('at runtime') - } else { - expect($('#layout').text()).toBe('at buildtime') - expect($('main').text()).toContain('inner loading...') - expect($('main').text()).not.toContain('outer loading...') - expect($('#value').text()).toBe('hello') - expect($('#page').text()).toBe('at runtime') - } - }) - } else { - it('should not prerender pages that await searchParams in a server component', async () => { - let $ = await next.render$('/search/async/server/await?sentinel=hello') - if (isNextDev) { - expect($('#layout').text()).toBe('at runtime') - expect($('#value').text()).toBe('hello') - expect($('#page').text()).toBe('at runtime') - } else { - expect($('#layout').text()).toBe('at runtime') - expect($('#value').text()).toBe('hello') - expect($('#page').text()).toBe('at runtime') - } - }) - - it('should not prerender pages that `use` searchParams in a server component', async () => { - let $ = await next.render$('/search/async/server/use?sentinel=hello') - if (isNextDev) { - expect($('#layout').text()).toBe('at runtime') - expect($('#value').text()).toBe('hello') - expect($('#page').text()).toBe('at runtime') - } else { - expect($('#layout').text()).toBe('at runtime') - expect($('#value').text()).toBe('hello') - expect($('#page').text()).toBe('at runtime') - } - }) - - it('should not prerender pages that `use` searchParams in a client component', async () => { - let $ = await next.render$('/search/async/client/use?sentinel=hello') - if (isNextDev) { - expect($('#layout').text()).toBe('at runtime') - expect($('#value').text()).toBe('hello') - expect($('#page').text()).toBe('at runtime') - } else { - expect($('#layout').text()).toBe('at runtime') - expect($('#value').text()).toBe('hello') - expect($('#page').text()).toBe('at runtime') - } - }) - } - - if (WITH_PPR) { - it('should partially prerender pages that access a searchParam property synchronously in a server component', async () => { - let $ = await next.render$('/search/sync/server/access?sentinel=hello') - let searchWarnings = getLines('Route "/search') - if (isNextDev) { - expect($('#layout').text()).toBe('at runtime') - expect($('#value').text()).toBe('hello') - expect($('#page').text()).toBe('at runtime') - expect(searchWarnings).toEqual([ - expect.stringContaining('`searchParams.sentinel`'), - ]) - } else { - expect(searchWarnings).toHaveLength(0) - expect($('#layout').text()).toBe('at buildtime') - expect($('main').text()).not.toContain('inner loading...') - // This test case aborts synchronously and the later component render - // triggers the outer boundary - expect($('main').text()).toContain('outer loading...') - expect($('#value').text()).toBe('hello') - expect($('#page').text()).toBe('at runtime') - } - }) - - it('should partially prerender pages that access a searchParam property synchronously in a client component', async () => { - let $ = await next.render$('/search/sync/client/access?sentinel=hello') - let searchWarnings = getLines('Route "/search') - if (isNextDev) { - expect($('#layout').text()).toBe('at runtime') - expect($('#value').text()).toBe('hello') - expect($('#page').text()).toBe('at runtime') - expect(searchWarnings).toEqual([ - expect.stringContaining('`searchParams.sentinel`'), - ]) - } else { - expect(searchWarnings).toHaveLength(0) - expect($('#layout').text()).toBe('at buildtime') - expect($('main').text()).toContain('inner loading...') - expect($('main').text()).not.toContain('outer loading...') - expect($('#value').text()).toBe('hello') - expect($('#page').text()).toBe('at runtime') - } - }) - - it('should partially prerender pages that checks for the existence of a searchParam property synchronously in a server component', async () => { - let $ = await next.render$('/search/sync/server/has?sentinel=hello') - let searchWarnings = getLines('Route "/search') - if (isNextDev) { - expect($('#layout').text()).toBe('at runtime') - expect($('#has-sentinel').text()).toBe('true') - expect($('#has-foo').text()).toBe('false') - expect($('#page').text()).toBe('at runtime') - expect(searchWarnings).toEqual([ - expect.stringContaining( - '`Reflect.has(searchParams, "sentinel")`, `"sentinel" in searchParams`, or similar' - ), - expect.stringContaining( - '`Reflect.has(searchParams, "foo")`, `"foo" in searchParams`, or similar' - ), - ]) - } else { - expect(searchWarnings).toHaveLength(0) - expect($('#layout').text()).toBe('at buildtime') - expect($('main').text()).not.toContain('inner loading...') - // This test case aborts synchronously and the later component render - // triggers the outer boundary - expect($('main').text()).toContain('outer loading...') - expect($('#has-sentinel').text()).toBe('true') - expect($('#has-foo').text()).toBe('false') - expect($('#page').text()).toBe('at runtime') - } - }) + it('should partially prerender pages that await searchParams in a server component', async () => { + let $ = await next.render$('/search/async/server/await?sentinel=hello') + if (isNextDev) { + expect($('#layout').text()).toBe('at runtime') + expect($('#value').text()).toBe('hello') + expect($('#page').text()).toBe('at runtime') + } else { + expect($('#layout').text()).toBe('at buildtime') + expect($('main').text()).toContain('inner loading...') + expect($('main').text()).not.toContain('outer loading...') + expect($('#value').text()).toBe('hello') + expect($('#page').text()).toBe('at runtime') + } + }) - it('should partially prerender pages that checks for the existence of a searchParam property synchronously in a client component', async () => { - let $ = await next.render$('/search/sync/client/has?sentinel=hello') - let searchWarnings = getLines('Route "/search') - if (isNextDev) { - expect($('#layout').text()).toBe('at runtime') - expect($('#has-sentinel').text()).toBe('true') - expect($('#has-foo').text()).toBe('false') - expect($('#page').text()).toBe('at runtime') - expect(searchWarnings).toEqual([ - expect.stringContaining( - '`Reflect.has(searchParams, "sentinel")`, `"sentinel" in searchParams`, or similar' - ), - expect.stringContaining( - '`Reflect.has(searchParams, "foo")`, `"foo" in searchParams`, or similar' - ), - ]) - } else { - expect(searchWarnings).toHaveLength(0) - expect($('#layout').text()).toBe('at buildtime') - expect($('main').text()).toContain('inner loading...') - expect($('main').text()).not.toContain('outer loading...') - expect($('#has-sentinel').text()).toBe('true') - expect($('#has-foo').text()).toBe('false') - expect($('#page').text()).toBe('at runtime') - } - }) + it('should partially prerender pages that `use` searchParams in a server component', async () => { + let $ = await next.render$('/search/async/server/use?sentinel=hello') + if (isNextDev) { + expect($('#layout').text()).toBe('at runtime') + expect($('#value').text()).toBe('hello') + expect($('#page').text()).toBe('at runtime') + } else { + expect($('#layout').text()).toBe('at buildtime') + expect($('main').text()).toContain('inner loading...') + expect($('main').text()).not.toContain('outer loading...') + expect($('#value').text()).toBe('hello') + expect($('#page').text()).toBe('at runtime') + } + }) - it('should partially prerender pages that spreads ...searchParam synchronously in a server component', async () => { - let $ = await next.render$( - '/search/sync/server/spread?sentinel=hello&foo=foo&then=bar&value=baz' - ) - let searchWarnings = getLines('Route "/search') - if (isNextDev) { - expect($('#layout').text()).toBe('at runtime') - expect($('[data-value]').length).toBe(3) - expect($('#value-sentinel').text()).toBe('hello') - expect($('#value-foo').text()).toBe('foo') - expect($('#value-value').text()).toBe('baz') - expect($('#page').text()).toBe('at runtime') - expect(searchWarnings).toEqual([ - expect.stringContaining( - 'conflict with builtin or well-known property names: `then`' - ), - ]) - } else { - expect(searchWarnings).toHaveLength(0) - expect($('#layout').text()).toBe('at buildtime') - expect($('main').text()).not.toContain('inner loading...') - // This test case aborts synchronously and the later component render - // triggers the outer boundary - expect($('main').text()).toContain('outer loading...') - expect($('[data-value]').length).toBe(3) - expect($('#value-sentinel').text()).toBe('hello') - expect($('#value-foo').text()).toBe('foo') - expect($('#value-value').text()).toBe('baz') - expect($('#page').text()).toBe('at runtime') - } - }) + it('should partially prerender pages that `use` searchParams in a client component', async () => { + let $ = await next.render$('/search/async/client/use?sentinel=hello') + if (isNextDev) { + expect($('#layout').text()).toBe('at runtime') + expect($('#value').text()).toBe('hello') + expect($('#page').text()).toBe('at runtime') + } else { + expect($('#layout').text()).toBe('at buildtime') + expect($('main').text()).toContain('inner loading...') + expect($('main').text()).not.toContain('outer loading...') + expect($('#value').text()).toBe('hello') + expect($('#page').text()).toBe('at runtime') + } + }) - it('should partially prerender pages that spreads ...searchParam synchronously in a client component', async () => { - let $ = await next.render$( - '/search/sync/client/spread?sentinel=hello&foo=foo&then=bar&value=baz' - ) - let searchWarnings = getLines('Route "/search') - if (isNextDev) { - expect($('#layout').text()).toBe('at runtime') - expect($('[data-value]').length).toBe(3) - expect($('#value-sentinel').text()).toBe('hello') - expect($('#value-foo').text()).toBe('foo') - expect($('#value-value').text()).toBe('baz') - expect($('#page').text()).toBe('at runtime') - expect(searchWarnings).toEqual([ - expect.stringContaining( - 'conflict with builtin or well-known property names: `then`' - ), - expect.stringContaining('`searchParams.sentinel`'), - expect.stringContaining('`searchParams.foo`'), - expect.stringContaining('`searchParams.value`'), - ]) - } else { - expect(searchWarnings).toHaveLength(0) - expect($('#layout').text()).toBe('at buildtime') - expect($('main').text()).toContain('inner loading...') - expect($('main').text()).not.toContain('outer loading...') - expect($('[data-value]').length).toBe(3) - expect($('#value-sentinel').text()).toBe('hello') - expect($('#value-foo').text()).toBe('foo') - expect($('#value-value').text()).toBe('baz') - expect($('#page').text()).toBe('at runtime') - } - }) - } else { - it('should not prerender a page that accesses a searchParam property synchronously in a server component', async () => { - let $ = await next.render$('/search/sync/server/access?sentinel=hello') - let searchWarnings = getLines('Route "/search') - if (isNextDev) { - expect($('#layout').text()).toBe('at runtime') - expect($('#value').text()).toBe('hello') - expect($('#page').text()).toBe('at runtime') - expect(searchWarnings).toEqual([ - expect.stringContaining('`searchParams.sentinel`'), - ]) - } else { - expect(searchWarnings).toHaveLength(0) - expect($('#layout').text()).toBe('at runtime') - expect($('#value').text()).toBe('hello') - expect($('#page').text()).toBe('at runtime') - } - }) + it('should partially prerender pages that access a searchParam property synchronously in a server component', async () => { + let $ = await next.render$('/search/sync/server/access?sentinel=hello') + let searchWarnings = getLines('Route "/search') + if (isNextDev) { + expect($('#layout').text()).toBe('at runtime') + expect($('#value').text()).toBe('hello') + expect($('#page').text()).toBe('at runtime') + expect(searchWarnings).toEqual([ + expect.stringContaining('`searchParams.sentinel`'), + ]) + } else { + expect(searchWarnings).toHaveLength(0) + expect($('#layout').text()).toBe('at buildtime') + expect($('main').text()).not.toContain('inner loading...') + // This test case aborts synchronously and the later component render + // triggers the outer boundary + expect($('main').text()).toContain('outer loading...') + expect($('#value').text()).toBe('hello') + expect($('#page').text()).toBe('at runtime') + } + }) - it('should not prerender a page that accesses a searchParam property synchronously in a client component', async () => { - let $ = await next.render$('/search/sync/client/access?sentinel=hello') - let searchWarnings = getLines('Route "/search') - if (isNextDev) { - expect($('#layout').text()).toBe('at runtime') - expect($('#value').text()).toBe('hello') - expect($('#page').text()).toBe('at runtime') - expect(searchWarnings).toEqual([ - expect.stringContaining('`searchParams.sentinel`'), - ]) - } else { - expect(searchWarnings).toHaveLength(0) - expect($('#layout').text()).toBe('at runtime') - expect($('#value').text()).toBe('hello') - expect($('#page').text()).toBe('at runtime') - } - }) + it('should partially prerender pages that access a searchParam property synchronously in a client component', async () => { + let $ = await next.render$('/search/sync/client/access?sentinel=hello') + let searchWarnings = getLines('Route "/search') + if (isNextDev) { + expect($('#layout').text()).toBe('at runtime') + expect($('#value').text()).toBe('hello') + expect($('#page').text()).toBe('at runtime') + expect(searchWarnings).toEqual([ + expect.stringContaining('`searchParams.sentinel`'), + ]) + } else { + expect(searchWarnings).toHaveLength(0) + expect($('#layout').text()).toBe('at buildtime') + expect($('main').text()).toContain('inner loading...') + expect($('main').text()).not.toContain('outer loading...') + expect($('#value').text()).toBe('hello') + expect($('#page').text()).toBe('at runtime') + } + }) - it('should not prerender a page that checks for the existence of a searchParam property synchronously in a server component', async () => { - let $ = await next.render$('/search/sync/server/has?sentinel=hello') - let searchWarnings = getLines('Route "/search') - if (isNextDev) { - expect($('#layout').text()).toBe('at runtime') - expect($('#has-sentinel').text()).toBe('true') - expect($('#has-foo').text()).toBe('false') - expect($('#page').text()).toBe('at runtime') - expect(searchWarnings).toEqual([ - expect.stringContaining( - '`Reflect.has(searchParams, "sentinel")`, `"sentinel" in searchParams`, or similar' - ), - expect.stringContaining( - '`Reflect.has(searchParams, "foo")`, `"foo" in searchParams`, or similar' - ), - ]) - } else { - expect(searchWarnings).toHaveLength(0) - expect($('#layout').text()).toBe('at runtime') - expect($('#has-sentinel').text()).toBe('true') - expect($('#has-foo').text()).toBe('false') - expect($('#page').text()).toBe('at runtime') - } - }) + it('should partially prerender pages that checks for the existence of a searchParam property synchronously in a server component', async () => { + let $ = await next.render$('/search/sync/server/has?sentinel=hello') + let searchWarnings = getLines('Route "/search') + if (isNextDev) { + expect($('#layout').text()).toBe('at runtime') + expect($('#has-sentinel').text()).toBe('true') + expect($('#has-foo').text()).toBe('false') + expect($('#page').text()).toBe('at runtime') + expect(searchWarnings).toEqual([ + expect.stringContaining( + '`Reflect.has(searchParams, "sentinel")`, `"sentinel" in searchParams`, or similar' + ), + expect.stringContaining( + '`Reflect.has(searchParams, "foo")`, `"foo" in searchParams`, or similar' + ), + ]) + } else { + expect(searchWarnings).toHaveLength(0) + expect($('#layout').text()).toBe('at buildtime') + expect($('main').text()).not.toContain('inner loading...') + // This test case aborts synchronously and the later component render + // triggers the outer boundary + expect($('main').text()).toContain('outer loading...') + expect($('#has-sentinel').text()).toBe('true') + expect($('#has-foo').text()).toBe('false') + expect($('#page').text()).toBe('at runtime') + } + }) - it('should not prerender a page that checks for the existence of a searchParam property synchronously in a client component', async () => { - let $ = await next.render$('/search/sync/client/has?sentinel=hello') - let searchWarnings = getLines('Route "/search') - if (isNextDev) { - expect($('#layout').text()).toBe('at runtime') - expect($('#has-sentinel').text()).toBe('true') - expect($('#has-foo').text()).toBe('false') - expect($('#page').text()).toBe('at runtime') - expect(searchWarnings).toEqual([ - expect.stringContaining( - '`Reflect.has(searchParams, "sentinel")`, `"sentinel" in searchParams`, or similar' - ), - expect.stringContaining( - '`Reflect.has(searchParams, "foo")`, `"foo" in searchParams`, or similar' - ), - ]) - } else { - expect(searchWarnings).toHaveLength(0) - expect($('#layout').text()).toBe('at runtime') - expect($('#has-sentinel').text()).toBe('true') - expect($('#has-foo').text()).toBe('false') - expect($('#page').text()).toBe('at runtime') - } - }) + it('should partially prerender pages that checks for the existence of a searchParam property synchronously in a client component', async () => { + let $ = await next.render$('/search/sync/client/has?sentinel=hello') + let searchWarnings = getLines('Route "/search') + if (isNextDev) { + expect($('#layout').text()).toBe('at runtime') + expect($('#has-sentinel').text()).toBe('true') + expect($('#has-foo').text()).toBe('false') + expect($('#page').text()).toBe('at runtime') + expect(searchWarnings).toEqual([ + expect.stringContaining( + '`Reflect.has(searchParams, "sentinel")`, `"sentinel" in searchParams`, or similar' + ), + expect.stringContaining( + '`Reflect.has(searchParams, "foo")`, `"foo" in searchParams`, or similar' + ), + ]) + } else { + expect(searchWarnings).toHaveLength(0) + expect($('#layout').text()).toBe('at buildtime') + expect($('main').text()).toContain('inner loading...') + expect($('main').text()).not.toContain('outer loading...') + expect($('#has-sentinel').text()).toBe('true') + expect($('#has-foo').text()).toBe('false') + expect($('#page').text()).toBe('at runtime') + } + }) - it('should not prerender a page that spreads ...searchParam synchronously in a server component', async () => { - let $ = await next.render$( - '/search/sync/server/spread?sentinel=hello&foo=foo&then=bar&value=baz' - ) - let searchWarnings = getLines('Route "/search') - if (isNextDev) { - expect($('#layout').text()).toBe('at runtime') - expect($('[data-value]').length).toBe(3) - expect($('#value-sentinel').text()).toBe('hello') - expect($('#value-foo').text()).toBe('foo') - expect($('#value-value').text()).toBe('baz') - expect($('#page').text()).toBe('at runtime') - expect(searchWarnings).toEqual([ - expect.stringContaining( - 'conflict with builtin or well-known property names: `then`' - ), - ]) - } else { - expect(searchWarnings).toHaveLength(0) - expect($('#layout').text()).toBe('at runtime') - expect($('[data-value]').length).toBe(3) - expect($('#value-sentinel').text()).toBe('hello') - expect($('#value-foo').text()).toBe('foo') - expect($('#value-value').text()).toBe('baz') - expect($('#page').text()).toBe('at runtime') - } - }) + it('should partially prerender pages that spreads ...searchParam synchronously in a server component', async () => { + let $ = await next.render$( + '/search/sync/server/spread?sentinel=hello&foo=foo&then=bar&value=baz' + ) + let searchWarnings = getLines('Route "/search') + if (isNextDev) { + expect($('#layout').text()).toBe('at runtime') + expect($('[data-value]').length).toBe(3) + expect($('#value-sentinel').text()).toBe('hello') + expect($('#value-foo').text()).toBe('foo') + expect($('#value-value').text()).toBe('baz') + expect($('#page').text()).toBe('at runtime') + expect(searchWarnings).toEqual([ + expect.stringContaining( + 'conflict with builtin or well-known property names: `then`' + ), + ]) + } else { + expect(searchWarnings).toHaveLength(0) + expect($('#layout').text()).toBe('at buildtime') + expect($('main').text()).not.toContain('inner loading...') + // This test case aborts synchronously and the later component render + // triggers the outer boundary + expect($('main').text()).toContain('outer loading...') + expect($('[data-value]').length).toBe(3) + expect($('#value-sentinel').text()).toBe('hello') + expect($('#value-foo').text()).toBe('foo') + expect($('#value-value').text()).toBe('baz') + expect($('#page').text()).toBe('at runtime') + } + }) - it('should not prerender a page that spreads ...searchParam synchronously in a client component', async () => { - let $ = await next.render$( - '/search/sync/client/spread?sentinel=hello&foo=foo&then=bar&value=baz' - ) - let searchWarnings = getLines('Route "/search') - if (isNextDev) { - expect($('#layout').text()).toBe('at runtime') - expect($('[data-value]').length).toBe(3) - expect($('#value-sentinel').text()).toBe('hello') - expect($('#value-foo').text()).toBe('foo') - expect($('#value-value').text()).toBe('baz') - expect($('#page').text()).toBe('at runtime') - expect(searchWarnings).toEqual([ - expect.stringContaining( - 'conflict with builtin or well-known property names: `then`' - ), - expect.stringContaining('`searchParams.sentinel`'), - expect.stringContaining('`searchParams.foo`'), - expect.stringContaining('`searchParams.value`'), - ]) - } else { - expect(searchWarnings).toHaveLength(0) - expect($('#layout').text()).toBe('at runtime') - expect($('[data-value]').length).toBe(3) - expect($('#value-sentinel').text()).toBe('hello') - expect($('#value-foo').text()).toBe('foo') - expect($('#value-value').text()).toBe('baz') - expect($('#page').text()).toBe('at runtime') - } - }) - } + it('should partially prerender pages that spreads ...searchParam synchronously in a client component', async () => { + let $ = await next.render$( + '/search/sync/client/spread?sentinel=hello&foo=foo&then=bar&value=baz' + ) + let searchWarnings = getLines('Route "/search') + if (isNextDev) { + expect($('#layout').text()).toBe('at runtime') + expect($('[data-value]').length).toBe(3) + expect($('#value-sentinel').text()).toBe('hello') + expect($('#value-foo').text()).toBe('foo') + expect($('#value-value').text()).toBe('baz') + expect($('#page').text()).toBe('at runtime') + expect(searchWarnings).toEqual([ + expect.stringContaining( + 'conflict with builtin or well-known property names: `then`' + ), + expect.stringContaining('`searchParams.sentinel`'), + expect.stringContaining('`searchParams.foo`'), + expect.stringContaining('`searchParams.value`'), + ]) + } else { + expect(searchWarnings).toHaveLength(0) + expect($('#layout').text()).toBe('at buildtime') + expect($('main').text()).toContain('inner loading...') + expect($('main').text()).not.toContain('outer loading...') + expect($('[data-value]').length).toBe(3) + expect($('#value-sentinel').text()).toBe('hello') + expect($('#value-foo').text()).toBe('foo') + expect($('#value-value').text()).toBe('baz') + expect($('#page').text()).toBe('at runtime') + } + }) }) diff --git a/test/e2e/app-dir/dynamic-io/dynamic-io.server-action.test.ts b/test/e2e/app-dir/dynamic-io/dynamic-io.server-action.test.ts index afda00a755e0..04bd962f6559 100644 --- a/test/e2e/app-dir/dynamic-io/dynamic-io.server-action.test.ts +++ b/test/e2e/app-dir/dynamic-io/dynamic-io.server-action.test.ts @@ -25,18 +25,13 @@ describe('dynamic-io', () => { expect(await browser.elementByCss('p').text()).toBe('result') }) - const isExperimentalReact = Boolean(process.env.__NEXT_EXPERIMENTAL_PPR) - if (isExperimentalReact && isNextDev) { - // TODO(react-time-info): Remove this branch for experimental React in dev mode when the - // issue is resolved where the inclusion of server timings in the RSC - // payload makes the serialized bound args not suitable to be used as a - // cache key. + if (isNextDev) { + // TODO(react-time-info): For experimental React in dev mode, the + // inclusion of server timings in the RSC payload makes the serialized + // bound args not suitable to be used as a cache key. When this is fixed + // we expect this error not to be logged anymore. expect(next.cliOutput).toMatch('Error: Route "/server-action-inline"') - } else { - expect(next.cliOutput).not.toMatch('Error: Route "/server-action-inline"') - } - if (isNextDev) { await assertNoRedbox(browser) } }) diff --git a/test/e2e/app-dir/dynamic-io/dynamic-io.test.ts b/test/e2e/app-dir/dynamic-io/dynamic-io.test.ts index fbc68d0af393..9b1035d3c3f7 100644 --- a/test/e2e/app-dir/dynamic-io/dynamic-io.test.ts +++ b/test/e2e/app-dir/dynamic-io/dynamic-io.test.ts @@ -1,9 +1,5 @@ /* eslint-disable jest/no-standalone-expect */ import { nextTestSetup } from 'e2e-utils' -import { retry } from 'next-test-utils' -import { Playwright } from 'next-webdriver' - -const WITH_PPR = !!process.env.__NEXT_EXPERIMENTAL_PPR describe('dynamic-io', () => { const { next, isNextDev, skipped } = nextTestSetup({ @@ -15,68 +11,6 @@ describe('dynamic-io', () => { return } - if (isNextDev && !WITH_PPR) { - async function hasStaticIndicator(browser: Playwright) { - await browser.elementByCss('[data-nextjs-dev-tools-button]').click() - - return await browser.eval( - () => - document - .querySelector('nextjs-portal') - .shadowRoot.querySelector('[data-nextjs-route-type]') - .getAttribute('data-nextjs-route-type') === 'static' - ) - } - - it('should not have static indicator on dynamic method route', async () => { - const browser = await next.browser('/cases/dynamic_api_cookies') - - await retry(async () => { - expect(await browser.eval('!!window.next.router ? "yes": "no"')).toBe( - 'yes' - ) - }) - - expect(await hasStaticIndicator(browser)).toBe(false) - }) - - it('should not have static indicator on dynamic IO route', async () => { - const browser = await next.browser('/cases/fetch_mixed') - - await retry(async () => { - expect(await browser.eval('!!window.next.router ? "yes": "no"')).toBe( - 'yes' - ) - }) - - expect(await hasStaticIndicator(browser)).toBe(false) - }) - - it('should have static indicator on static route', async () => { - const browser = await next.browser('/cases/static') - - await retry(async () => { - expect(await browser.eval('!!window.next.router ? "yes": "no"')).toBe( - 'yes' - ) - }) - - expect(await hasStaticIndicator(browser)).toBe(true) - }) - - it('should have static indicator on not-found route', async () => { - const browser = await next.browser('/cases/not-found') - - await retry(async () => { - expect(await browser.eval('!!window.next.router ? "yes": "no"')).toBe( - 'yes' - ) - - expect(await hasStaticIndicator(browser)).toBe(true) - }) - }) - } - it('should not have route specific errors', async () => { expect(next.cliOutput).not.toMatch('Error: Route "/') expect(next.cliOutput).not.toMatch('Error occurred prerendering page') @@ -150,37 +84,20 @@ describe('dynamic-io', () => { } }) - if (WITH_PPR) { - it('should partially prerender pages that take longer than a task to render', async () => { - let $ = await next.render$('/cases/task', {}) - if (isNextDev) { - expect($('#layout').text()).toBe('at runtime') - expect($('#page').text()).toBe('at runtime') - expect($('#inner').text()).toBe('at runtime') - } else { - expect($('#layout').text()).toBe('at buildtime') - expect($('#page').text()).toBe('at buildtime') - // The inner slot is computed during the prerender but is hidden - // it gets revealed when the resume happens - expect($('#inner').text()).toBe('at buildtime') - } - }) - } else { - it('should not prerender pages that take longer than a single task to render', async () => { - let $ = await next.render$('/cases/task', {}) - if (isNextDev) { - expect($('#layout').text()).toBe('at runtime') - expect($('#page').text()).toBe('at runtime') - expect($('#inner').text()).toBe('at runtime') - } else { - expect($('#layout').text()).toBe('at runtime') - expect($('#page').text()).toBe('at runtime') - // The inner slot is computed during the prerender but is hidden - // it gets revealed when the resume happens - expect($('#inner').text()).toBe('at runtime') - } - }) - } + it('should partially prerender pages that take longer than a task to render', async () => { + let $ = await next.render$('/cases/task', {}) + if (isNextDev) { + expect($('#layout').text()).toBe('at runtime') + expect($('#page').text()).toBe('at runtime') + expect($('#inner').text()).toBe('at runtime') + } else { + expect($('#layout').text()).toBe('at buildtime') + expect($('#page').text()).toBe('at buildtime') + // The inner slot is computed during the prerender but is hidden + // it gets revealed when the resume happens + expect($('#inner').text()).toBe('at buildtime') + } + }) it('should prerender pages that only use cached fetches', async () => { const $ = await next.render$('/cases/fetch_cached', {}) @@ -193,33 +110,18 @@ describe('dynamic-io', () => { } }) - if (WITH_PPR) { - it('should partially prerender pages that use at least one fetch without cache', async () => { - let $ = await next.render$('/cases/fetch_mixed', {}) - if (isNextDev) { - expect($('#layout').text()).toBe('at runtime') - expect($('#page').text()).toBe('at runtime') - expect($('#inner').text()).toBe('at runtime') - } else { - expect($('#layout').text()).toBe('at buildtime') - expect($('#page').text()).toBe('at buildtime') - expect($('#inner').text()).toBe('at buildtime') - } - }) - } else { - it('should not prerender pages that use at least one fetch without cache', async () => { - let $ = await next.render$('/cases/fetch_mixed', {}) - if (isNextDev) { - expect($('#layout').text()).toBe('at runtime') - expect($('#page').text()).toBe('at runtime') - expect($('#inner').text()).toBe('at runtime') - } else { - expect($('#layout').text()).toBe('at runtime') - expect($('#page').text()).toBe('at runtime') - expect($('#inner').text()).toBe('at runtime') - } - }) - } + it('should partially prerender pages that use at least one fetch without cache', async () => { + let $ = await next.render$('/cases/fetch_mixed', {}) + if (isNextDev) { + expect($('#layout').text()).toBe('at runtime') + expect($('#page').text()).toBe('at runtime') + expect($('#inner').text()).toBe('at runtime') + } else { + expect($('#layout').text()).toBe('at buildtime') + expect($('#page').text()).toBe('at buildtime') + expect($('#inner').text()).toBe('at buildtime') + } + }) it('should prerender pages that only use cached (unstable_cache) IO', async () => { const $ = await next.render$('/cases/io_cached', {}) @@ -254,125 +156,61 @@ describe('dynamic-io', () => { } }) - if (WITH_PPR) { - it('should partially prerender pages that do any uncached IO', async () => { - let $ = await next.render$('/cases/io_mixed', {}) - if (isNextDev) { - expect($('#layout').text()).toBe('at runtime') - expect($('#page').text()).toBe('at runtime') - expect($('#inner').text()).toBe('at runtime') - } else { - expect($('#layout').text()).toBe('at buildtime') - expect($('#page').text()).toBe('at buildtime') - expect($('#inner').text()).toBe('at buildtime') - } - }) - } else { - it('should not prerender pages that do any uncached IO', async () => { - let $ = await next.render$('/cases/io_mixed', {}) - if (isNextDev) { - expect($('#layout').text()).toBe('at runtime') - expect($('#page').text()).toBe('at runtime') - expect($('#inner').text()).toBe('at runtime') - } else { - expect($('#layout').text()).toBe('at runtime') - expect($('#page').text()).toBe('at runtime') - expect($('#inner').text()).toBe('at runtime') - } - }) - } + it('should partially prerender pages that do any uncached IO', async () => { + let $ = await next.render$('/cases/io_mixed', {}) + if (isNextDev) { + expect($('#layout').text()).toBe('at runtime') + expect($('#page').text()).toBe('at runtime') + expect($('#inner').text()).toBe('at runtime') + } else { + expect($('#layout').text()).toBe('at buildtime') + expect($('#page').text()).toBe('at buildtime') + expect($('#inner').text()).toBe('at buildtime') + } + }) - if (WITH_PPR) { - it('should partially prerender pages that do any uncached IO (use cache)', async () => { - let $ = await next.render$('/cases/use_cache_mixed', {}) - if (isNextDev) { - expect($('#layout').text()).toBe('at runtime') - expect($('#page').text()).toBe('at runtime') - expect($('#inner').text()).toBe('at runtime') - } else { - expect($('#layout').text()).toBe('at buildtime') - expect($('#page').text()).toBe('at buildtime') - expect($('#inner').text()).toBe('at buildtime') - } - }) - } else { - it('should not prerender pages that do any uncached IO (use cache)', async () => { - let $ = await next.render$('/cases/use_cache_mixed', {}) - if (isNextDev) { - expect($('#layout').text()).toBe('at runtime') - expect($('#page').text()).toBe('at runtime') - expect($('#inner').text()).toBe('at runtime') - } else { - expect($('#layout').text()).toBe('at runtime') - expect($('#page').text()).toBe('at runtime') - expect($('#inner').text()).toBe('at runtime') - } - }) - } + it('should partially prerender pages that do any uncached IO (use cache)', async () => { + let $ = await next.render$('/cases/use_cache_mixed', {}) + if (isNextDev) { + expect($('#layout').text()).toBe('at runtime') + expect($('#page').text()).toBe('at runtime') + expect($('#inner').text()).toBe('at runtime') + } else { + expect($('#layout').text()).toBe('at buildtime') + expect($('#page').text()).toBe('at buildtime') + expect($('#inner').text()).toBe('at buildtime') + } + }) - if (WITH_PPR) { - it('should partially prerender pages that use `cookies()`', async () => { - let $ = await next.render$('/cases/dynamic_api_cookies', {}) - if (isNextDev) { - expect($('#layout').text()).toBe('at runtime') - expect($('#page').text()).toBe('at runtime') - expect($('#inner').text()).toBe('at runtime') - expect($('#value').text()).toBe('hello') - } else { - expect($('#layout').text()).toBe('at buildtime') - expect($('#page').text()).toBe('at buildtime') - expect($('#inner').text()).toBe('at buildtime') - expect($('#value').text()).toBe('hello') - } - }) - } else { - it('should not prerender pages that use `cookies()`', async () => { - let $ = await next.render$('/cases/dynamic_api_cookies', {}) - if (isNextDev) { - expect($('#layout').text()).toBe('at runtime') - expect($('#page').text()).toBe('at runtime') - expect($('#inner').text()).toBe('at runtime') - expect($('#value').text()).toBe('hello') - } else { - expect($('#layout').text()).toBe('at runtime') - expect($('#page').text()).toBe('at runtime') - expect($('#inner').text()).toBe('at runtime') - expect($('#value').text()).toBe('hello') - } - }) - } + it('should partially prerender pages that use `cookies()`', async () => { + let $ = await next.render$('/cases/dynamic_api_cookies', {}) + if (isNextDev) { + expect($('#layout').text()).toBe('at runtime') + expect($('#page').text()).toBe('at runtime') + expect($('#inner').text()).toBe('at runtime') + expect($('#value').text()).toBe('hello') + } else { + expect($('#layout').text()).toBe('at buildtime') + expect($('#page').text()).toBe('at buildtime') + expect($('#inner').text()).toBe('at buildtime') + expect($('#value').text()).toBe('hello') + } + }) - if (WITH_PPR) { - it('should partially prerender pages that use `headers()`', async () => { - let $ = await next.render$('/cases/dynamic_api_headers') - if (isNextDev) { - expect($('#layout').text()).toBe('at runtime') - expect($('#page').text()).toBe('at runtime') - expect($('#inner').text()).toBe('at runtime') - expect($('#value').text()).toBe('hello') - } else { - expect($('#layout').text()).toBe('at buildtime') - expect($('#page').text()).toBe('at buildtime') - expect($('#inner').text()).toBe('at buildtime') - expect($('#value').text()).toBe('hello') - } - }) - } else { - it('should not prerender pages that use `headers()`', async () => { - let $ = await next.render$('/cases/dynamic_api_headers') - if (isNextDev) { - expect($('#layout').text()).toBe('at runtime') - expect($('#page').text()).toBe('at runtime') - expect($('#inner').text()).toBe('at runtime') - expect($('#value').text()).toBe('hello') - } else { - expect($('#layout').text()).toBe('at runtime') - expect($('#page').text()).toBe('at runtime') - expect($('#inner').text()).toBe('at runtime') - expect($('#value').text()).toBe('hello') - } - }) - } + it('should partially prerender pages that use `headers()`', async () => { + let $ = await next.render$('/cases/dynamic_api_headers') + if (isNextDev) { + expect($('#layout').text()).toBe('at runtime') + expect($('#page').text()).toBe('at runtime') + expect($('#inner').text()).toBe('at runtime') + expect($('#value').text()).toBe('hello') + } else { + expect($('#layout').text()).toBe('at buildtime') + expect($('#page').text()).toBe('at buildtime') + expect($('#inner').text()).toBe('at buildtime') + expect($('#value').text()).toBe('hello') + } + }) it('should fully prerender pages that use `unstable_noStore()`', async () => { let $ = await next.render$('/cases/dynamic_api_no_store', {}) @@ -387,83 +225,43 @@ describe('dynamic-io', () => { } }) - if (WITH_PPR) { - it('should partially prerender pages that use `searchParams` in Server Components', async () => { - let $ = await next.render$( - '/cases/dynamic_api_search_params_server?sentinel=my+sentinel', - {} - ) - if (isNextDev) { - expect($('#layout').text()).toBe('at runtime') - expect($('#page').text()).toBe('at runtime') - expect($('#inner').text()).toBe('at runtime') - expect($('#value').text()).toBe('my sentinel') - } else { - expect($('#layout').text()).toBe('at buildtime') - expect($('#page').text()).toBe('at buildtime') - expect($('#inner').text()).toBe('at buildtime') - expect($('#value').text()).toBe('my sentinel') - } - }) - } else { - it('should not prerender pages that use `searchParams` in Server Components', async () => { - let $ = await next.render$( - '/cases/dynamic_api_search_params_server?sentinel=my+sentinel', - {} - ) - if (isNextDev) { - expect($('#layout').text()).toBe('at runtime') - expect($('#page').text()).toBe('at runtime') - expect($('#inner').text()).toBe('at runtime') - expect($('#value').text()).toBe('my sentinel') - } else { - expect($('#layout').text()).toBe('at runtime') - expect($('#page').text()).toBe('at runtime') - expect($('#inner').text()).toBe('at runtime') - expect($('#value').text()).toBe('my sentinel') - } - }) - } + it('should partially prerender pages that use `searchParams` in Server Components', async () => { + let $ = await next.render$( + '/cases/dynamic_api_search_params_server?sentinel=my+sentinel', + {} + ) + if (isNextDev) { + expect($('#layout').text()).toBe('at runtime') + expect($('#page').text()).toBe('at runtime') + expect($('#inner').text()).toBe('at runtime') + expect($('#value').text()).toBe('my sentinel') + } else { + expect($('#layout').text()).toBe('at buildtime') + expect($('#page').text()).toBe('at buildtime') + expect($('#inner').text()).toBe('at buildtime') + expect($('#value').text()).toBe('my sentinel') + } + }) - if (WITH_PPR) { - it('should partially prerender pages that use `searchParams` in Client Components', async () => { - let $ = await next.render$( - '/cases/dynamic_api_search_params_client?sentinel=my+sentinel', - {} - ) - if (isNextDev) { - expect($('#layout').text()).toBe('at runtime') - expect($('#page').text()).toBe('at runtime') - expect($('#inner').text()).toBe('at runtime') - expect($('#value').text()).toBe('my sentinel') - } else { - expect($('#layout').text()).toBe('at buildtime') - expect($('#page').text()).toBe('at buildtime') - // The second component renders before the first one aborts so we end up - // capturing the static value during buildtime - expect($('#inner').text()).toBe('at buildtime') - expect($('#value').text()).toBe('my sentinel') - } - }) - } else { - it('should not prerender pages that use `searchParams` in Client Components', async () => { - let $ = await next.render$( - '/cases/dynamic_api_search_params_client?sentinel=my+sentinel', - {} - ) - if (isNextDev) { - expect($('#layout').text()).toBe('at runtime') - expect($('#page').text()).toBe('at runtime') - expect($('#inner').text()).toBe('at runtime') - expect($('#value').text()).toBe('my sentinel') - } else { - expect($('#layout').text()).toBe('at runtime') - expect($('#page').text()).toBe('at runtime') - expect($('#inner').text()).toBe('at runtime') - expect($('#value').text()).toBe('my sentinel') - } - }) - } + it('should partially prerender pages that use `searchParams` in Client Components', async () => { + let $ = await next.render$( + '/cases/dynamic_api_search_params_client?sentinel=my+sentinel', + {} + ) + if (isNextDev) { + expect($('#layout').text()).toBe('at runtime') + expect($('#page').text()).toBe('at runtime') + expect($('#inner').text()).toBe('at runtime') + expect($('#value').text()).toBe('my sentinel') + } else { + expect($('#layout').text()).toBe('at buildtime') + expect($('#page').text()).toBe('at buildtime') + // The second component renders before the first one aborts so we end up + // capturing the static value during buildtime + expect($('#inner').text()).toBe('at buildtime') + expect($('#value').text()).toBe('my sentinel') + } + }) it('can prerender pages with parallel routes that are static', async () => { const $ = await next.render$('/cases/parallel/static', {}) @@ -501,15 +299,9 @@ describe('dynamic-io', () => { expect($('#page-slot').text()).toBe('at runtime') expect($('#page-children').text()).toBe('at runtime') } else { - if (WITH_PPR) { - expect($('#layout').text()).toBe('at buildtime') - expect($('#page-slot').text()).toBe('at runtime') - expect($('#page-children').text()).toBe('at buildtime') - } else { - expect($('#layout').text()).toBe('at runtime') - expect($('#page-slot').text()).toBe('at runtime') - expect($('#page-children').text()).toBe('at runtime') - } + expect($('#layout').text()).toBe('at buildtime') + expect($('#page-slot').text()).toBe('at runtime') + expect($('#page-children').text()).toBe('at buildtime') } }) @@ -533,15 +325,9 @@ describe('dynamic-io', () => { expect($('#page-slot').text()).toBe('at runtime') expect($('#page-children').text()).toBe('at runtime') } else { - if (WITH_PPR) { - expect($('#layout').text()).toBe('at buildtime') - expect($('#page-slot').text()).toBe('at runtime') - expect($('#page-children').text()).toBe('at buildtime') - } else { - expect($('#layout').text()).toBe('at runtime') - expect($('#page-slot').text()).toBe('at runtime') - expect($('#page-children').text()).toBe('at runtime') - } + expect($('#layout').text()).toBe('at buildtime') + expect($('#page-slot').text()).toBe('at runtime') + expect($('#page-children').text()).toBe('at buildtime') } }) }) diff --git a/test/e2e/app-dir/dynamic-io/dynamic-io.web-crypto.test.ts b/test/e2e/app-dir/dynamic-io/dynamic-io.web-crypto.test.ts index 1b5ac0400ad1..67459e87e231 100644 --- a/test/e2e/app-dir/dynamic-io/dynamic-io.web-crypto.test.ts +++ b/test/e2e/app-dir/dynamic-io/dynamic-io.web-crypto.test.ts @@ -1,7 +1,5 @@ import { nextTestSetup } from 'e2e-utils' -const WITH_PPR = !!process.env.__NEXT_EXPERIMENTAL_PPR - describe('dynamic-io', () => { const { next, isNextDev, skipped } = nextTestSetup({ files: __dirname, @@ -36,12 +34,8 @@ describe('dynamic-io', () => { expect($('#layout').text()).toBe('at runtime') expect($('#page').text()).toBe('at runtime') expect($('#first').text()).not.toEqual($('#second').text()) - } else if (WITH_PPR) { - expect($('#layout').text()).toBe('at buildtime') - expect($('#page').text()).toBe('at runtime') - expect($('#first').text()).not.toEqual($('#second').text()) } else { - expect($('#layout').text()).toBe('at runtime') + expect($('#layout').text()).toBe('at buildtime') expect($('#page').text()).toBe('at runtime') expect($('#first').text()).not.toEqual($('#second').text()) } @@ -66,12 +60,8 @@ describe('dynamic-io', () => { expect($('#layout').text()).toBe('at runtime') expect($('#page').text()).toBe('at runtime') expect($('#first').text()).not.toEqual($('#second').text()) - } else if (WITH_PPR) { - expect($('#layout').text()).toBe('at buildtime') - expect($('#page').text()).toBe('at runtime') - expect($('#first').text()).not.toEqual($('#second').text()) } else { - expect($('#layout').text()).toBe('at runtime') + expect($('#layout').text()).toBe('at buildtime') expect($('#page').text()).toBe('at runtime') expect($('#first').text()).not.toEqual($('#second').text()) } From 1c19ff8393136582ccd919662c605e1fce00957a Mon Sep 17 00:00:00 2001 From: Hendrik Liebau Date: Thu, 15 May 2025 20:03:24 +0200 Subject: [PATCH 05/10] Update `errors.json` --- packages/next/errors.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/next/errors.json b/packages/next/errors.json index 938bf2cbbb1d..c7b04300867b 100644 --- a/packages/next/errors.json +++ b/packages/next/errors.json @@ -679,5 +679,6 @@ "678": "CacheSignal got more endRead() calls than beginRead() calls", "679": "A CacheSignal cannot subscribe to itself", "680": "Expected clientReferenceManifest to be defined.", - "681": "Accessed fallback \\`params\\` during prerendering." + "681": "Accessed fallback \\`params\\` during prerendering.", + "682": "\\`experimental.ppr\\` can not be \\`%s\\` when \\`experimental.dynamicIO\\` is \\`true\\`. PPR is implicitly enabled when Dynamic IO is enabled." } From f5ed87ad54c2f1066e064128a0ba5c1728e0a16f Mon Sep 17 00:00:00 2001 From: Hendrik Liebau Date: Thu, 15 May 2025 20:04:12 +0200 Subject: [PATCH 06/10] Remove prospective fallback prerenders This was only needed when DIO was enabled with PPR disabled. --- 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 b5d2f1aa018a..d374252278d4 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( @@ -2717,28 +2687,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 7d896b57b502..bfbaaa1a2b7b 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 81418acf1b67..ee1acdee932f 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 to allow that an empty static shell // is generated while rendering using PPR and Dynamic IO. _allowEmptyStaticShell: allowEmptyStaticShell = 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 47539f9e5c04..a93f24a0b57c 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(), _allowEmptyStaticShell: z.boolean().optional(), }) ) diff --git a/packages/next/src/server/config-shared.ts b/packages/next/src/server/config-shared.ts index bdba7d7d107c..2b3d632afdb7 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, the page is prerendered as a fallback shell, while allowing * any dynamic accesses to result in an empty shell. This is the case when diff --git a/packages/next/src/server/request/fallback-params.ts b/packages/next/src/server/request/fallback-params.ts index e3af245362e8..52ebe2c2f7f6 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 d9c59ec79045..000000000000 --- 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 cc65c1e63b57..000000000000 --- 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 803f17d863c8..000000000000 --- 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 195ad29be3c5..000000000000 --- 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, - }, -} From 9051b15af65654d7c568eee8907dc737fe83a456 Mon Sep 17 00:00:00 2001 From: Hendrik Liebau Date: Thu, 15 May 2025 22:27:26 +0200 Subject: [PATCH 07/10] Fix config overwrite for `ppr` --- packages/next/src/server/config.test.ts | 40 +++++++++++++++++++++++++ packages/next/src/server/config.ts | 8 ++--- 2 files changed, 44 insertions(+), 4 deletions(-) diff --git a/packages/next/src/server/config.test.ts b/packages/next/src/server/config.test.ts index 4ec04e82f896..700ac089f368 100644 --- a/packages/next/src/server/config.test.ts +++ b/packages/next/src/server/config.test.ts @@ -136,4 +136,44 @@ describe('loadConfig', () => { ) }) }) + + describe('with a canary version', () => { + beforeAll(() => { + process.env.__NEXT_VERSION = '15.4.0-canary.35' + }) + + afterAll(() => { + delete process.env.__NEXT_VERSION + }) + + it('errors when dynamicIO is enabled but PPR is disabled', async () => { + await expect( + loadConfig('', __dirname, { + customConfig: { + experimental: { + dynamicIO: true, + ppr: false, + }, + }, + }) + ).rejects.toThrow( + '`experimental.ppr` can not be `false` when `experimental.dynamicIO` is `true`. PPR is implicitly enabled when Dynamic IO is enabled.' + ) + }) + + it('errors when dynamicIO is enabled but PPR set to "incremental"', async () => { + await expect( + loadConfig('', __dirname, { + customConfig: { + experimental: { + dynamicIO: true, + ppr: 'incremental', + }, + }, + }) + ).rejects.toThrow( + '`experimental.ppr` can not be `"incremental"` when `experimental.dynamicIO` is `true`. PPR is implicitly enabled when Dynamic IO is enabled.' + ) + }) + }) }) diff --git a/packages/next/src/server/config.ts b/packages/next/src/server/config.ts index 4e3d6160b934..f53de12325b4 100644 --- a/packages/next/src/server/config.ts +++ b/packages/next/src/server/config.ts @@ -143,7 +143,7 @@ function warnCustomizedOption( function assignDefaults( dir: string, - userConfig: { [key: string]: any; configFileName: string }, + userConfig: NextConfig & { configFileName: string }, silent: boolean ): NextConfigComplete { const configFileName = userConfig.configFileName @@ -1108,11 +1108,11 @@ function assignDefaults( // If dynamicIO is enabled, we also enable PPR. if (result.experimental.dynamicIO) { if ( - config.experimental?.ppr === false || - config.experimental?.ppr === 'incremental' + userConfig.experimental?.ppr === false || + userConfig.experimental?.ppr === 'incremental' ) { throw new Error( - `\`experimental.ppr\` can not be \`${JSON.stringify(config.experimental?.ppr)}\` when \`experimental.dynamicIO\` is \`true\`. PPR is implicitly enabled when Dynamic IO is enabled.` + `\`experimental.ppr\` can not be \`${JSON.stringify(userConfig.experimental?.ppr)}\` when \`experimental.dynamicIO\` is \`true\`. PPR is implicitly enabled when Dynamic IO is enabled.` ) } From f26e24f53eb6a654f43852fda8728261379086fc Mon Sep 17 00:00:00 2001 From: Hendrik Liebau Date: Thu, 15 May 2025 23:22:17 +0200 Subject: [PATCH 08/10] Disable `dynamicIO` for `segment cache (output: "export")` test suite --- test/e2e/app-dir/segment-cache/export/next.config.js | 1 - test/ppr-tests-manifest.json | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/test/e2e/app-dir/segment-cache/export/next.config.js b/test/e2e/app-dir/segment-cache/export/next.config.js index 0a7ffa13a5f3..62ddfa93abf3 100644 --- a/test/e2e/app-dir/segment-cache/export/next.config.js +++ b/test/e2e/app-dir/segment-cache/export/next.config.js @@ -4,7 +4,6 @@ const nextConfig = { output: 'export', experimental: { - dynamicIO: true, clientSegmentCache: true, }, } diff --git a/test/ppr-tests-manifest.json b/test/ppr-tests-manifest.json index 97b403a7e105..dd6d17745338 100644 --- a/test/ppr-tests-manifest.json +++ b/test/ppr-tests-manifest.json @@ -98,6 +98,7 @@ "test/e2e/app-dir/react-max-headers-length/react-max-headers-length.test.ts", "test/e2e/app-dir/searchparams-static-bailout/searchparams-static-bailout.test.ts", "test/e2e/app-dir/segment-cache/basic/segment-cache-basic.test.ts", + "test/e2e/app-dir/segment-cache/export/segment-cache-output-export.test.ts", "test/e2e/app-dir/segment-cache/incremental-opt-in/segment-cache-incremental-opt-in.test.ts", "test/e2e/app-dir/segment-cache/memory-pressure/segment-cache-memory-pressure.test.ts", "test/e2e/app-dir/segment-cache/prefetch-scheduling/prefetch-scheduling.test.ts", From ce33992fae17b6665308c7963ac0948e8bc2c651 Mon Sep 17 00:00:00 2001 From: Hendrik Liebau Date: Fri, 16 May 2025 10:53:21 +0200 Subject: [PATCH 09/10] Fix global default cache handler test --- test/ppr-tests-manifest.json | 2 +- .../app/actions.ts | 0 .../app/layout.tsx | 0 .../app/page.tsx | 5 ++++- .../app/revalidate-tag/route.ts | 0 .../global-default-cache-handler.test.ts} | 6 +----- .../next.config.js | 2 +- 7 files changed, 7 insertions(+), 8 deletions(-) rename test/production/app-dir/{dynamic-io-cache-handlers => global-default-cache-handler}/app/actions.ts (100%) rename test/production/app-dir/{dynamic-io-cache-handlers => global-default-cache-handler}/app/layout.tsx (100%) rename test/production/app-dir/{dynamic-io-cache-handlers => global-default-cache-handler}/app/page.tsx (89%) rename test/production/app-dir/{dynamic-io-cache-handlers => global-default-cache-handler}/app/revalidate-tag/route.ts (100%) rename test/production/app-dir/{dynamic-io-cache-handlers/dynamic-io-cache-handlers.test.ts => global-default-cache-handler/global-default-cache-handler.test.ts} (95%) rename test/production/app-dir/{dynamic-io-cache-handlers => global-default-cache-handler}/next.config.js (87%) diff --git a/test/ppr-tests-manifest.json b/test/ppr-tests-manifest.json index dd6d17745338..69b57d258673 100644 --- a/test/ppr-tests-manifest.json +++ b/test/ppr-tests-manifest.json @@ -112,7 +112,7 @@ "test/e2e/app-dir/use-cache-route-handler-only/**/*", "test/integration/app-dir-export/**/*", "test/production/app-dir/build-output-tree-view/build-output-tree-view.test.ts", - "test/production/app-dir/dynamic-io-cache-handlers/**/*" + "test/production/app-dir/global-default-cache-handler/global-default-cache-handler.test.ts" ] } } diff --git a/test/production/app-dir/dynamic-io-cache-handlers/app/actions.ts b/test/production/app-dir/global-default-cache-handler/app/actions.ts similarity index 100% rename from test/production/app-dir/dynamic-io-cache-handlers/app/actions.ts rename to test/production/app-dir/global-default-cache-handler/app/actions.ts diff --git a/test/production/app-dir/dynamic-io-cache-handlers/app/layout.tsx b/test/production/app-dir/global-default-cache-handler/app/layout.tsx similarity index 100% rename from test/production/app-dir/dynamic-io-cache-handlers/app/layout.tsx rename to test/production/app-dir/global-default-cache-handler/app/layout.tsx diff --git a/test/production/app-dir/dynamic-io-cache-handlers/app/page.tsx b/test/production/app-dir/global-default-cache-handler/app/page.tsx similarity index 89% rename from test/production/app-dir/dynamic-io-cache-handlers/app/page.tsx rename to test/production/app-dir/global-default-cache-handler/app/page.tsx index d94b82524303..b3c2de1e0438 100644 --- a/test/production/app-dir/dynamic-io-cache-handlers/app/page.tsx +++ b/test/production/app-dir/global-default-cache-handler/app/page.tsx @@ -1,5 +1,6 @@ import React, { Suspense } from 'react' import { revalidate } from './actions' +import { connection } from 'next/server' async function Random({ cached }: { cached?: boolean }) { const data = await fetch( @@ -22,7 +23,9 @@ async function CachedRandom() { return } -export default function Page() { +export default async function Page() { + await connection() + return ( <>

index page

diff --git a/test/production/app-dir/dynamic-io-cache-handlers/app/revalidate-tag/route.ts b/test/production/app-dir/global-default-cache-handler/app/revalidate-tag/route.ts similarity index 100% rename from test/production/app-dir/dynamic-io-cache-handlers/app/revalidate-tag/route.ts rename to test/production/app-dir/global-default-cache-handler/app/revalidate-tag/route.ts diff --git a/test/production/app-dir/dynamic-io-cache-handlers/dynamic-io-cache-handlers.test.ts b/test/production/app-dir/global-default-cache-handler/global-default-cache-handler.test.ts similarity index 95% rename from test/production/app-dir/dynamic-io-cache-handlers/dynamic-io-cache-handlers.test.ts rename to test/production/app-dir/global-default-cache-handler/global-default-cache-handler.test.ts index e30786cdd2e3..bf7f79e2414b 100644 --- a/test/production/app-dir/dynamic-io-cache-handlers/dynamic-io-cache-handlers.test.ts +++ b/test/production/app-dir/global-default-cache-handler/global-default-cache-handler.test.ts @@ -8,16 +8,12 @@ import { retry, } from 'next-test-utils' -describe('dynamic-io-cache-handlers', () => { +describe('global-default-cache-handler', () => { let appPort: number let server: any let output = '' let next: NextInstance - if (process.env.__NEXT_EXPERIMENTAL_PPR) { - return it('should skip', () => {}) - } - beforeAll(async () => { next = await createNext({ files: new FileRef(__dirname), diff --git a/test/production/app-dir/dynamic-io-cache-handlers/next.config.js b/test/production/app-dir/global-default-cache-handler/next.config.js similarity index 87% rename from test/production/app-dir/dynamic-io-cache-handlers/next.config.js rename to test/production/app-dir/global-default-cache-handler/next.config.js index 1dcb709cd948..5bf8b9f6b99f 100644 --- a/test/production/app-dir/dynamic-io-cache-handlers/next.config.js +++ b/test/production/app-dir/global-default-cache-handler/next.config.js @@ -4,7 +4,7 @@ const nextConfig = { output: 'standalone', experimental: { - dynamicIO: true, + useCache: true, }, } From f41887b7a4e79ce39cc49f8340351b20d08ce40e Mon Sep 17 00:00:00 2001 From: Hendrik Liebau Date: Fri, 16 May 2025 16:23:08 +0200 Subject: [PATCH 10/10] Use `onPostpone` to determine if segment prefetch is partial --- .../next/src/server/app-render/app-render.tsx | 19 ------------- .../app-render/collect-segment-data.tsx | 27 ++++++------------- 2 files changed, 8 insertions(+), 38 deletions(-) diff --git a/packages/next/src/server/app-render/app-render.tsx b/packages/next/src/server/app-render/app-render.tsx index 47c698a89bdf..46d2e0ab4729 100644 --- a/packages/next/src/server/app-render/app-render.tsx +++ b/packages/next/src/server/app-render/app-render.tsx @@ -3853,27 +3853,8 @@ async function collectSegmentData( serverModuleMap: null, } - // When dynamicIO is enabled, missing data is encoded to an infinitely hanging - // promise, the absence of which we use to determine if a segment is fully - // static or partially static. However, when dynamicIO is not enabled, this - // trick doesn't work. - // - // So if PPR is enabled, and dynamicIO is not, we have to be conservative and - // assume all segments are partial. - // - // TODO: When PPR is on, we can at least optimize the case where the entire - // page is static. Either by passing that as an argument to this function, or - // by setting a header on the response like the we do for full page RSC - // prefetches today. The latter approach might be simpler since it requires - // less plumbing, and the client has to check the header regardless to see if - // PPR is enabled. - const shouldAssumePartialData = - renderOpts.experimental.isRoutePPREnabled === true && // PPR is enabled - !renderOpts.experimental.dynamicIO // dynamicIO is disabled - const staleTime = prerenderStore.stale return await ComponentMod.collectSegmentData( - shouldAssumePartialData, fullPageDataBuffer, staleTime, clientReferenceManifest.clientModules as ManifestNode, diff --git a/packages/next/src/server/app-render/collect-segment-data.tsx b/packages/next/src/server/app-render/collect-segment-data.tsx index 1ec7b2a07f25..d595b4d8799a 100644 --- a/packages/next/src/server/app-render/collect-segment-data.tsx +++ b/packages/next/src/server/app-render/collect-segment-data.tsx @@ -74,7 +74,6 @@ function onSegmentPrerenderError(error: unknown) { } export async function collectSegmentData( - shouldAssumePartialData: boolean, fullPageDataBuffer: Buffer, staleTime: number, clientModules: ManifestNode, @@ -119,7 +118,6 @@ export async function collectSegmentData( // inside of it, the side effects are transferred to the new stream. // @ts-expect-error - renderSegmentPrefetch( - shouldAssumePartialData, - buildId, - seedData, - key, - clientModules - ) + renderSegmentPrefetch(buildId, seedData, key, clientModules) ) ) } else { @@ -344,7 +330,6 @@ function encodeSegmentWithPossibleFallbackParam( } async function renderSegmentPrefetch( - shouldAssumePartialData: boolean, buildId: string, seedData: CacheNodeSeedData, key: string, @@ -359,8 +344,7 @@ async function renderSegmentPrefetch( buildId, rsc, loading, - isPartial: - shouldAssumePartialData || (await isPartialRSCData(rsc, clientModules)), + isPartial: await isPartialRSCData(rsc, clientModules), } // Since all we're doing is decoding and re-encoding a cached prerender, if // it takes longer than a microtask, it must because of hanging promises @@ -403,6 +387,11 @@ async function isPartialRSCData( await prerender(rsc, clientModules, { signal: abortController.signal, onError() {}, + onPostpone() { + // If something postponed, i.e. when Dynamic IO is not enabled, we can + // infer that the RSC data is partial. + isPartial = true + }, }) return isPartial }