mirror of
https://github.com/hanzoai/next.js.git
synced 2026-09-24 17:00:51 +00:00
Revert "Remove legacy PPR codepaths" (#95113)
This commit is contained in:
@@ -20,7 +20,7 @@ All flags need: `config-shared.ts` (type) → `config-schema.ts` (zod). If the f
|
||||
|
||||
## Where the Flag Is Consumed
|
||||
|
||||
**Client/bundled code only** (e.g. `__NEXT_CACHE_COMPONENTS` in client components): `define-env.ts` is sufficient. Webpack/Turbopack replaces `process.env.X` at the user's build time.
|
||||
**Client/bundled code only** (e.g. `__NEXT_PPR` in client components): `define-env.ts` is sufficient. Webpack/Turbopack replaces `process.env.X` at the user's build time.
|
||||
|
||||
**Pre-compiled runtime bundles** (e.g. code in `app-render.tsx`): The flag must also be set as a real `process.env` var at runtime, because `app-render.tsx` runs from pre-compiled bundles where `define-env.ts` doesn't reach. Two approaches:
|
||||
|
||||
|
||||
@@ -2109,6 +2109,7 @@ export async function handleBuildComplete({
|
||||
onMatchHeaders: [],
|
||||
rewrites,
|
||||
restrictedRedirectPaths: [],
|
||||
isAppPPREnabled: config.cacheComponents,
|
||||
}).routesManifest.rsc,
|
||||
},
|
||||
outputs,
|
||||
|
||||
@@ -13,6 +13,7 @@ import { discoverRoutes } from '../route-discovery'
|
||||
import { findPagesDir } from '../../lib/find-pages-dir'
|
||||
import loadCustomRoutes from '../../lib/load-custom-routes'
|
||||
import { generateRoutesManifest } from '../generate-routes-manifest'
|
||||
import { checkIsAppPPREnabled } from '../../server/lib/experimental/ppr'
|
||||
import { normalizeAppPath } from '../../shared/lib/router/utils/app-paths'
|
||||
import http from 'node:http'
|
||||
|
||||
@@ -166,6 +167,8 @@ async function collectRoutesForAnalyze(
|
||||
config.basePath ? `${config.basePath}${pathPrefix}` : pathPrefix
|
||||
)
|
||||
|
||||
const isAppPPREnabled = checkIsAppPPREnabled(config.experimental.ppr)
|
||||
|
||||
// Generate routes manifest
|
||||
const { routesManifest } = generateRoutesManifest({
|
||||
appType,
|
||||
@@ -176,6 +179,7 @@ async function collectRoutesForAnalyze(
|
||||
onMatchHeaders,
|
||||
rewrites,
|
||||
restrictedRedirectPaths,
|
||||
isAppPPREnabled,
|
||||
})
|
||||
|
||||
return routesManifest.dynamicRoutes
|
||||
|
||||
@@ -7,6 +7,7 @@ import type { ProxyMatcher } from './analysis/get-page-static-info'
|
||||
import type { Rewrite } from '../lib/load-custom-routes'
|
||||
import path from 'node:path'
|
||||
import { needsExperimentalReact } from '../lib/needs-experimental-react'
|
||||
import { checkIsAppPPREnabled } from '../server/lib/experimental/ppr'
|
||||
import {
|
||||
getNextConfigEnv,
|
||||
getNextPublicEnvironmentVariables,
|
||||
@@ -120,6 +121,7 @@ export function getDefineEnv({
|
||||
const nextPublicEnv = getNextPublicEnvironmentVariables()
|
||||
const nextConfigEnv = getNextConfigEnv(config)
|
||||
|
||||
const isPPREnabled = checkIsAppPPREnabled(config.experimental.ppr)
|
||||
const isCacheComponentsEnabled = !!config.cacheComponents
|
||||
const isUseCacheEnabled = !!config.experimental.useCache
|
||||
|
||||
@@ -171,6 +173,7 @@ export function getDefineEnv({
|
||||
'process.env.__NEXT_APP_NEW_SCROLL_HANDLER': Boolean(
|
||||
config.experimental.appNewScrollHandler
|
||||
),
|
||||
'process.env.__NEXT_PPR': isPPREnabled,
|
||||
'process.env.__NEXT_CACHE_COMPONENTS': isCacheComponentsEnabled,
|
||||
'process.env.__NEXT_EXPERIMENTAL_CACHED_NAVIGATIONS': Boolean(
|
||||
config.experimental.cachedNavigations
|
||||
|
||||
@@ -34,6 +34,7 @@ export interface GenerateRoutesManifestOptions {
|
||||
onMatchHeaders: CustomRoutes['headers']
|
||||
rewrites: CustomRoutes['rewrites']
|
||||
restrictedRedirectPaths: string[]
|
||||
isAppPPREnabled: boolean
|
||||
appType: 'pages' | 'app' | 'hybrid'
|
||||
deploymentId?: string
|
||||
}
|
||||
@@ -60,6 +61,7 @@ export function generateRoutesManifest(
|
||||
onMatchHeaders,
|
||||
rewrites,
|
||||
restrictedRedirectPaths,
|
||||
isAppPPREnabled,
|
||||
appType,
|
||||
deploymentId,
|
||||
} = options
|
||||
@@ -92,8 +94,6 @@ export function generateRoutesManifest(
|
||||
}
|
||||
}
|
||||
|
||||
const enableCacheComponents = Boolean(config.cacheComponents)
|
||||
|
||||
const routesManifest: RoutesManifest = {
|
||||
version: 3,
|
||||
pages404: true,
|
||||
@@ -130,9 +130,9 @@ export function generateRoutesManifest(
|
||||
prefetchSegmentHeader: NEXT_ROUTER_SEGMENT_PREFETCH_HEADER,
|
||||
prefetchSegmentSuffix: RSC_SEGMENT_SUFFIX,
|
||||
prefetchSegmentDirSuffix: RSC_SEGMENTS_DIR_SUFFIX,
|
||||
clientParamParsing: enableCacheComponents,
|
||||
clientParamParsing: config.cacheComponents ?? false,
|
||||
clientParamParsingOrigins: config.experimental.clientParamParsingOrigins,
|
||||
dynamicRSCPrerender: enableCacheComponents,
|
||||
dynamicRSCPrerender: isAppPPREnabled && config.cacheComponents === true,
|
||||
},
|
||||
rewriteHeaders: {
|
||||
pathHeader: NEXT_REWRITTEN_PATH_HEADER,
|
||||
@@ -140,7 +140,7 @@ export function generateRoutesManifest(
|
||||
},
|
||||
skipProxyUrlNormalize: config.skipProxyUrlNormalize,
|
||||
deploymentId: deploymentId || undefined,
|
||||
ppr: enableCacheComponents
|
||||
ppr: isAppPPREnabled
|
||||
? {
|
||||
chain: {
|
||||
headers: {
|
||||
|
||||
@@ -191,6 +191,10 @@ import { traceMemoryUsage } from '../lib/memory/trace'
|
||||
import { generateEncryptionKeyBase64 } from '../server/app-render/encryption-utils-server'
|
||||
import type { DeepReadonly } from '../shared/lib/deep-readonly'
|
||||
import uploadTrace from '../trace/upload-trace'
|
||||
import {
|
||||
checkIsAppPPREnabled,
|
||||
checkIsRoutePPREnabled,
|
||||
} from '../server/lib/experimental/ppr'
|
||||
import { FallbackMode, fallbackModeToFallbackField } from '../lib/fallback'
|
||||
import { RenderingMode } from './rendering-mode'
|
||||
import { InvariantError } from '../shared/lib/invariant-error'
|
||||
@@ -1520,6 +1524,7 @@ export default async function build(
|
||||
const isAuthInterruptsEnabled = Boolean(
|
||||
config.experimental.authInterrupts
|
||||
)
|
||||
const isAppPPREnabled = checkIsAppPPREnabled(config.experimental.ppr)
|
||||
|
||||
const routesManifestPath = path.join(distDir, ROUTES_MANIFEST)
|
||||
|
||||
@@ -1536,6 +1541,7 @@ export default async function build(
|
||||
onMatchHeaders,
|
||||
rewrites,
|
||||
restrictedRedirectPaths,
|
||||
isAppPPREnabled,
|
||||
deploymentId: config.deploymentId,
|
||||
})
|
||||
)
|
||||
@@ -2105,6 +2111,7 @@ export default async function build(
|
||||
locales: config.i18n?.locales,
|
||||
defaultLocale: config.i18n?.defaultLocale,
|
||||
nextConfigOutput: config.output,
|
||||
pprConfig: config.experimental.ppr,
|
||||
cacheLifeProfiles: config.cacheLife,
|
||||
buildId,
|
||||
deploymentId: config.deploymentId,
|
||||
@@ -2337,6 +2344,7 @@ export default async function build(
|
||||
: config.experimental.isrFlushToDisk,
|
||||
cacheMaxMemorySize: config.cacheMaxMemorySize,
|
||||
nextConfigOutput: config.output,
|
||||
pprConfig: config.experimental.ppr,
|
||||
cacheLifeProfiles: config.cacheLife,
|
||||
buildId,
|
||||
deploymentId: config.deploymentId,
|
||||
@@ -2741,6 +2749,10 @@ export default async function build(
|
||||
featureName: 'experimental/nextScriptWorkers',
|
||||
invocationCount: config.experimental.nextScriptWorkers ? 1 : 0,
|
||||
},
|
||||
{
|
||||
featureName: 'experimental/ppr',
|
||||
invocationCount: config.experimental.ppr ? 1 : 0,
|
||||
},
|
||||
{
|
||||
featureName: 'turbopackFileSystemCache',
|
||||
invocationCount: config.experimental?.turbopackFileSystemCacheForBuild
|
||||
@@ -2930,6 +2942,10 @@ export default async function build(
|
||||
const appConfig = appDefaultConfigs.get(originalAppPath)
|
||||
const isDynamicError = appConfig?.dynamic === 'error'
|
||||
|
||||
const isRoutePPREnabled: boolean = appConfig
|
||||
? checkIsRoutePPREnabled(config.experimental.ppr)
|
||||
: false
|
||||
|
||||
routes.forEach((route) => {
|
||||
// If the route has any dynamic root segments, we need to skip
|
||||
// rendering the route. This is because we don't support
|
||||
@@ -2954,6 +2970,7 @@ export default async function build(
|
||||
_fallbackRouteParams: route.fallbackRouteParams,
|
||||
_isDynamicError: isDynamicError,
|
||||
_isAppDir: true,
|
||||
_isRoutePPREnabled: isRoutePPREnabled,
|
||||
_allowEmptyStaticShell: !route.throwOnEmptyStaticShell,
|
||||
// A fallback shell can only be upgraded if at least one of
|
||||
// its fallback params is a `generateStaticParams` candidate.
|
||||
@@ -3124,7 +3141,8 @@ export default async function build(
|
||||
// When this is an app page and PPR is enabled, the route supports
|
||||
// partial pre-rendering.
|
||||
const isRoutePPREnabled: true | undefined =
|
||||
!isAppRouteHandler && Boolean(config.cacheComponents)
|
||||
!isAppRouteHandler &&
|
||||
checkIsRoutePPREnabled(config.experimental.ppr)
|
||||
? true
|
||||
: undefined
|
||||
|
||||
@@ -3306,7 +3324,7 @@ export default async function build(
|
||||
prerenderManifest.routes[route.pathname] = {
|
||||
initialStatus: status,
|
||||
initialHeaders: meta.headers,
|
||||
renderingMode: isAppCacheComponentsEnabled
|
||||
renderingMode: isAppPPREnabled
|
||||
? isRoutePPREnabled
|
||||
? RenderingMode.PARTIALLY_STATIC
|
||||
: RenderingMode.STATIC
|
||||
@@ -3404,7 +3422,7 @@ export default async function build(
|
||||
let dynamicRoute = routesManifest.dynamicRoutes.find(
|
||||
(r) => r.page === route.pathname
|
||||
)
|
||||
if (!isAppRouteHandler && isAppCacheComponentsEnabled) {
|
||||
if (!isAppRouteHandler && isAppPPREnabled) {
|
||||
// If the dynamic route wasn't found, then we need to create
|
||||
// it. This ensures that for each fallback shell there's an
|
||||
// entry in the app routes manifest which enables routing for
|
||||
@@ -3539,7 +3557,7 @@ export default async function build(
|
||||
experimentalPPR: isRoutePPREnabled,
|
||||
remainingPrerenderableParams:
|
||||
route.remainingPrerenderableParams,
|
||||
renderingMode: isAppCacheComponentsEnabled
|
||||
renderingMode: isAppPPREnabled
|
||||
? isRoutePPREnabled
|
||||
? RenderingMode.PARTIALLY_STATIC
|
||||
: RenderingMode.STATIC
|
||||
|
||||
@@ -789,7 +789,7 @@ export async function buildAppStaticPaths({
|
||||
fetchCacheKeyPrefix,
|
||||
nextConfigOutput,
|
||||
ComponentMod,
|
||||
isRoutePPREnabled,
|
||||
isRoutePPREnabled = false,
|
||||
buildId,
|
||||
deploymentId,
|
||||
rootParamKeys,
|
||||
|
||||
@@ -29,6 +29,7 @@ import {
|
||||
NodeNextRequest,
|
||||
NodeNextResponse,
|
||||
} from '../../server/base-http/node' with { 'turbopack-transition': 'next-server-utility' }
|
||||
import { checkIsAppPPREnabled } from '../../server/lib/experimental/ppr' with { 'turbopack-transition': 'next-server-utility' }
|
||||
import { isRSCRequestHeader } from '../../server/lib/is-rsc-request' with { 'turbopack-transition': 'next-server-utility' }
|
||||
import {
|
||||
getFallbackRouteParams,
|
||||
@@ -118,6 +119,7 @@ import * as entryBase from '../../server/app-render/entry-base' with { 'turbopac
|
||||
import { RedirectStatusCode } from '../../client/components/redirect-status-code' with { 'turbopack-transition': 'next-server-utility' }
|
||||
import { InvariantError } from '../../shared/lib/invariant-error' with { 'turbopack-transition': 'next-server-utility' }
|
||||
import { scheduleOnNextTick } from '../../lib/scheduler' with { 'turbopack-transition': 'next-server-utility' }
|
||||
import { isInterceptionRouteAppPath } from '../../shared/lib/router/utils/interception-routes' with { 'turbopack-transition': 'next-server-utility' }
|
||||
import { getSegmentParam } from '../../shared/lib/router/utils/get-segment-param' with { 'turbopack-transition': 'next-server-utility' }
|
||||
|
||||
export * from '../../server/app-render/entry-base' with { 'turbopack-transition': 'next-server-utility' }
|
||||
@@ -270,8 +272,16 @@ export async function handler(
|
||||
// We use the resolvedPathname instead of the parsedUrl.pathname because it
|
||||
// is not rewritten as resolvedPathname is. This will ensure that the correct
|
||||
// prerender info is used instead of using the original pathname as the
|
||||
// source.
|
||||
const prerenderMatch = routeModule.match(resolvedPathname, prerenderManifest)
|
||||
// source. If however PPR is enabled and cacheComponents is disabled, we
|
||||
// treat the pathname as dynamic. Currently, there's a bug in the PPR
|
||||
// implementation that incorrectly leaves %%drp placeholders in the output of
|
||||
// parallel routes. This is addressed with cacheComponents.
|
||||
const prerenderMatch =
|
||||
nextConfig.experimental.ppr &&
|
||||
!nextConfig.cacheComponents &&
|
||||
isInterceptionRouteAppPath(resolvedPathname)
|
||||
? null
|
||||
: routeModule.match(resolvedPathname, prerenderManifest)
|
||||
const prerenderInfo = prerenderMatch?.route ?? null
|
||||
|
||||
const isPrerendered = !!prerenderManifest.routes[resolvedPathname]
|
||||
@@ -300,7 +310,9 @@ export async function handler(
|
||||
* If the route being rendered is an app page, and the ppr feature has been
|
||||
* enabled, then the given route _could_ support PPR.
|
||||
*/
|
||||
const couldSupportPPR = Boolean(nextConfig.cacheComponents)
|
||||
const couldSupportPPR: boolean = checkIsAppPPREnabled(
|
||||
nextConfig.experimental.ppr
|
||||
)
|
||||
|
||||
// Stash postponed state for server actions when in minimal mode.
|
||||
// We extract it here so the RDC is available for the re-render after the action completes.
|
||||
@@ -899,6 +911,7 @@ export async function handler(
|
||||
validationLevel:
|
||||
nextConfig.experimental.instantInsights.validationLevel,
|
||||
experimental: {
|
||||
isRoutePPREnabled,
|
||||
expireTime: nextConfig.expireTime,
|
||||
staleTimes: nextConfig.experimental.staleTimes,
|
||||
dynamicOnHover: Boolean(nextConfig.experimental.dynamicOnHover),
|
||||
|
||||
@@ -166,6 +166,7 @@ async function requestHandler(
|
||||
cacheComponents: Boolean(nextConfig.cacheComponents),
|
||||
validationLevel: nextConfig.experimental.instantInsights.validationLevel,
|
||||
experimental: {
|
||||
isRoutePPREnabled: false,
|
||||
expireTime: nextConfig.expireTime,
|
||||
staleTimes: nextConfig.experimental.staleTimes,
|
||||
dynamicOnHover: Boolean(nextConfig.experimental.dynamicOnHover),
|
||||
|
||||
@@ -2,6 +2,8 @@ import type {
|
||||
NextConfigComplete,
|
||||
NextConfigRuntime,
|
||||
} from '../server/config-shared'
|
||||
import type { ExperimentalPPRConfig } from '../server/lib/experimental/ppr'
|
||||
import { checkIsRoutePPREnabled } from '../server/lib/experimental/ppr'
|
||||
import type { AssetBinding } from './webpack/loaders/get-module-build-info'
|
||||
import type { ServerRuntime } from '../types'
|
||||
import type { BuildManifest } from '../server/get-page-files'
|
||||
@@ -703,6 +705,7 @@ export async function isPageStatic({
|
||||
cacheHandler,
|
||||
cacheHandlers,
|
||||
cacheLifeProfiles,
|
||||
pprConfig,
|
||||
buildId,
|
||||
deploymentId,
|
||||
clientAssetToken,
|
||||
@@ -732,6 +735,7 @@ export async function isPageStatic({
|
||||
[profile: string]: import('../server/use-cache/cache-life').CacheLife
|
||||
}
|
||||
nextConfigOutput: 'standalone' | 'export' | undefined
|
||||
pprConfig: ExperimentalPPRConfig | undefined
|
||||
buildId: string
|
||||
deploymentId: string
|
||||
clientAssetToken: string
|
||||
@@ -859,8 +863,12 @@ export async function isPageStatic({
|
||||
|
||||
rootParamKeys = collectRootParamKeys(routeModule)
|
||||
|
||||
// A page supports partial prerendering if it is an app page and either
|
||||
// the whole app has PPR enabled or this page has PPR enabled when we're
|
||||
// in incremental mode.
|
||||
isRoutePPREnabled =
|
||||
routeModule.definition.kind === RouteKind.APP_PAGE && cacheComponents
|
||||
routeModule.definition.kind === RouteKind.APP_PAGE &&
|
||||
checkIsRoutePPREnabled(pprConfig)
|
||||
|
||||
// If force dynamic was set and we don't have PPR enabled, then set the
|
||||
// revalidate to 0.
|
||||
|
||||
@@ -20,6 +20,7 @@ function hasFallbackRouteParams(): boolean {
|
||||
switch (workUnitStore.type) {
|
||||
case 'prerender':
|
||||
case 'prerender-client':
|
||||
case 'prerender-ppr':
|
||||
case 'validation-client':
|
||||
const fallbackParams = workUnitStore.fallbackRouteParams
|
||||
return fallbackParams ? fallbackParams.size > 0 : false
|
||||
|
||||
@@ -190,7 +190,10 @@ export async function fetchServerResponse(
|
||||
|
||||
// Typically, during a navigation, we decode the response using Flight's
|
||||
// `createFromFetch` API, which accepts a `fetch` promise.
|
||||
const shouldImmediatelyDecode = true
|
||||
// TODO: Remove this check once the old PPR flag is removed
|
||||
const isLegacyPPR =
|
||||
process.env.__NEXT_PPR && !process.env.__NEXT_CACHE_COMPONENTS
|
||||
const shouldImmediatelyDecode = !isLegacyPPR
|
||||
const res = await createFetch<NavigationFlightResponse>(
|
||||
url,
|
||||
headers,
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
import { isHangingPromiseRejectionError } from '../../server/dynamic-rendering-utils'
|
||||
import { isPostpone } from '../../server/lib/router-utils/is-postpone'
|
||||
import { isBailoutToCSRError } from '../../shared/lib/lazy-dynamic/bailout-to-csr'
|
||||
import { isNextRouterError } from './is-next-router-error'
|
||||
import { isPrerenderInterruptedError } from '../../server/app-render/dynamic-rendering'
|
||||
import {
|
||||
isDynamicPostpone,
|
||||
isPrerenderInterruptedError,
|
||||
} from '../../server/app-render/dynamic-rendering'
|
||||
import { isDynamicServerError } from './hooks-server-context'
|
||||
|
||||
export function unstable_rethrow(error: unknown): void {
|
||||
@@ -9,6 +13,8 @@ export function unstable_rethrow(error: unknown): void {
|
||||
isNextRouterError(error) ||
|
||||
isBailoutToCSRError(error) ||
|
||||
isDynamicServerError(error) ||
|
||||
isDynamicPostpone(error) ||
|
||||
isPostpone(error) ||
|
||||
isHangingPromiseRejectionError(error) ||
|
||||
isPrerenderInterruptedError(error)
|
||||
) {
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import { isDynamicServerError } from '../../client/components/hooks-server-context'
|
||||
import { isBailoutToCSRError } from '../../shared/lib/lazy-dynamic/bailout-to-csr'
|
||||
import { isNextRouterError } from '../../client/components/is-next-router-error'
|
||||
import { isDynamicPostpone } from '../../server/app-render/dynamic-rendering'
|
||||
|
||||
export const isDynamicUsageError = (err: unknown) =>
|
||||
isDynamicServerError(err) ||
|
||||
isBailoutToCSRError(err) ||
|
||||
isNextRouterError(err)
|
||||
isNextRouterError(err) ||
|
||||
isDynamicPostpone(err)
|
||||
|
||||
@@ -880,11 +880,9 @@ async function exportAppImpl(
|
||||
}
|
||||
|
||||
// Export mode provide static outputs that are not compatible with PPR mode.
|
||||
if (!options.buildExport && nextConfig.cacheComponents) {
|
||||
if (!options.buildExport && nextConfig.experimental.ppr) {
|
||||
// TODO: add message
|
||||
throw new Error(
|
||||
'Invariant: Cache Components cannot be enabled in export mode'
|
||||
)
|
||||
throw new Error('Invariant: PPR cannot be enabled in export mode')
|
||||
}
|
||||
|
||||
// copy prerendered routes to outDir
|
||||
|
||||
@@ -105,7 +105,7 @@ export async function exportAppPage(
|
||||
} = metadata
|
||||
|
||||
// Ensure we don't postpone without having PPR enabled.
|
||||
if (postponed && !renderOpts.cacheComponents) {
|
||||
if (postponed && !renderOpts.experimental.isRoutePPREnabled) {
|
||||
throw new Error('Invariant: page postponed without PPR being enabled')
|
||||
}
|
||||
|
||||
@@ -145,7 +145,8 @@ export async function exportAppPage(
|
||||
const hasFallbackParams =
|
||||
fallbackRouteParams != null && fallbackRouteParams.size > 0
|
||||
const shouldWriteRsc =
|
||||
!renderOpts.cacheComponents || (!postponed && !hasFallbackParams)
|
||||
!renderOpts.experimental.isRoutePPREnabled ||
|
||||
(!postponed && !hasFallbackParams)
|
||||
hasStaticRsc = shouldWriteRsc
|
||||
|
||||
// With PPR enabled, we normally skip writing .rsc because it may contain
|
||||
@@ -197,7 +198,7 @@ export async function exportAppPage(
|
||||
// When PPR is enabled, we don't always send 200 for routes that have been
|
||||
// pregenerated, so we should grab the status code from the mocked
|
||||
// response.
|
||||
let status: number | undefined = renderOpts.cacheComponents
|
||||
let status: number | undefined = renderOpts.experimental.isRoutePPREnabled
|
||||
? res.statusCode
|
||||
: undefined
|
||||
|
||||
|
||||
@@ -36,6 +36,7 @@ import { exportAppPage } from './routes/app-page'
|
||||
import { exportPagesPage } from './routes/pages'
|
||||
import { getParams } from './helpers/get-params'
|
||||
import { createIncrementalCache } from './helpers/create-incremental-cache'
|
||||
import { isPostpone } from '../server/lib/router-utils/is-postpone'
|
||||
import { isDynamicUsageError } from './helpers/is-dynamic-usage-error'
|
||||
import { isBailoutToCSRError } from '../shared/lib/lazy-dynamic/bailout-to-csr'
|
||||
import {
|
||||
@@ -108,6 +109,10 @@ async function exportPageImpl(
|
||||
// Check if this should error when dynamic usage is detected.
|
||||
_isDynamicError: isDynamicError = false,
|
||||
|
||||
// If this page supports partial prerendering, then we need to pass that to
|
||||
// the renderOpts.
|
||||
_isRoutePPREnabled: isRoutePPREnabled,
|
||||
|
||||
// Configure the rendering of the page to allow that an empty static shell
|
||||
// is generated while rendering using PPR and Cache Components.
|
||||
_allowEmptyStaticShell: allowEmptyStaticShell = false,
|
||||
@@ -275,6 +280,10 @@ async function exportPageImpl(
|
||||
allowEmptyStaticShell,
|
||||
runInstantValidation,
|
||||
isFallbackUpgradeable,
|
||||
experimental: {
|
||||
...commonRenderOpts.experimental,
|
||||
isRoutePPREnabled,
|
||||
},
|
||||
renderResumeDataCache,
|
||||
}
|
||||
|
||||
@@ -610,6 +619,12 @@ async function exportPage(
|
||||
}
|
||||
|
||||
process.on('unhandledRejection', (err: unknown) => {
|
||||
// if it's a postpone error, it'll be handled later
|
||||
// when the postponed promise is actually awaited.
|
||||
if (isPostpone(err)) {
|
||||
return
|
||||
}
|
||||
|
||||
// we don't want to log these errors
|
||||
if (isDynamicUsageError(err)) {
|
||||
return
|
||||
|
||||
@@ -16,6 +16,7 @@ import { isHTTPAccessFallbackError } from '../../client/components/http-access-f
|
||||
import type { MetadataContext } from './types/resolvers'
|
||||
import { createServerSearchParamsForMetadata } from '../../server/request/search-params'
|
||||
import { createServerPathnameForMetadata } from '../../server/request/pathname'
|
||||
import { isPostpone } from '../../server/lib/router-utils/is-postpone'
|
||||
import {
|
||||
workUnitAsyncStorage,
|
||||
getStagedRenderingController,
|
||||
@@ -90,6 +91,12 @@ export function createMetadataComponents({
|
||||
isRuntimePrefetchable,
|
||||
errorType
|
||||
).catch((viewportErr) => {
|
||||
// When Legacy PPR is enabled viewport can reject with a Postpone type
|
||||
// This will go away once Legacy PPR is removed and dynamic metadata will
|
||||
// stay pending until after the prerender is complete when it is dynamic
|
||||
if (isPostpone(viewportErr)) {
|
||||
throw viewportErr
|
||||
}
|
||||
if (!errorType && isHTTPAccessFallbackError(viewportErr)) {
|
||||
return getNotFoundViewport(
|
||||
tree,
|
||||
@@ -137,6 +144,12 @@ export function createMetadataComponents({
|
||||
isRuntimePrefetchable,
|
||||
errorType
|
||||
).catch((metadataErr) => {
|
||||
// When Legacy PPR is enabled metadata can reject with a Postpone type
|
||||
// This will go away once Legacy PPR is removed and dynamic metadata will
|
||||
// stay pending until after the prerender is complete when it is dynamic
|
||||
if (isPostpone(metadataErr)) {
|
||||
throw metadataErr
|
||||
}
|
||||
if (!errorType && isHTTPAccessFallbackError(metadataErr)) {
|
||||
return getNotFoundMetadata(
|
||||
tree,
|
||||
|
||||
@@ -2139,7 +2139,8 @@ async function getRSCPayload(
|
||||
//
|
||||
// See similar comment in create-component-tree.tsx for more context.
|
||||
const isPossiblyPartialHead =
|
||||
workStore.isStaticGeneration && ctx.renderOpts.cacheComponents === true
|
||||
workStore.isStaticGeneration &&
|
||||
ctx.renderOpts.experimental.isRoutePPREnabled === true
|
||||
|
||||
return maybeAppendBuildIdToRSCPayload(ctx, {
|
||||
// See the comment above the `Preloads` component (below) for why this is part of the payload
|
||||
@@ -2302,7 +2303,8 @@ async function getErrorRSCPayload(
|
||||
)
|
||||
|
||||
const isPossiblyPartialHead =
|
||||
workStore.isStaticGeneration && ctx.renderOpts.cacheComponents === true
|
||||
workStore.isStaticGeneration &&
|
||||
ctx.renderOpts.experimental.isRoutePPREnabled === true
|
||||
|
||||
return maybeAppendBuildIdToRSCPayload(ctx, {
|
||||
c: prepareInitialCanonicalUrl(url),
|
||||
@@ -2489,6 +2491,7 @@ function installGlobalModuleLoadingHandlers(
|
||||
case 'cache':
|
||||
case 'private-cache':
|
||||
return true
|
||||
case 'prerender-ppr':
|
||||
case 'prerender-legacy':
|
||||
case 'request':
|
||||
case 'unstable-cache':
|
||||
@@ -3064,7 +3067,7 @@ export const renderToHTMLOrFlight: AppPageRender = (
|
||||
// We read these values from the request object as, in certain cases,
|
||||
// base-server will strip them to opt into different rendering behavior.
|
||||
const parsedRequestHeaders = parseRequestHeaders(req.headers, {
|
||||
isRoutePPREnabled: renderOpts.cacheComponents === true,
|
||||
isRoutePPREnabled: renderOpts.experimental.isRoutePPREnabled === true,
|
||||
previewModeId: renderOpts.previewProps?.previewModeId,
|
||||
})
|
||||
|
||||
@@ -7213,7 +7216,7 @@ async function prerenderToStream(
|
||||
|
||||
const { reactServerErrorsByDigest } = workStore
|
||||
// We don't report errors during prerendering through our instrumentation hooks
|
||||
const reportErrors = !cacheComponents
|
||||
const reportErrors = !experimental.isRoutePPREnabled
|
||||
function onHTMLRenderRSCError(err: DigestedError, silenceLog: boolean) {
|
||||
if (reportErrors) {
|
||||
return onInstrumentationRequestError?.(
|
||||
@@ -8156,6 +8159,248 @@ async function prerenderToStream(
|
||||
collectedTags: finalServerPrerenderStore.tags,
|
||||
renderResumeDataCache: createRenderResumeDataCache(resumeDataCache),
|
||||
}
|
||||
} else if (experimental.isRoutePPREnabled) {
|
||||
// We're statically generating with PPR and need to do dynamic tracking
|
||||
let dynamicTracking = createDynamicTrackingState(isDebugDynamicAccesses)
|
||||
|
||||
const resumeDataCache = createPrerenderResumeDataCache()
|
||||
const pprReactServerPrerenderStore: PrerenderStore = (prerenderStore = {
|
||||
type: 'prerender-ppr',
|
||||
phase: 'render',
|
||||
rootParams,
|
||||
fallbackRouteParams,
|
||||
implicitTags,
|
||||
dynamicTracking,
|
||||
revalidate: INFINITE_CACHE,
|
||||
expire: INFINITE_CACHE,
|
||||
stale: INFINITE_CACHE,
|
||||
tags: [...implicitTags.tags],
|
||||
resumeDataCache,
|
||||
})
|
||||
const RSCPayload = await workUnitAsyncStorage.run(
|
||||
pprReactServerPrerenderStore,
|
||||
getRSCPayload,
|
||||
tree,
|
||||
ctx,
|
||||
{ is404: res.statusCode === 404 }
|
||||
)
|
||||
let reactServerResult: ReactServerPrerenderResult
|
||||
reactServerResult = reactServerPrerenderResult =
|
||||
await createReactServerPrerenderResultFromRender(
|
||||
workUnitAsyncStorage.run(
|
||||
pprReactServerPrerenderStore,
|
||||
renderFlightStream,
|
||||
ComponentMod,
|
||||
RSCPayload,
|
||||
clientModules,
|
||||
{
|
||||
filterStackFrame,
|
||||
onError: serverComponentsErrorHandler,
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
const ssrPrerenderStore: PrerenderStore = {
|
||||
type: 'prerender-ppr',
|
||||
phase: 'render',
|
||||
rootParams,
|
||||
fallbackRouteParams,
|
||||
implicitTags,
|
||||
dynamicTracking,
|
||||
revalidate: INFINITE_CACHE,
|
||||
expire: INFINITE_CACHE,
|
||||
stale: INFINITE_CACHE,
|
||||
tags: [...implicitTags.tags],
|
||||
resumeDataCache,
|
||||
}
|
||||
const pprOnHeaders = createOnHeadersCallback(appendHeader)
|
||||
const { prelude: unprocessedPrelude, postponed } =
|
||||
await workUnitAsyncStorage.run(
|
||||
ssrPrerenderStore,
|
||||
getClientPrerender,
|
||||
// eslint-disable-next-line @next/internal/no-ambiguous-jsx
|
||||
<App
|
||||
reactServerStream={reactServerResult.asUnclosingStream()}
|
||||
reactDebugStream={undefined}
|
||||
debugEndTime={undefined}
|
||||
preinitScripts={preinitScripts}
|
||||
ServerInsertedHTMLProvider={ServerInsertedHTMLProvider}
|
||||
nonce={nonce}
|
||||
images={ctx.renderOpts.images}
|
||||
/>,
|
||||
{
|
||||
onError: htmlRendererErrorHandler,
|
||||
onHeaders: pprOnHeaders,
|
||||
maxHeadersLength: reactMaxHeadersLength,
|
||||
bootstrapScripts: [bootstrapScript],
|
||||
}
|
||||
)
|
||||
const getServerInsertedHTML = makeGetServerInsertedHTML({
|
||||
polyfills,
|
||||
renderServerInsertedHTML,
|
||||
serverCapturedErrors: allCapturedErrors,
|
||||
basePath,
|
||||
tracingMetadata: tracingMetadata,
|
||||
})
|
||||
|
||||
// After awaiting here we've waited for the entire RSC render to complete. Crucially this means
|
||||
// that when we detect whether we've used dynamic APIs below we know we'll have picked up even
|
||||
// parts of the React Server render that might not be used in the SSR render.
|
||||
const flightData = await streamToBuffer(reactServerResult.asStream())
|
||||
|
||||
if (shouldGenerateStaticFlightData(workStore)) {
|
||||
metadata.flightData = flightData
|
||||
await collectSegmentData(
|
||||
flightData,
|
||||
ssrPrerenderStore,
|
||||
ComponentMod,
|
||||
renderOpts,
|
||||
ctx.pagePath,
|
||||
metadata
|
||||
)
|
||||
}
|
||||
|
||||
const { prelude, preludeIsEmpty } =
|
||||
await processPreludeOp(unprocessedPrelude)
|
||||
|
||||
/**
|
||||
* When prerendering there are three outcomes to consider
|
||||
*
|
||||
* Dynamic HTML: The prerender has dynamic holes (caused by using Next.js Dynamic Rendering APIs)
|
||||
* We will need to resume this result when requests are handled and we don't include
|
||||
* any server inserted HTML or inlined flight data in the static HTML
|
||||
*
|
||||
* Dynamic Data: The prerender has no dynamic holes but dynamic APIs were used. We will not
|
||||
* resume this render when requests are handled but we will generate new inlined
|
||||
* flight data since it is dynamic and differences may end up reconciling on the client
|
||||
*
|
||||
* Static: The prerender has no dynamic holes and no dynamic APIs were used. We statically encode
|
||||
* all server inserted HTML and flight data
|
||||
*/
|
||||
// First we check if we have any dynamic holes in our HTML prerender
|
||||
if (accessedDynamicData(dynamicTracking.dynamicAccesses)) {
|
||||
if (postponed != null) {
|
||||
// Dynamic HTML case.
|
||||
metadata.postponed = await getDynamicHTMLPostponedState(
|
||||
postponed,
|
||||
preludeIsEmpty
|
||||
? DynamicHTMLPreludeState.Empty
|
||||
: DynamicHTMLPreludeState.Full,
|
||||
fallbackRouteParams,
|
||||
resumeDataCache,
|
||||
cacheComponents
|
||||
)
|
||||
} else {
|
||||
// Dynamic Data case.
|
||||
metadata.postponed = await getDynamicDataPostponedState(
|
||||
resumeDataCache,
|
||||
cacheComponents
|
||||
)
|
||||
}
|
||||
// Regardless of whether this is the Dynamic HTML or Dynamic Data case we need to ensure we include
|
||||
// server inserted html in the static response because the html that is part of the prerender may depend on it
|
||||
// It is possible in the set of stream transforms for Dynamic HTML vs Dynamic Data may differ but currently both states
|
||||
// require the same set so we unify the code path here
|
||||
reactServerResult.consume()
|
||||
const pprDynamicOpts = {
|
||||
getServerInsertedHTML,
|
||||
getServerInsertedMetadata,
|
||||
deploymentId: ctx.sharedContext.deploymentId,
|
||||
}
|
||||
return {
|
||||
digestErrorsMap: reactServerErrorsByDigest,
|
||||
ssrErrors: allCapturedErrors,
|
||||
stream: await continueDynamicPrerender(prelude, pprDynamicOpts),
|
||||
dynamicAccess: dynamicTracking.dynamicAccesses,
|
||||
// TODO: Should this include the SSR pass?
|
||||
collectedRevalidate: pprReactServerPrerenderStore.revalidate,
|
||||
collectedExpire: pprReactServerPrerenderStore.expire,
|
||||
collectedStale: selectStaleTime(pprReactServerPrerenderStore.stale),
|
||||
collectedTags: pprReactServerPrerenderStore.tags,
|
||||
}
|
||||
} else if (fallbackRouteParams && fallbackRouteParams.size > 0) {
|
||||
// Rendering the fallback case.
|
||||
metadata.postponed = await getDynamicDataPostponedState(
|
||||
resumeDataCache,
|
||||
cacheComponents
|
||||
)
|
||||
|
||||
const pprFallbackDynamicOpts = {
|
||||
getServerInsertedHTML,
|
||||
getServerInsertedMetadata,
|
||||
deploymentId: ctx.sharedContext.deploymentId,
|
||||
}
|
||||
return {
|
||||
digestErrorsMap: reactServerErrorsByDigest,
|
||||
ssrErrors: allCapturedErrors,
|
||||
stream: await continueDynamicPrerender(
|
||||
prelude,
|
||||
pprFallbackDynamicOpts
|
||||
),
|
||||
dynamicAccess: dynamicTracking.dynamicAccesses,
|
||||
// TODO: Should this include the SSR pass?
|
||||
collectedRevalidate: pprReactServerPrerenderStore.revalidate,
|
||||
collectedExpire: pprReactServerPrerenderStore.expire,
|
||||
collectedStale: selectStaleTime(pprReactServerPrerenderStore.stale),
|
||||
collectedTags: pprReactServerPrerenderStore.tags,
|
||||
}
|
||||
} else {
|
||||
// Static case
|
||||
// We still have not used any dynamic APIs. At this point we can produce an entirely static prerender response
|
||||
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: AnyStream = 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 foreverStream = createPendingStream()
|
||||
const resumePrelude = await resumeAndAbort(
|
||||
// eslint-disable-next-line @next/internal/no-ambiguous-jsx
|
||||
<App
|
||||
reactServerStream={foreverStream}
|
||||
reactDebugStream={undefined}
|
||||
debugEndTime={undefined}
|
||||
preinitScripts={() => {}}
|
||||
ServerInsertedHTMLProvider={ServerInsertedHTMLProvider}
|
||||
nonce={nonce}
|
||||
images={ctx.renderOpts.images}
|
||||
/>,
|
||||
JSON.parse(JSON.stringify(postponed)),
|
||||
{
|
||||
signal: createRenderInBrowserAbortSignal(),
|
||||
onError: htmlRendererErrorHandler,
|
||||
nonce,
|
||||
}
|
||||
)
|
||||
// First we write everything from the prerender, then we write everything from the aborted resume render
|
||||
htmlStream = chainStreams(prelude, resumePrelude)
|
||||
}
|
||||
|
||||
return {
|
||||
digestErrorsMap: reactServerErrorsByDigest,
|
||||
ssrErrors: allCapturedErrors,
|
||||
stream: await continueStaticPrerender(htmlStream, {
|
||||
inlinedDataStream: createInlinedDataStream(
|
||||
reactServerResult.consumeAsStream(),
|
||||
nonce,
|
||||
formState
|
||||
),
|
||||
getServerInsertedHTML,
|
||||
getServerInsertedMetadata,
|
||||
deploymentId: ctx.sharedContext.deploymentId,
|
||||
}),
|
||||
dynamicAccess: dynamicTracking.dynamicAccesses,
|
||||
// TODO: Should this include the SSR pass?
|
||||
collectedRevalidate: pprReactServerPrerenderStore.revalidate,
|
||||
collectedExpire: pprReactServerPrerenderStore.expire,
|
||||
collectedStale: selectStaleTime(pprReactServerPrerenderStore.stale),
|
||||
collectedTags: pprReactServerPrerenderStore.tags,
|
||||
}
|
||||
}
|
||||
} else {
|
||||
const prerenderLegacyStore: PrerenderStore = (prerenderStore = {
|
||||
type: 'prerender-legacy',
|
||||
|
||||
@@ -119,7 +119,7 @@ async function createComponentTreeInternal(
|
||||
isRoot: boolean
|
||||
): Promise<CacheNodeSeedData> {
|
||||
const {
|
||||
renderOpts: { nextConfigOutput, cacheComponents },
|
||||
renderOpts: { nextConfigOutput, experimental, cacheComponents },
|
||||
workStore,
|
||||
componentMod: {
|
||||
createElement,
|
||||
@@ -135,6 +135,7 @@ async function createComponentTreeInternal(
|
||||
createServerParamsForServerSegment,
|
||||
createPrerenderParamsForClientSegment,
|
||||
serverHooks: { DynamicServerError },
|
||||
Postpone,
|
||||
},
|
||||
pagePath,
|
||||
getDynamicParamFromSegment,
|
||||
@@ -263,15 +264,6 @@ async function createComponentTreeInternal(
|
||||
: []
|
||||
|
||||
let dynamic = layoutOrPageMod?.dynamic
|
||||
if (typeof dynamic === 'string') {
|
||||
if (cacheComponents) {
|
||||
// TODO: Remove this since this should be caught during compilation.
|
||||
// Left as a guard in case usage of Postpone was attempted
|
||||
throw new Error(
|
||||
`Route segment config "dynamic" is not compatible with \`nextConfig.cacheComponents\`. Please remove it.`
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if (nextConfigOutput === 'export') {
|
||||
if (!dynamic || dynamic === 'auto') {
|
||||
@@ -293,7 +285,10 @@ async function createComponentTreeInternal(
|
||||
} else if (dynamic === 'force-dynamic') {
|
||||
workStore.forceDynamic = true
|
||||
|
||||
if (workStore.isStaticGeneration) {
|
||||
// TODO: (PPR) remove this bailout once PPR is the default
|
||||
if (workStore.isStaticGeneration && !experimental.isRoutePPREnabled) {
|
||||
// If the postpone API isn't available, we can't postpone the render and
|
||||
// therefore we can't use the dynamic API.
|
||||
const err = new DynamicServerError(
|
||||
`Page with \`dynamic = "force-dynamic"\` won't be rendered statically.`
|
||||
)
|
||||
@@ -325,6 +320,7 @@ async function createComponentTreeInternal(
|
||||
case 'prerender':
|
||||
case 'prerender-runtime':
|
||||
case 'prerender-legacy':
|
||||
case 'prerender-ppr':
|
||||
if (workUnitStore.revalidate > defaultRevalidate) {
|
||||
workUnitStore.revalidate = defaultRevalidate
|
||||
}
|
||||
@@ -351,7 +347,7 @@ async function createComponentTreeInternal(
|
||||
defaultRevalidate === 0 &&
|
||||
// If the postpone API isn't available, we can't postpone the render and
|
||||
// therefore we can't use the dynamic API.
|
||||
!cacheComponents
|
||||
!experimental.isRoutePPREnabled
|
||||
) {
|
||||
const dynamicUsageDescription = `revalidate: 0 configured ${segment}`
|
||||
workStore.dynamicUsageDescription = dynamicUsageDescription
|
||||
@@ -375,6 +371,7 @@ async function createComponentTreeInternal(
|
||||
case 'prerender':
|
||||
case 'prerender-runtime':
|
||||
case 'prerender-legacy':
|
||||
case 'prerender-ppr':
|
||||
if (workUnitStore.stale > pageStaleTime) {
|
||||
workUnitStore.stale = pageStaleTime
|
||||
}
|
||||
@@ -417,7 +414,7 @@ async function createComponentTreeInternal(
|
||||
// For dynamic requests, this must always be `false` because dynamic responses
|
||||
// are never partial.
|
||||
const isPossiblyPartialResponse =
|
||||
isStaticGeneration && cacheComponents === true
|
||||
isStaticGeneration && experimental.isRoutePPREnabled === true
|
||||
|
||||
const LayoutOrPage: ComponentType<any> | undefined = layoutOrPageMod
|
||||
? interopDefault(layoutOrPageMod)
|
||||
@@ -579,7 +576,7 @@ async function createComponentTreeInternal(
|
||||
// possible during both prefetches and dynamic navigations. But during
|
||||
// the beta period, we should be clear about this trade off in our
|
||||
// communications.
|
||||
!cacheComponents
|
||||
!experimental.isRoutePPREnabled
|
||||
) {
|
||||
// Don't prefetch this child. This will trigger a lazy fetch by the
|
||||
// client router.
|
||||
@@ -774,15 +771,43 @@ async function createComponentTreeInternal(
|
||||
}
|
||||
|
||||
const Component = MaybeComponent
|
||||
// If force-dynamic is used and the current render supports postponing, we
|
||||
// replace it with a node that will postpone the render. This ensures that the
|
||||
// postpone is invoked during the react render phase and not during the next
|
||||
// render phase.
|
||||
// @TODO this does not actually do what it seems like it would or should do. The idea is that
|
||||
// if we are rendering in a force-dynamic mode and we can postpone we should only make the segments
|
||||
// that ask for force-dynamic to be dynamic, allowing other segments to still prerender. However
|
||||
// because this comes after the children traversal and the static generation store is mutated every segment
|
||||
// along the parent path of a force-dynamic segment will hit this condition effectively making the entire
|
||||
// render force-dynamic. We should refactor this function so that we can correctly track which segments
|
||||
// need to be dynamic
|
||||
if (
|
||||
workStore.isStaticGeneration &&
|
||||
workStore.forceDynamic &&
|
||||
cacheComponents
|
||||
experimental.isRoutePPREnabled
|
||||
) {
|
||||
// TODO: Remove this since this should be caught during compilation.
|
||||
// Left as a guard in case usage of Postpone was attempted
|
||||
throw new Error(
|
||||
`Route segment config "dynamic" is not compatible with \`nextConfig.cacheComponents\`. Please remove it.`
|
||||
return createSeedData(
|
||||
ctx,
|
||||
createElement(
|
||||
Fragment,
|
||||
{
|
||||
key: cacheNodeKey,
|
||||
},
|
||||
createElement(Postpone, {
|
||||
reason: 'dynamic = "force-dynamic" was used',
|
||||
route: workStore.route,
|
||||
}),
|
||||
layerAssets
|
||||
),
|
||||
parallelRouteCacheNodeSeedData,
|
||||
loadingData,
|
||||
true,
|
||||
isRuntimePrefetchable,
|
||||
|
||||
// force-dynamic postpones without rendering the component, so no params
|
||||
// are accessed. The vary params are empty.
|
||||
emptyVaryParamsAccumulator
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1306,6 +1331,7 @@ function createSeedData(
|
||||
case 'prerender':
|
||||
case 'prerender-client':
|
||||
case 'validation-client':
|
||||
case 'prerender-ppr':
|
||||
case 'prerender-legacy':
|
||||
case 'cache':
|
||||
case 'private-cache':
|
||||
|
||||
@@ -29,6 +29,7 @@ import type {
|
||||
PrerenderStoreModernServer,
|
||||
} from '../app-render/work-unit-async-storage.external'
|
||||
|
||||
// Once postpone is in stable we should switch to importing the postpone export directly
|
||||
import React from 'react'
|
||||
|
||||
import { DynamicServerError } from '../../client/components/hooks-server-context'
|
||||
@@ -78,6 +79,8 @@ import {
|
||||
} from './instant-validation/boundary-tracking'
|
||||
import type { InstantValidationSampleTracking } from './instant-validation/instant-samples'
|
||||
|
||||
const hasPostpone = typeof React.unstable_postpone === 'function'
|
||||
|
||||
export type DynamicAccess = {
|
||||
/**
|
||||
* If debugging, this will contain the stack trace of where the dynamic access
|
||||
@@ -179,6 +182,7 @@ export function markCurrentScopeAsDynamic(
|
||||
// A private cache scope is already dynamic by definition.
|
||||
return
|
||||
case 'prerender-legacy':
|
||||
case 'prerender-ppr':
|
||||
case 'request':
|
||||
case 'generate-static-params':
|
||||
break
|
||||
@@ -200,6 +204,12 @@ export function markCurrentScopeAsDynamic(
|
||||
|
||||
if (workUnitStore) {
|
||||
switch (workUnitStore.type) {
|
||||
case 'prerender-ppr':
|
||||
return postponeWithTracking(
|
||||
store.route,
|
||||
expression,
|
||||
workUnitStore.dynamicTracking
|
||||
)
|
||||
case 'prerender-legacy':
|
||||
workUnitStore.revalidate = 0
|
||||
|
||||
@@ -271,6 +281,7 @@ export function trackDynamicDataInDynamicRender(workUnitStore: WorkUnitStore) {
|
||||
case 'prerender':
|
||||
case 'prerender-runtime':
|
||||
case 'prerender-legacy':
|
||||
case 'prerender-ppr':
|
||||
case 'prerender-client':
|
||||
case 'validation-client':
|
||||
case 'generate-static-params':
|
||||
@@ -368,6 +379,78 @@ export function abortAndThrowOnSynchronousRequestDataAccess(
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* This component will call `React.postpone` that throws the postponed error.
|
||||
*/
|
||||
type PostponeProps = {
|
||||
reason: string
|
||||
route: string
|
||||
}
|
||||
export function Postpone({ reason, route }: PostponeProps): never {
|
||||
const prerenderStore = workUnitAsyncStorage.getStore()
|
||||
const dynamicTracking =
|
||||
prerenderStore && prerenderStore.type === 'prerender-ppr'
|
||||
? prerenderStore.dynamicTracking
|
||||
: null
|
||||
postponeWithTracking(route, reason, dynamicTracking)
|
||||
}
|
||||
|
||||
export function postponeWithTracking(
|
||||
route: string,
|
||||
expression: string,
|
||||
dynamicTracking: null | DynamicTrackingState
|
||||
): never {
|
||||
assertPostpone()
|
||||
if (dynamicTracking) {
|
||||
dynamicTracking.dynamicAccesses.push({
|
||||
// When we aren't debugging, we don't need to create another error for the
|
||||
// stack trace.
|
||||
stack: dynamicTracking.isDebugDynamicAccesses
|
||||
? new Error().stack
|
||||
: undefined,
|
||||
expression,
|
||||
})
|
||||
}
|
||||
|
||||
React.unstable_postpone(createPostponeReason(route, expression))
|
||||
}
|
||||
|
||||
function createPostponeReason(route: string, expression: string) {
|
||||
return (
|
||||
`Route ${route} needs to bail out of prerendering at this point because it used ${expression}. ` +
|
||||
`React throws this special object to indicate where. It should not be caught by ` +
|
||||
`your own try/catch. Learn more: https://nextjs.org/docs/messages/ppr-caught-error`
|
||||
)
|
||||
}
|
||||
|
||||
export function isDynamicPostpone(err: unknown) {
|
||||
if (
|
||||
typeof err === 'object' &&
|
||||
err !== null &&
|
||||
typeof (err as any).message === 'string'
|
||||
) {
|
||||
return isDynamicPostponeReason((err as any).message)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
function isDynamicPostponeReason(reason: string) {
|
||||
return (
|
||||
reason.includes(
|
||||
'needs to bail out of prerendering at this point because it used'
|
||||
) &&
|
||||
reason.includes(
|
||||
'Learn more: https://nextjs.org/docs/messages/ppr-caught-error'
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
if (isDynamicPostponeReason(createPostponeReason('%%%', '^^^')) === false) {
|
||||
throw new Error(
|
||||
'Invariant: isDynamicPostpone misidentified a postpone reason. This is a bug in Next.js'
|
||||
)
|
||||
}
|
||||
|
||||
const NEXT_PRERENDER_INTERRUPTED = 'NEXT_PRERENDER_INTERRUPTED'
|
||||
|
||||
function createPrerenderInterruptedError(message: string): Error {
|
||||
@@ -448,6 +531,18 @@ export function formatDynamicAPIAccesses(
|
||||
})
|
||||
}
|
||||
|
||||
function assertPostpone() {
|
||||
if (!hasPostpone) {
|
||||
throw new Error(
|
||||
`Invariant: React.unstable_postpone is not defined. This suggests the wrong version of React was loaded. This is a bug in Next.js`
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This is a bit of a hack to allow us to abort a render using a Postpone instance instead of an Error which changes React's
|
||||
* abort semantics slightly.
|
||||
*/
|
||||
export function createRenderInBrowserAbortSignal(): AbortSignal {
|
||||
const controller = new AbortController()
|
||||
controller.abort(new BailoutToCSRError('Render in Browser'))
|
||||
@@ -505,6 +600,7 @@ export function createHangingInputAbortSignal(
|
||||
return controller.signal
|
||||
case 'prerender-client':
|
||||
case 'validation-client':
|
||||
case 'prerender-ppr':
|
||||
case 'prerender-legacy':
|
||||
case 'request':
|
||||
case 'cache':
|
||||
@@ -559,6 +655,17 @@ export function useDynamicRouteParams(expression: string) {
|
||||
throw new InvariantError(
|
||||
`\`${expression}\` was called from a Server Component. Next.js should be preventing ${expression} from being included in server components statically, but did not in this case.`
|
||||
)
|
||||
case 'prerender-ppr': {
|
||||
const fallbackParams = workUnitStore.fallbackRouteParams
|
||||
if (fallbackParams && fallbackParams.size > 0) {
|
||||
return postponeWithTracking(
|
||||
workStore.route,
|
||||
expression,
|
||||
workUnitStore.dynamicTracking
|
||||
)
|
||||
}
|
||||
break
|
||||
}
|
||||
case 'validation-client': {
|
||||
// Don't check fallbackRouteParams here. We handle params that weren't
|
||||
// provided in the samples using a proxy that throws when accessed.
|
||||
@@ -614,7 +721,8 @@ export function useDynamicSearchParams(expression: string) {
|
||||
)
|
||||
break
|
||||
}
|
||||
case 'prerender-legacy': {
|
||||
case 'prerender-legacy':
|
||||
case 'prerender-ppr': {
|
||||
if (workStore.forceStatic) {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -293,6 +293,7 @@ export async function decryptActionBoundArgs(
|
||||
break
|
||||
case 'prerender-client':
|
||||
case 'validation-client':
|
||||
case 'prerender-ppr':
|
||||
case 'prerender-legacy':
|
||||
case 'request':
|
||||
case 'cache':
|
||||
|
||||
@@ -62,6 +62,7 @@ export { createMetadataComponents } from '../../lib/metadata/metadata'
|
||||
export { RootLayoutBoundary } from '../../lib/framework/boundary-components'
|
||||
|
||||
export { preloadStyle, preloadFont, preconnect } from './rsc/preloads'
|
||||
export { Postpone } from './rsc/postpone'
|
||||
export { taintObjectReference } from './rsc/taint'
|
||||
export {
|
||||
collectSegmentData,
|
||||
|
||||
@@ -24,6 +24,7 @@ function getValidationBoundaryTracking(): ValidationBoundaryTracking | null {
|
||||
return store.boundaryState
|
||||
case 'prerender':
|
||||
case 'prerender-client':
|
||||
case 'prerender-ppr':
|
||||
case 'prerender-legacy':
|
||||
case 'prerender-runtime':
|
||||
case 'request':
|
||||
|
||||
@@ -32,6 +32,7 @@ export function instrumentParamsForClientValidation<TPArams extends Params>(
|
||||
case 'prerender-runtime':
|
||||
case 'prerender-client':
|
||||
case 'prerender-legacy':
|
||||
case 'prerender-ppr':
|
||||
case 'prerender':
|
||||
case 'cache':
|
||||
case 'request':
|
||||
@@ -71,6 +72,7 @@ export function expectCompleteParamsInClientValidation(
|
||||
case 'prerender-runtime':
|
||||
case 'prerender-client':
|
||||
case 'prerender-legacy':
|
||||
case 'prerender-ppr':
|
||||
case 'prerender':
|
||||
case 'cache':
|
||||
case 'request':
|
||||
@@ -107,6 +109,7 @@ export function instrumentSearchParamsForClientValidation(
|
||||
case 'prerender-runtime':
|
||||
case 'prerender-client':
|
||||
case 'prerender-legacy':
|
||||
case 'prerender-ppr':
|
||||
case 'prerender':
|
||||
case 'cache':
|
||||
case 'request':
|
||||
|
||||
@@ -42,6 +42,7 @@ function getExpectedSampleTracking(): InstantValidationSampleTracking {
|
||||
case 'private-cache':
|
||||
case 'unstable-cache':
|
||||
case 'prerender-legacy':
|
||||
case 'prerender-ppr':
|
||||
case 'prerender-client':
|
||||
case 'prerender':
|
||||
case 'prerender-runtime':
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
/*
|
||||
|
||||
Files in the rsc directory are meant to be packaged as part of the RSC graph using next-app-loader.
|
||||
|
||||
*/
|
||||
|
||||
// When postpone is available in canary React we can switch to importing it directly
|
||||
export { Postpone } from '../dynamic-rendering'
|
||||
@@ -149,6 +149,11 @@ export interface RenderOptsPartial {
|
||||
isPrefetch?: boolean
|
||||
htmlLimitedBots: string | undefined
|
||||
experimental: {
|
||||
/**
|
||||
* When true, it indicates that the current page supports partial
|
||||
* prerendering.
|
||||
*/
|
||||
isRoutePPREnabled?: boolean
|
||||
expireTime: number | undefined
|
||||
staleTimes: ExperimentalConfig['staleTimes'] | undefined
|
||||
clientTraceMetadata: string[] | undefined
|
||||
|
||||
@@ -135,6 +135,7 @@ export function getFlightStream<T>(
|
||||
return responseOnNextTick
|
||||
case 'prerender':
|
||||
case 'prerender-runtime':
|
||||
case 'prerender-ppr':
|
||||
case 'prerender-legacy':
|
||||
case 'request':
|
||||
case 'cache':
|
||||
|
||||
@@ -134,7 +134,7 @@ export async function walkTreeWithFlightRouterState({
|
||||
|
||||
if (
|
||||
isInsideSharedLayout &&
|
||||
!cacheComponents &&
|
||||
!experimental.isRoutePPREnabled &&
|
||||
// If PPR is disabled, and this is a request for the route tree, then we
|
||||
// never render any components. Only send the router state.
|
||||
(parsedRequestHeaders.isRouteTreePrefetchRequest ||
|
||||
|
||||
@@ -11,7 +11,10 @@ import type { OpaqueFallbackRouteParams } from '../request/fallback-params'
|
||||
// Share the instance module in the next-shared layer
|
||||
import { workUnitAsyncStorageInstance } from './work-unit-async-storage-instance' with { 'turbopack-transition': 'next-shared' }
|
||||
import type { ServerComponentsHmrCache } from '../response-cache'
|
||||
import type { ResumeDataCache } from '../resume-data-cache/resume-data-cache'
|
||||
import type {
|
||||
PrerenderResumeDataCache,
|
||||
ResumeDataCache,
|
||||
} from '../resume-data-cache/resume-data-cache'
|
||||
import type { Params } from '../request/params'
|
||||
import type { ImplicitTags } from '../lib/implicit-tags'
|
||||
import type { WorkStore } from './work-async-storage.external'
|
||||
@@ -280,6 +283,25 @@ interface StaticPrerenderStoreCommon {
|
||||
readonly fallbackRouteParams: OpaqueFallbackRouteParams | null
|
||||
}
|
||||
|
||||
export interface PrerenderStorePPR
|
||||
extends CommonWorkUnitStore,
|
||||
RevalidateStore {
|
||||
readonly type: 'prerender-ppr'
|
||||
readonly rootParams: Params
|
||||
readonly dynamicTracking: null | DynamicTrackingState
|
||||
|
||||
/**
|
||||
* The set of unknown route parameters. Accessing these will be tracked as
|
||||
* a dynamic access.
|
||||
*/
|
||||
readonly fallbackRouteParams: OpaqueFallbackRouteParams | null
|
||||
|
||||
/**
|
||||
* The resume data cache for this prerender. Always mutable in PPR mode.
|
||||
*/
|
||||
resumeDataCache: PrerenderResumeDataCache
|
||||
}
|
||||
|
||||
export interface PrerenderStoreLegacy
|
||||
extends CommonWorkUnitStore,
|
||||
RevalidateStore {
|
||||
@@ -287,7 +309,10 @@ export interface PrerenderStoreLegacy
|
||||
readonly rootParams: Params
|
||||
}
|
||||
|
||||
export type PrerenderStore = PrerenderStoreLegacy | PrerenderStoreModern
|
||||
export type PrerenderStore =
|
||||
| PrerenderStoreLegacy
|
||||
| PrerenderStorePPR
|
||||
| PrerenderStoreModern
|
||||
|
||||
// /** Like `PrerenderStoreModern`, but only including static prerenders (i.e. not runtime prerenders) */
|
||||
export type StaticPrerenderStore = Exclude<
|
||||
@@ -423,6 +448,7 @@ export function getResumeDataCache(
|
||||
case 'prerender-runtime':
|
||||
case 'prerender-client':
|
||||
case 'validation-client':
|
||||
case 'prerender-ppr':
|
||||
return workUnitStore.resumeDataCache
|
||||
case 'cache':
|
||||
case 'private-cache':
|
||||
@@ -449,6 +475,7 @@ export function getHmrRefreshHash(
|
||||
return workUnitStore.cookies.get(NEXT_HMR_REFRESH_HASH_COOKIE)?.value
|
||||
case 'prerender-client':
|
||||
case 'validation-client':
|
||||
case 'prerender-ppr':
|
||||
case 'prerender-legacy':
|
||||
case 'unstable-cache':
|
||||
case 'generate-static-params':
|
||||
@@ -472,6 +499,7 @@ export function isHmrRefresh(workUnitStore: WorkUnitStore): boolean {
|
||||
case 'prerender-client':
|
||||
case 'validation-client':
|
||||
case 'prerender-runtime':
|
||||
case 'prerender-ppr':
|
||||
case 'prerender-legacy':
|
||||
case 'unstable-cache':
|
||||
case 'generate-static-params':
|
||||
@@ -497,6 +525,7 @@ export function getServerComponentsHmrCache(
|
||||
case 'prerender-client':
|
||||
case 'validation-client':
|
||||
case 'prerender-runtime':
|
||||
case 'prerender-ppr':
|
||||
case 'prerender-legacy':
|
||||
case 'unstable-cache':
|
||||
case 'generate-static-params':
|
||||
@@ -527,6 +556,7 @@ export function getDraftModeProviderForCacheScope(
|
||||
case 'prerender':
|
||||
case 'prerender-client':
|
||||
case 'validation-client':
|
||||
case 'prerender-ppr':
|
||||
case 'prerender-legacy':
|
||||
case 'generate-static-params':
|
||||
break
|
||||
@@ -548,6 +578,7 @@ export function getStagedRenderingController(
|
||||
return workUnitStore.stagedRendering ?? null
|
||||
case 'prerender-client':
|
||||
case 'validation-client':
|
||||
case 'prerender-ppr':
|
||||
case 'prerender-legacy':
|
||||
case 'cache':
|
||||
case 'private-cache':
|
||||
@@ -575,6 +606,7 @@ export function getCacheSignal(
|
||||
}
|
||||
// fallthrough
|
||||
}
|
||||
case 'prerender-ppr':
|
||||
case 'prerender-legacy':
|
||||
case 'cache':
|
||||
case 'private-cache':
|
||||
@@ -595,6 +627,7 @@ export function getVaryParamsAccumulator(
|
||||
case 'request': {
|
||||
return workUnitStore.varyParamsAccumulator ?? null
|
||||
}
|
||||
case 'prerender-ppr':
|
||||
case 'prerender-legacy':
|
||||
case 'cache':
|
||||
case 'private-cache':
|
||||
|
||||
@@ -34,7 +34,7 @@ export type WorkStoreContext = {
|
||||
pendingWaitUntil?: Promise<any>
|
||||
experimental: Pick<
|
||||
RenderOpts['experimental'],
|
||||
'authInterrupts' | 'useCacheTimeout'
|
||||
'isRoutePPREnabled' | 'authInterrupts' | 'useCacheTimeout'
|
||||
>
|
||||
|
||||
/**
|
||||
|
||||
@@ -134,6 +134,7 @@ import { toRoute } from './lib/to-route'
|
||||
import type { DeepReadonly } from '../shared/lib/deep-readonly'
|
||||
import { isNodeNextRequest, isNodeNextResponse } from './base-http/helpers'
|
||||
import { patchSetHeaderWithCookieSupport } from './lib/patch-set-header'
|
||||
import { checkIsAppPPREnabled } from './lib/experimental/ppr'
|
||||
import {
|
||||
getBuiltinRequestContext,
|
||||
type WaitUntil,
|
||||
@@ -431,7 +432,7 @@ export default abstract class Server<
|
||||
readonly data: NextDataPathnameNormalizer | undefined
|
||||
}
|
||||
|
||||
private readonly isAppCacheComponentsEnabled: boolean
|
||||
private readonly isAppPPREnabled: boolean
|
||||
|
||||
/**
|
||||
* This is used to persist cache scopes across
|
||||
@@ -517,8 +518,9 @@ export default abstract class Server<
|
||||
|
||||
this.enabledDirectories = this.getEnabledDirectories(dev)
|
||||
|
||||
this.isAppCacheComponentsEnabled =
|
||||
this.enabledDirectories.app && Boolean(this.nextConfig.cacheComponents)
|
||||
this.isAppPPREnabled =
|
||||
this.enabledDirectories.app &&
|
||||
checkIsAppPPREnabled(this.nextConfig.experimental.ppr)
|
||||
|
||||
this.normalizers = {
|
||||
// We should normalize the pathname from the RSC prefix only in minimal
|
||||
@@ -1095,7 +1097,7 @@ export default abstract class Server<
|
||||
// It's important to execute the following block even it the request
|
||||
// matches a pages data route from above.
|
||||
if (
|
||||
this.isAppCacheComponentsEnabled &&
|
||||
this.isAppPPREnabled &&
|
||||
this.minimalMode &&
|
||||
req.headers[NEXT_RESUME_HEADER] === '1' &&
|
||||
req.method === 'POST'
|
||||
@@ -2217,7 +2219,7 @@ export default abstract class Server<
|
||||
* enabled, then the given route _could_ support PPR.
|
||||
*/
|
||||
const couldSupportPPR: boolean =
|
||||
this.isAppCacheComponentsEnabled &&
|
||||
this.isAppPPREnabled &&
|
||||
typeof routeModule !== 'undefined' &&
|
||||
isAppPageRouteModule(routeModule)
|
||||
|
||||
|
||||
@@ -41,6 +41,7 @@ const zExportMap: zod.ZodType<ExportPathMap> = z.record(
|
||||
_fallbackRouteParams: z.array(z.any()).optional(),
|
||||
_isAppDir: z.boolean().optional(),
|
||||
_isDynamicError: z.boolean().optional(),
|
||||
_isRoutePPREnabled: z.boolean().optional(),
|
||||
_allowEmptyStaticShell: z.boolean().optional(),
|
||||
_isFallbackUpgradeable: z.boolean().optional(),
|
||||
})
|
||||
|
||||
@@ -11,6 +11,7 @@ import type { WEB_VITALS } from '../shared/lib/utils'
|
||||
import type { NextParsedUrlQuery } from './request-meta'
|
||||
import type { SizeLimit } from '../types'
|
||||
import type { SupportedTestRunners } from '../cli/next-test'
|
||||
import type { ExperimentalPPRConfig } from './lib/experimental/ppr'
|
||||
import { INFINITE_CACHE } from '../lib/constants'
|
||||
import type { FallbackRouteParam } from '../build/static-paths/types'
|
||||
import type { MemoryEvictionMode } from '../build/swc/types'
|
||||
@@ -972,6 +973,12 @@ export interface ExperimentalConfig {
|
||||
*/
|
||||
clientTraceMetadata?: string[]
|
||||
|
||||
/**
|
||||
* @deprecated This configuration option has been merged into `cacheComponents`.
|
||||
* The Partial Prerendering feature is still available via `cacheComponents`.
|
||||
*/
|
||||
ppr?: ExperimentalPPRConfig
|
||||
|
||||
/**
|
||||
* Enables experimental taint APIs in React.
|
||||
* Using this feature will enable the `react@experimental` for the `app` directory.
|
||||
@@ -1356,6 +1363,11 @@ export type ExportPathMap = {
|
||||
*/
|
||||
_isDynamicError?: boolean
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
_isRoutePPREnabled?: 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
|
||||
@@ -2076,6 +2088,7 @@ export const defaultConfig = Object.freeze({
|
||||
clientTraceMetadata: undefined,
|
||||
parallelServerCompiles: false,
|
||||
parallelServerBuildTraces: false,
|
||||
ppr: false,
|
||||
authInterrupts: false,
|
||||
webpackBuildWorker: undefined,
|
||||
webpackMemoryOptimizations: false,
|
||||
@@ -2140,6 +2153,7 @@ export async function normalizeConfig(phase: string, config: any) {
|
||||
// cacheComponents?: boolean;
|
||||
// clientParamParsingOrigins?: string[];
|
||||
// clientSegmentCache?: boolean;
|
||||
// ppr?: boolean | 'incremental';
|
||||
// serverActions?: Record<string, never>;
|
||||
// };
|
||||
// };
|
||||
@@ -2182,6 +2196,7 @@ export interface NextConfigRuntime {
|
||||
|
||||
experimental: Pick<
|
||||
NextConfigComplete['experimental'],
|
||||
| 'ppr'
|
||||
| 'taint'
|
||||
| 'serverActions'
|
||||
| 'staleTimes'
|
||||
@@ -2249,6 +2264,7 @@ export function getNextConfigRuntime(
|
||||
}
|
||||
|
||||
const experimental = {
|
||||
ppr: ex.ppr,
|
||||
taint: ex.taint,
|
||||
serverActions: ex.serverActions,
|
||||
staleTimes: ex.staleTimes,
|
||||
|
||||
@@ -575,7 +575,7 @@ function assignDefaultsAndValidate(
|
||||
}
|
||||
}
|
||||
|
||||
if ((result.experimental as any).ppr) {
|
||||
if (result.experimental.ppr) {
|
||||
throw new HardDeprecatedConfigError(
|
||||
`\`experimental.ppr\` has been merged into \`cacheComponents\`. The Partial Prerendering feature is still available, but is now enabled via \`cacheComponents\`. Please update your ${configFileName} accordingly.`
|
||||
)
|
||||
@@ -1570,6 +1570,11 @@ function assignDefaultsAndValidate(
|
||||
result.experimental.mcpServer = true
|
||||
}
|
||||
|
||||
if (result.cacheComponents) {
|
||||
// TODO: remove once we've finished migrating internally to cacheComponents.
|
||||
result.experimental.ppr = true
|
||||
}
|
||||
|
||||
// "use cache" was originally implicitly enabled with the cacheComponents flag, so
|
||||
// we transfer the value for cacheComponents to the explicit useCache flag to ensure
|
||||
// backwards compatibility.
|
||||
|
||||
@@ -72,6 +72,7 @@ import { DefaultFileReader } from '../route-matcher-providers/dev/helpers/file-r
|
||||
import { LRUCache } from '../lib/lru-cache'
|
||||
import { getMiddlewareRouteMatcher } from '../../shared/lib/router/utils/middleware-route-matcher'
|
||||
import { DetachedPromise } from '../../lib/detached-promise'
|
||||
import { isPostpone } from '../lib/router-utils/is-postpone'
|
||||
import { generateInterceptionRoutesRewrites } from '../../lib/generate-interception-routes-rewrites'
|
||||
import { buildCustomRoute } from '../../lib/build-custom-route'
|
||||
import { decorateServerError } from '../../shared/lib/error-source'
|
||||
@@ -344,6 +345,11 @@ export default class DevServer extends Server {
|
||||
}
|
||||
|
||||
process.on('unhandledRejection', (reason) => {
|
||||
if (isPostpone(reason)) {
|
||||
// React postpones that are unhandled might end up logged here but they're
|
||||
// not really errors. They're just part of rendering.
|
||||
return
|
||||
}
|
||||
this.logErrorWithOriginalStack(reason, 'unhandledRejection')
|
||||
})
|
||||
process.on('uncaughtException', (err) => {
|
||||
@@ -800,6 +806,7 @@ export default class DevServer extends Server {
|
||||
distDir: this.distDir,
|
||||
pathname,
|
||||
config: {
|
||||
pprConfig: this.nextConfig.experimental.ppr,
|
||||
configFileName,
|
||||
cacheComponents: Boolean(this.nextConfig.cacheComponents),
|
||||
},
|
||||
|
||||
@@ -17,6 +17,10 @@ import { loadComponents } from '../load-components'
|
||||
import { setHttpClientAndAgentOptions } from '../setup-http-agent-env'
|
||||
import type { IncrementalCache } from '../lib/incremental-cache'
|
||||
import { isAppPageRouteModule } from '../route-modules/checks'
|
||||
import {
|
||||
checkIsRoutePPREnabled,
|
||||
type ExperimentalPPRConfig,
|
||||
} from '../lib/experimental/ppr'
|
||||
import { InvariantError } from '../../shared/lib/invariant-error'
|
||||
import { collectRootParamKeys } from '../../build/segment-config/app/collect-root-param-keys'
|
||||
import { buildAppStaticPaths } from '../../build/static-paths/app'
|
||||
@@ -25,6 +29,7 @@ import { createIncrementalCache } from '../../export/helpers/create-incremental-
|
||||
import { parseNormalizedAppRoute } from '../../shared/lib/router/routes/app'
|
||||
|
||||
type RuntimeConfig = {
|
||||
pprConfig: ExperimentalPPRConfig | undefined
|
||||
configFileName: string
|
||||
cacheComponents: boolean
|
||||
}
|
||||
@@ -127,7 +132,8 @@ export async function loadStaticPaths({
|
||||
}
|
||||
|
||||
const isRoutePPREnabled =
|
||||
isAppPageRouteModule(routeModule) && Boolean(config.cacheComponents)
|
||||
isAppPageRouteModule(routeModule) &&
|
||||
checkIsRoutePPREnabled(config.pprConfig)
|
||||
|
||||
const rootParamKeys = collectRootParamKeys(routeModule)
|
||||
|
||||
|
||||
@@ -251,6 +251,7 @@ export function applyOwnerStack(error: Error): Error {
|
||||
case 'unstable-cache':
|
||||
case 'request':
|
||||
case 'prerender':
|
||||
case 'prerender-ppr':
|
||||
case 'prerender-legacy':
|
||||
case 'prerender-runtime':
|
||||
case 'prerender-client':
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
/**
|
||||
* If set to `incremental`, only those leaf pages that export
|
||||
* `experimental_ppr = true` will have partial prerendering enabled. If any
|
||||
* page exports this value as `false` or does not export it at all will not
|
||||
* have partial prerendering enabled. If set to a boolean, the options for
|
||||
* `experimental_ppr` will be ignored.
|
||||
*/
|
||||
|
||||
export type ExperimentalPPRConfig = boolean | 'incremental'
|
||||
|
||||
/**
|
||||
* Returns true if partial prerendering is enabled for the application. It does
|
||||
* not tell you if a given route has PPR enabled, as that requires analysis of
|
||||
* the route's configuration.
|
||||
*
|
||||
* @see {@link checkIsRoutePPREnabled} - for checking if a specific route has PPR enabled.
|
||||
*/
|
||||
export function checkIsAppPPREnabled(
|
||||
config: ExperimentalPPRConfig | undefined
|
||||
): boolean {
|
||||
// If the config is undefined, partial prerendering is disabled.
|
||||
if (typeof config === 'undefined') return false
|
||||
|
||||
// If the config is a boolean, use it directly.
|
||||
if (typeof config === 'boolean') return config
|
||||
|
||||
// If the config is a string, it must be 'incremental' to enable partial
|
||||
// prerendering.
|
||||
if (config === 'incremental') return true
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if partial prerendering is supported for the current page with
|
||||
* the provided app configuration. If the application doesn't have partial
|
||||
* prerendering enabled, this function will always return false. If you want to
|
||||
* check if the application has partial prerendering enabled
|
||||
*
|
||||
* @see {@link checkIsAppPPREnabled} for checking if the application has PPR enabled.
|
||||
*/
|
||||
export function checkIsRoutePPREnabled(
|
||||
config: ExperimentalPPRConfig | undefined
|
||||
): boolean {
|
||||
// If the config is undefined, partial prerendering is disabled.
|
||||
if (typeof config === 'undefined') return false
|
||||
|
||||
// If the config is a boolean, use it directly.
|
||||
if (typeof config === 'boolean') return config
|
||||
|
||||
return false
|
||||
}
|
||||
@@ -369,6 +369,7 @@ export function createPatchedFetcher(
|
||||
// TODO: Stop accumulating tags in client prerender. (fallthrough)
|
||||
case 'prerender-client':
|
||||
case 'validation-client':
|
||||
case 'prerender-ppr':
|
||||
case 'prerender-legacy':
|
||||
case 'cache':
|
||||
case 'private-cache':
|
||||
@@ -411,6 +412,7 @@ export function createPatchedFetcher(
|
||||
case 'prerender-client':
|
||||
case 'validation-client':
|
||||
case 'prerender-runtime':
|
||||
case 'prerender-ppr':
|
||||
case 'prerender-legacy':
|
||||
case 'request':
|
||||
case 'cache':
|
||||
@@ -582,6 +584,7 @@ export function createPatchedFetcher(
|
||||
)
|
||||
}
|
||||
break
|
||||
case 'prerender-ppr':
|
||||
case 'prerender-legacy':
|
||||
case 'cache':
|
||||
case 'private-cache':
|
||||
@@ -714,6 +717,7 @@ export function createPatchedFetcher(
|
||||
)
|
||||
}
|
||||
break
|
||||
case 'prerender-ppr':
|
||||
case 'prerender-legacy':
|
||||
case 'cache':
|
||||
case 'private-cache':
|
||||
@@ -760,6 +764,7 @@ export function createPatchedFetcher(
|
||||
case 'prerender-client':
|
||||
case 'validation-client':
|
||||
case 'prerender-runtime':
|
||||
case 'prerender-ppr':
|
||||
case 'prerender-legacy':
|
||||
case 'unstable-cache':
|
||||
case 'generate-static-params':
|
||||
@@ -912,6 +917,7 @@ export function createPatchedFetcher(
|
||||
)
|
||||
}
|
||||
// fallthrough
|
||||
case 'prerender-ppr':
|
||||
case 'prerender-legacy':
|
||||
case 'cache':
|
||||
case 'private-cache':
|
||||
@@ -995,6 +1001,7 @@ export function createPatchedFetcher(
|
||||
)
|
||||
}
|
||||
break
|
||||
case 'prerender-ppr':
|
||||
case 'prerender-legacy':
|
||||
case 'cache':
|
||||
case 'private-cache':
|
||||
@@ -1124,6 +1131,7 @@ export function createPatchedFetcher(
|
||||
)
|
||||
}
|
||||
break
|
||||
case 'prerender-ppr':
|
||||
case 'prerender-legacy':
|
||||
case 'cache':
|
||||
case 'private-cache':
|
||||
@@ -1175,6 +1183,7 @@ export function createPatchedFetcher(
|
||||
case 'private-cache':
|
||||
case 'unstable-cache':
|
||||
case 'prerender-legacy':
|
||||
case 'prerender-ppr':
|
||||
case 'generate-static-params':
|
||||
break
|
||||
default:
|
||||
|
||||
@@ -24,6 +24,7 @@ import { pathHasPrefix } from '../../shared/lib/router/utils/path-has-prefix'
|
||||
import { removePathPrefix } from '../../shared/lib/router/utils/remove-path-prefix'
|
||||
import setupCompression from 'next/dist/compiled/compression'
|
||||
import { signalFromNodeResponse } from '../web/spec-extension/adapters/next-request'
|
||||
import { isPostpone } from './router-utils/is-postpone'
|
||||
import { parseUrl as parseUrlUtil } from '../../shared/lib/router/utils/parse-url'
|
||||
|
||||
import {
|
||||
@@ -794,6 +795,11 @@ export async function initialize(opts: {
|
||||
type: 'uncaughtException' | 'unhandledRejection',
|
||||
err: Error | undefined
|
||||
) => {
|
||||
if (isPostpone(err)) {
|
||||
// React postpones that are unhandled might end up logged here but they're
|
||||
// not really errors. They're just part of rendering.
|
||||
return
|
||||
}
|
||||
if (type === 'unhandledRejection') {
|
||||
Log.error('unhandledRejection: ', err)
|
||||
} else if (type === 'uncaughtException') {
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
const REACT_POSTPONE_TYPE: symbol = Symbol.for('react.postpone')
|
||||
|
||||
export function isPostpone(error: any): boolean {
|
||||
return (
|
||||
typeof error === 'object' &&
|
||||
error !== null &&
|
||||
error.$$typeof === REACT_POSTPONE_TYPE
|
||||
)
|
||||
}
|
||||
@@ -257,6 +257,7 @@ function patchConsoleMethod(methodName: InterceptableConsoleMethod): void {
|
||||
}
|
||||
// intentional fallthrough
|
||||
case 'prerender-legacy':
|
||||
case 'prerender-ppr':
|
||||
case 'cache':
|
||||
case 'unstable-cache':
|
||||
case 'private-cache':
|
||||
|
||||
@@ -90,6 +90,7 @@ export function io(expression: string, type: SyncIOApiType) {
|
||||
break
|
||||
}
|
||||
case 'validation-client':
|
||||
case 'prerender-ppr':
|
||||
case 'prerender-legacy':
|
||||
case 'cache':
|
||||
case 'private-cache':
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { isPostpone } from '../lib/router-utils/is-postpone'
|
||||
|
||||
let _global = globalThis as typeof globalThis & {
|
||||
nextInitializedProcessErrorHandlers?: boolean
|
||||
}
|
||||
@@ -54,6 +56,11 @@ export function installProcessErrorHandlers(
|
||||
|
||||
// Install a new handler to prevent the process from crashing.
|
||||
process.on('unhandledRejection', (reason: unknown) => {
|
||||
if (isPostpone(reason)) {
|
||||
// React postpones that are unhandled might end up logged here but they're
|
||||
// not really errors. They're just part of rendering.
|
||||
return
|
||||
}
|
||||
// Immediately log the error.
|
||||
// TODO: Ideally, if we knew that this error was triggered by application
|
||||
// code, we would suppress it entirely without logging. We can't reliably
|
||||
@@ -74,6 +81,9 @@ export function installProcessErrorHandlers(
|
||||
// is unrelated to the late-awaiting pattern. However, for similar reasons,
|
||||
// we still shouldn't crash the process. Just log it.
|
||||
process.on('uncaughtException', (reason: unknown) => {
|
||||
if (isPostpone(reason)) {
|
||||
return
|
||||
}
|
||||
console.error(reason)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -615,6 +615,7 @@ function filteringUnhandledRejectionHandler(
|
||||
}
|
||||
break
|
||||
}
|
||||
case 'prerender-ppr':
|
||||
case 'prerender-legacy':
|
||||
case 'cache':
|
||||
case 'private-cache':
|
||||
|
||||
@@ -76,6 +76,7 @@ async function getCachedImageResponseArrayBuffer(
|
||||
case 'prerender-runtime':
|
||||
case 'prerender-client':
|
||||
case 'validation-client':
|
||||
case 'prerender-ppr':
|
||||
case 'prerender-legacy':
|
||||
case 'generate-static-params':
|
||||
return renderImageResponseArrayBuffer(args)
|
||||
|
||||
@@ -4,6 +4,7 @@ import {
|
||||
workUnitAsyncStorage,
|
||||
} from '../app-render/work-unit-async-storage.external'
|
||||
import {
|
||||
postponeWithTracking,
|
||||
throwToInterruptStaticGeneration,
|
||||
trackDynamicDataInDynamicRender,
|
||||
} from '../app-render/dynamic-rendering'
|
||||
@@ -95,6 +96,14 @@ export function connection(): Promise<void> {
|
||||
`${exportName} must not be used within a Client Component. Next.js should be preventing ${exportName} from being included in Client Components statically, but did not in this case.`
|
||||
)
|
||||
}
|
||||
case 'prerender-ppr':
|
||||
// We use React's postpone API to interrupt rendering here to create a
|
||||
// dynamic hole
|
||||
return postponeWithTracking(
|
||||
workStore.route,
|
||||
'connection',
|
||||
workUnitStore.dynamicTracking
|
||||
)
|
||||
case 'prerender-legacy':
|
||||
// We throw an error here to interrupt prerendering to mark the route
|
||||
// as dynamic
|
||||
|
||||
@@ -16,6 +16,7 @@ import {
|
||||
isInEarlyRenderStage,
|
||||
} from '../app-render/work-unit-async-storage.external'
|
||||
import {
|
||||
postponeWithTracking,
|
||||
throwToInterruptStaticGeneration,
|
||||
trackDynamicDataInDynamicRender,
|
||||
} from '../app-render/dynamic-rendering'
|
||||
@@ -82,6 +83,14 @@ export function cookies(): Promise<ReadonlyRequestCookies> {
|
||||
throw new InvariantError(
|
||||
`${exportName} must not be used within a Client Component. Next.js should be preventing ${exportName} from being included in Client Components statically, but did not in this case.`
|
||||
)
|
||||
case 'prerender-ppr':
|
||||
// We need track dynamic access here eagerly to keep continuity with
|
||||
// how cookies has worked in PPR without cacheComponents.
|
||||
return postponeWithTracking(
|
||||
workStore.route,
|
||||
callingExpression,
|
||||
workUnitStore.dynamicTracking
|
||||
)
|
||||
case 'prerender-legacy':
|
||||
// We track dynamic access here so we don't need to wrap the cookies
|
||||
// in individual property access tracking.
|
||||
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
import { workUnitAsyncStorage } from '../app-render/work-unit-async-storage.external'
|
||||
import {
|
||||
abortAndThrowOnSynchronousRequestDataAccess,
|
||||
postponeWithTracking,
|
||||
trackDynamicDataInDynamicRender,
|
||||
} from '../app-render/dynamic-rendering'
|
||||
import { createDedupedByCallsiteServerErrorLoggerDev } from '../create-deduped-by-callsite-server-error-logger'
|
||||
@@ -68,6 +69,7 @@ export function draftMode(): Promise<DraftMode> {
|
||||
// Otherwise, we fall through to providing an empty draft mode.
|
||||
// eslint-disable-next-line no-fallthrough
|
||||
case 'prerender':
|
||||
case 'prerender-ppr':
|
||||
case 'prerender-legacy':
|
||||
// Return empty draft mode
|
||||
return createOrGetCachedDraftMode(null, workStore)
|
||||
@@ -241,6 +243,12 @@ function trackDynamicDraftMode(expression: string, constructorOpt: Function) {
|
||||
throw new InvariantError(
|
||||
`${exportName} must not be used within a Client Component. Next.js should be preventing ${exportName} from being included in Client Components statically, but did not in this case.`
|
||||
)
|
||||
case 'prerender-ppr':
|
||||
return postponeWithTracking(
|
||||
workStore.route,
|
||||
expression,
|
||||
workUnitStore.dynamicTracking
|
||||
)
|
||||
case 'prerender-legacy':
|
||||
workUnitStore.revalidate = 0
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@ import {
|
||||
isInEarlyRenderStage,
|
||||
} from '../app-render/work-unit-async-storage.external'
|
||||
import {
|
||||
postponeWithTracking,
|
||||
throwToInterruptStaticGeneration,
|
||||
trackDynamicDataInDynamicRender,
|
||||
} from '../app-render/dynamic-rendering'
|
||||
@@ -81,6 +82,7 @@ export function headers(): Promise<ReadonlyHeaders> {
|
||||
case 'validation-client':
|
||||
case 'private-cache':
|
||||
case 'prerender-runtime':
|
||||
case 'prerender-ppr':
|
||||
case 'prerender-legacy':
|
||||
case 'request':
|
||||
break
|
||||
@@ -105,6 +107,16 @@ export function headers(): Promise<ReadonlyHeaders> {
|
||||
throw new InvariantError(
|
||||
`${exportName} must not be used within a client component. Next.js should be preventing ${exportName} from being included in client components statically, but did not in this case.`
|
||||
)
|
||||
case 'prerender-ppr':
|
||||
// PPR Prerender (no cacheComponents)
|
||||
// We are prerendering with PPR. We need track dynamic access here eagerly
|
||||
// to keep continuity with how headers has worked in PPR without cacheComponents.
|
||||
// TODO consider switching the semantic to throw on property access instead
|
||||
return postponeWithTracking(
|
||||
workStore.route,
|
||||
callingExpression,
|
||||
workUnitStore.dynamicTracking
|
||||
)
|
||||
case 'prerender-legacy':
|
||||
// Legacy Prerender
|
||||
// We are in a legacy static generation mode while prerendering
|
||||
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
makeDevtoolsIOAwarePromise,
|
||||
} from '../dynamic-rendering-utils'
|
||||
import { RenderStage } from '../app-render/staged-rendering'
|
||||
import { throwPrerenderPPRRemovedError } from '../../shared/lib/ppr-removed-error'
|
||||
import { isRequestApiAllowedInCurrentPhase } from './utils'
|
||||
|
||||
// A fulfilled thenable that React can unwrap synchronously via `use()` without
|
||||
@@ -66,6 +67,10 @@ export function io(): Promise<void> {
|
||||
workStore.route,
|
||||
'`io()`'
|
||||
)
|
||||
case 'prerender-ppr':
|
||||
// Dead code to be removed when we eliminate legacy ppr code
|
||||
throwPrerenderPPRRemovedError()
|
||||
break
|
||||
case 'cache':
|
||||
case 'private-cache':
|
||||
case 'unstable-cache':
|
||||
|
||||
@@ -10,8 +10,15 @@ import {
|
||||
} from '../app-render/vary-params'
|
||||
|
||||
import { ReflectAdapter } from '../web/spec-extension/adapters/reflect'
|
||||
import {
|
||||
throwToInterruptStaticGeneration,
|
||||
postponeWithTracking,
|
||||
} from '../app-render/dynamic-rendering'
|
||||
|
||||
import {
|
||||
workUnitAsyncStorage,
|
||||
type PrerenderStorePPR,
|
||||
type PrerenderStoreLegacy,
|
||||
type StaticPrerenderStoreModern,
|
||||
type StaticPrerenderStore,
|
||||
throwInvariantForMissingStore,
|
||||
@@ -49,6 +56,7 @@ export function createParamsFromClient(
|
||||
switch (workUnitStore.type) {
|
||||
case 'prerender':
|
||||
case 'prerender-client':
|
||||
case 'prerender-ppr':
|
||||
case 'prerender-legacy':
|
||||
// Client params don't need additional vary tracking because by the
|
||||
// time they reach the client, the access would have already been
|
||||
@@ -139,6 +147,7 @@ export function createServerParamsForRoute(
|
||||
if (workUnitStore) {
|
||||
switch (workUnitStore.type) {
|
||||
case 'prerender':
|
||||
case 'prerender-ppr':
|
||||
case 'prerender-legacy':
|
||||
return createStaticPrerenderParams(
|
||||
underlyingParams,
|
||||
@@ -203,6 +212,7 @@ export function createServerParamsForServerSegment(
|
||||
switch (workUnitStore.type) {
|
||||
case 'prerender':
|
||||
case 'prerender-client':
|
||||
case 'prerender-ppr':
|
||||
case 'prerender-legacy':
|
||||
return createStaticPrerenderParams(
|
||||
underlyingParams,
|
||||
@@ -298,6 +308,7 @@ export function createPrerenderParamsForClientSegment(
|
||||
'createPrerenderParamsForClientSegment should not be called inside generateStaticParams.'
|
||||
)
|
||||
case 'prerender-runtime':
|
||||
case 'prerender-ppr':
|
||||
case 'prerender-legacy':
|
||||
case 'request':
|
||||
break
|
||||
@@ -381,6 +392,22 @@ function createStaticPrerenderParams(
|
||||
}
|
||||
break
|
||||
}
|
||||
case 'prerender-ppr': {
|
||||
const fallbackParams = prerenderStore.fallbackRouteParams
|
||||
if (fallbackParams) {
|
||||
for (const key in underlyingParams) {
|
||||
if (fallbackParams.has(key)) {
|
||||
return makeErroringParams(
|
||||
underlyingParams,
|
||||
fallbackParams,
|
||||
workStore,
|
||||
prerenderStore
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
case 'prerender-legacy':
|
||||
break
|
||||
default:
|
||||
@@ -733,6 +760,65 @@ function makeHangingParams(
|
||||
return promise
|
||||
}
|
||||
|
||||
function makeErroringParams(
|
||||
underlyingParams: Params,
|
||||
fallbackParams: OpaqueFallbackRouteParams,
|
||||
workStore: WorkStore,
|
||||
prerenderStore: PrerenderStorePPR | PrerenderStoreLegacy
|
||||
): Promise<Params> {
|
||||
const cachedParams = CachedParams.get(underlyingParams)
|
||||
if (cachedParams) {
|
||||
return cachedParams
|
||||
}
|
||||
|
||||
const augmentedUnderlying = { ...underlyingParams }
|
||||
|
||||
// We don't use makeResolvedReactPromise here because params
|
||||
// supports copying with spread and we don't want to unnecessarily
|
||||
// instrument the promise with spreadable properties of ReactPromise.
|
||||
const promise = Promise.resolve(augmentedUnderlying)
|
||||
CachedParams.set(underlyingParams, promise)
|
||||
|
||||
Object.keys(underlyingParams).forEach((prop) => {
|
||||
if (wellKnownProperties.has(prop)) {
|
||||
// These properties cannot be shadowed because they need to be the
|
||||
// true underlying value for Promises to work correctly at runtime
|
||||
} else {
|
||||
if (fallbackParams.has(prop)) {
|
||||
Object.defineProperty(augmentedUnderlying, prop, {
|
||||
get() {
|
||||
const expression = describeStringPropertyAccess('params', prop)
|
||||
// In most dynamic APIs we also throw if `dynamic = "error"` however
|
||||
// for params is only dynamic when we're generating a fallback shell
|
||||
// and even when `dynamic = "error"` we still support generating dynamic
|
||||
// fallback shells
|
||||
// TODO remove this comment when cacheComponents is the default since there
|
||||
// will be no `dynamic = "error"`
|
||||
if (prerenderStore.type === 'prerender-ppr') {
|
||||
// PPR Prerender (no cacheComponents)
|
||||
postponeWithTracking(
|
||||
workStore.route,
|
||||
expression,
|
||||
prerenderStore.dynamicTracking
|
||||
)
|
||||
} else {
|
||||
// Legacy Prerender
|
||||
throwToInterruptStaticGeneration(
|
||||
expression,
|
||||
workStore,
|
||||
prerenderStore
|
||||
)
|
||||
}
|
||||
},
|
||||
enumerable: true,
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
return promise
|
||||
}
|
||||
|
||||
function makeUntrackedParams(underlyingParams: Params): Promise<Params> {
|
||||
const cachedParams = CachedParams.get(underlyingParams)
|
||||
if (cachedParams) {
|
||||
|
||||
@@ -3,11 +3,17 @@ import {
|
||||
type WorkStore,
|
||||
} from '../app-render/work-async-storage.external'
|
||||
|
||||
import {
|
||||
postponeWithTracking,
|
||||
type DynamicTrackingState,
|
||||
} from '../app-render/dynamic-rendering'
|
||||
|
||||
import {
|
||||
throwInvariantForMissingStore,
|
||||
workUnitAsyncStorage,
|
||||
type PrerenderStoreLegacy,
|
||||
type PrerenderStoreModernServer,
|
||||
type PrerenderStorePPR,
|
||||
} from '../app-render/work-unit-async-storage.external'
|
||||
import {
|
||||
makeHangingPromise,
|
||||
@@ -27,6 +33,7 @@ export function createServerPathnameForMetadata(
|
||||
if (workUnitStore) {
|
||||
switch (workUnitStore.type) {
|
||||
case 'prerender':
|
||||
case 'prerender-ppr':
|
||||
case 'prerender-legacy': {
|
||||
return createPrerenderPathname(
|
||||
underlyingPathname,
|
||||
@@ -84,7 +91,10 @@ export function createServerPathnameForMetadata(
|
||||
function createPrerenderPathname(
|
||||
underlyingPathname: string,
|
||||
workStore: WorkStore,
|
||||
prerenderStore: PrerenderStoreLegacy | PrerenderStoreModernServer
|
||||
prerenderStore:
|
||||
| PrerenderStoreLegacy
|
||||
| PrerenderStorePPR
|
||||
| PrerenderStoreModernServer
|
||||
): Promise<string> {
|
||||
switch (prerenderStore.type) {
|
||||
case 'prerender': {
|
||||
@@ -98,6 +108,13 @@ function createPrerenderPathname(
|
||||
}
|
||||
break
|
||||
}
|
||||
case 'prerender-ppr': {
|
||||
const fallbackParams = prerenderStore.fallbackRouteParams
|
||||
if (fallbackParams && fallbackParams.size > 0) {
|
||||
return makeErroringPathname(workStore, prerenderStore.dynamicTracking)
|
||||
}
|
||||
break
|
||||
}
|
||||
case 'prerender-legacy':
|
||||
break
|
||||
default:
|
||||
@@ -108,6 +125,41 @@ function createPrerenderPathname(
|
||||
return Promise.resolve(underlyingPathname)
|
||||
}
|
||||
|
||||
function makeErroringPathname<T>(
|
||||
workStore: WorkStore,
|
||||
dynamicTracking: null | DynamicTrackingState
|
||||
): Promise<T> {
|
||||
let reject: null | ((reason: unknown) => void) = null
|
||||
const promise = new Promise<T>((_, re) => {
|
||||
reject = re
|
||||
})
|
||||
|
||||
const originalThen = promise.then.bind(promise)
|
||||
|
||||
// We instrument .then so that we can generate a tracking event only if you actually
|
||||
// await this promise, not just that it is created.
|
||||
promise.then = (onfulfilled, onrejected) => {
|
||||
if (reject) {
|
||||
try {
|
||||
postponeWithTracking(
|
||||
workStore.route,
|
||||
'metadata relative url resolving',
|
||||
dynamicTracking
|
||||
)
|
||||
} catch (error) {
|
||||
reject(error)
|
||||
reject = null
|
||||
}
|
||||
}
|
||||
return originalThen(onfulfilled, onrejected)
|
||||
}
|
||||
|
||||
// We wrap in a noop proxy to trick the runtime into thinking it
|
||||
// isn't a native promise (it's not really). This is so that awaiting
|
||||
// the promise will call the `then` property triggering the lazy postpone
|
||||
return new Proxy(promise, {})
|
||||
}
|
||||
|
||||
function createRenderPathname(underlyingPathname: string): Promise<string> {
|
||||
return Promise.resolve(underlyingPathname)
|
||||
}
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
import { InvariantError } from '../../shared/lib/invariant-error'
|
||||
import {
|
||||
postponeWithTracking,
|
||||
throwToInterruptStaticGeneration,
|
||||
} from '../app-render/dynamic-rendering'
|
||||
import {
|
||||
workAsyncStorage,
|
||||
type WorkStore,
|
||||
@@ -7,9 +11,11 @@ import {
|
||||
workUnitAsyncStorage,
|
||||
type PrerenderStoreLegacy,
|
||||
type PrerenderStoreModernServer,
|
||||
type PrerenderStorePPR,
|
||||
} from '../app-render/work-unit-async-storage.external'
|
||||
import { makeHangingPromise } from '../dynamic-rendering-utils'
|
||||
import type { ParamValue } from './params'
|
||||
import { describeStringPropertyAccess } from '../../shared/lib/utils/reflect-utils'
|
||||
import { actionAsyncStorage } from '../app-render/action-async-storage.external'
|
||||
import { accumulateRootVaryParam } from '../app-render/vary-params'
|
||||
|
||||
@@ -67,6 +73,7 @@ export function getRootParam(paramName: string): Promise<ParamValue> {
|
||||
return Promise.resolve(workUnitStore.rootParams[paramName])
|
||||
}
|
||||
case 'prerender':
|
||||
case 'prerender-ppr':
|
||||
case 'prerender-legacy': {
|
||||
return createPrerenderRootParamPromise(
|
||||
paramName,
|
||||
@@ -132,12 +139,16 @@ export function getRootParam(paramName: string): Promise<ParamValue> {
|
||||
function createPrerenderRootParamPromise(
|
||||
paramName: string,
|
||||
workStore: WorkStore,
|
||||
prerenderStore: PrerenderStoreLegacy | PrerenderStoreModernServer,
|
||||
prerenderStore:
|
||||
| PrerenderStorePPR
|
||||
| PrerenderStoreLegacy
|
||||
| PrerenderStoreModernServer,
|
||||
apiName: string
|
||||
): Promise<ParamValue> {
|
||||
switch (prerenderStore.type) {
|
||||
case 'prerender':
|
||||
case 'prerender-legacy':
|
||||
case 'prerender-ppr':
|
||||
default:
|
||||
}
|
||||
|
||||
@@ -159,6 +170,22 @@ function createPrerenderRootParamPromise(
|
||||
}
|
||||
break
|
||||
}
|
||||
case 'prerender-ppr': {
|
||||
// We aren't in a cacheComponents prerender, but the param is a fallback,
|
||||
// so we need to make an erroring params object which will postpone/error if you access it
|
||||
if (
|
||||
prerenderStore.fallbackRouteParams &&
|
||||
prerenderStore.fallbackRouteParams.has(paramName)
|
||||
) {
|
||||
return makeErroringRootParamPromise(
|
||||
paramName,
|
||||
workStore,
|
||||
prerenderStore,
|
||||
apiName
|
||||
)
|
||||
}
|
||||
break
|
||||
}
|
||||
case 'prerender-legacy': {
|
||||
// legacy prerenders can't have fallback params
|
||||
break
|
||||
@@ -172,3 +199,36 @@ function createPrerenderRootParamPromise(
|
||||
accumulateRootVaryParam(paramName)
|
||||
return Promise.resolve(underlyingParams[paramName])
|
||||
}
|
||||
|
||||
/** Deliberately async -- we want to create a rejected promise, not error synchronously. */
|
||||
async function makeErroringRootParamPromise(
|
||||
paramName: string,
|
||||
workStore: WorkStore,
|
||||
prerenderStore: PrerenderStorePPR | PrerenderStoreLegacy,
|
||||
apiName: string
|
||||
): Promise<ParamValue> {
|
||||
const expression = describeStringPropertyAccess(apiName, paramName)
|
||||
// In most dynamic APIs, we also throw if `dynamic = "error"`.
|
||||
// However, root params are only dynamic when we're generating a fallback shell,
|
||||
// and even with `dynamic = "error"` we still support generating dynamic fallback shells.
|
||||
// TODO: remove this comment when cacheComponents is the default since there will be no `dynamic = "error"`
|
||||
switch (prerenderStore.type) {
|
||||
case 'prerender-ppr': {
|
||||
return postponeWithTracking(
|
||||
workStore.route,
|
||||
expression,
|
||||
prerenderStore.dynamicTracking
|
||||
)
|
||||
}
|
||||
case 'prerender-legacy': {
|
||||
return throwToInterruptStaticGeneration(
|
||||
expression,
|
||||
workStore,
|
||||
prerenderStore
|
||||
)
|
||||
}
|
||||
default: {
|
||||
prerenderStore satisfies never
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,12 +11,14 @@ import {
|
||||
import { ReflectAdapter } from '../web/spec-extension/adapters/reflect'
|
||||
import {
|
||||
throwToInterruptStaticGeneration,
|
||||
postponeWithTracking,
|
||||
annotateDynamicAccess,
|
||||
} from '../app-render/dynamic-rendering'
|
||||
|
||||
import {
|
||||
workUnitAsyncStorage,
|
||||
type PrerenderStoreLegacy,
|
||||
type PrerenderStorePPR,
|
||||
type PrerenderStoreModern,
|
||||
type PrerenderStoreModernRuntime,
|
||||
type StaticPrerenderStore,
|
||||
@@ -57,6 +59,7 @@ export function createSearchParamsFromClient(
|
||||
switch (workUnitStore.type) {
|
||||
case 'prerender':
|
||||
case 'prerender-client':
|
||||
case 'prerender-ppr':
|
||||
case 'prerender-legacy':
|
||||
return createStaticPrerenderSearchParams(workStore, workUnitStore)
|
||||
case 'validation-client': {
|
||||
@@ -123,6 +126,7 @@ export function createServerSearchParamsForServerPage(
|
||||
switch (workUnitStore.type) {
|
||||
case 'prerender':
|
||||
case 'prerender-client':
|
||||
case 'prerender-ppr':
|
||||
case 'prerender-legacy':
|
||||
return createStaticPrerenderSearchParams(workStore, workUnitStore)
|
||||
case 'validation-client':
|
||||
@@ -201,6 +205,7 @@ export function createPrerenderSearchParamsForClientPage(): Promise<SearchParams
|
||||
throw new InvariantError(
|
||||
'createPrerenderSearchParamsForClientPage should not be called inside generateStaticParams.'
|
||||
)
|
||||
case 'prerender-ppr':
|
||||
case 'prerender-legacy':
|
||||
case 'request':
|
||||
return Promise.resolve({})
|
||||
@@ -226,6 +231,7 @@ function createStaticPrerenderSearchParams(
|
||||
case 'prerender-client':
|
||||
// We are in a cacheComponents (PPR or otherwise) prerender
|
||||
return makeHangingSearchParams(workStore, prerenderStore)
|
||||
case 'prerender-ppr':
|
||||
case 'prerender-legacy':
|
||||
// We are in a legacy static generation and need to interrupt the
|
||||
// prerender when search params are accessed.
|
||||
@@ -415,7 +421,7 @@ function makeHangingSearchParams(
|
||||
|
||||
function makeErroringSearchParams(
|
||||
workStore: WorkStore,
|
||||
prerenderStore: PrerenderStoreLegacy
|
||||
prerenderStore: PrerenderStoreLegacy | PrerenderStorePPR
|
||||
): Promise<SearchParams> {
|
||||
const cachedSearchParams = CachedSearchParams.get(workStore)
|
||||
if (cachedSearchParams) {
|
||||
@@ -445,6 +451,13 @@ function makeErroringSearchParams(
|
||||
workStore.route,
|
||||
expression
|
||||
)
|
||||
} else if (prerenderStore.type === 'prerender-ppr') {
|
||||
// PPR Prerender (no cacheComponents)
|
||||
postponeWithTracking(
|
||||
workStore.route,
|
||||
expression,
|
||||
prerenderStore.dynamicTracking
|
||||
)
|
||||
} else {
|
||||
// Legacy Prerender
|
||||
throwToInterruptStaticGeneration(
|
||||
|
||||
@@ -60,6 +60,7 @@ import { StaticGenBailoutError } from '../../../client/components/static-generat
|
||||
import { isStaticGenEnabled } from './helpers/is-static-gen-enabled'
|
||||
import {
|
||||
abortAndThrowOnSynchronousRequestDataAccess,
|
||||
postponeWithTracking,
|
||||
createDynamicTrackingState,
|
||||
getFirstDynamicReason,
|
||||
} from '../../app-render/dynamic-rendering'
|
||||
@@ -1351,6 +1352,12 @@ function trackDynamic(
|
||||
throw new InvariantError(
|
||||
'A runtime prerender store should not be used for a route handler.'
|
||||
)
|
||||
case 'prerender-ppr':
|
||||
return postponeWithTracking(
|
||||
store.route,
|
||||
expression,
|
||||
workUnitStore.dynamicTracking
|
||||
)
|
||||
case 'prerender-legacy':
|
||||
workUnitStore.revalidate = 0
|
||||
|
||||
|
||||
@@ -88,6 +88,7 @@ export function cacheLife(profile: CacheLifeProfiles | CacheLife): void {
|
||||
case 'prerender-client':
|
||||
case 'validation-client':
|
||||
case 'prerender-runtime':
|
||||
case 'prerender-ppr':
|
||||
case 'prerender-legacy':
|
||||
case 'request':
|
||||
case 'unstable-cache':
|
||||
|
||||
@@ -15,6 +15,7 @@ export function cacheTag(...tags: string[]): void {
|
||||
case 'prerender-client':
|
||||
case 'validation-client':
|
||||
case 'prerender-runtime':
|
||||
case 'prerender-ppr':
|
||||
case 'prerender-legacy':
|
||||
case 'request':
|
||||
case 'unstable-cache':
|
||||
|
||||
@@ -83,6 +83,7 @@ import {
|
||||
} from './use-cache-errors'
|
||||
import {
|
||||
createHangingInputAbortSignal,
|
||||
postponeWithTracking,
|
||||
throwToInterruptStaticGeneration,
|
||||
} from '../app-render/dynamic-rendering'
|
||||
import {
|
||||
@@ -685,6 +686,7 @@ function createUseCacheStore(
|
||||
break
|
||||
case 'prerender-runtime':
|
||||
case 'prerender':
|
||||
case 'prerender-ppr':
|
||||
case 'prerender-legacy':
|
||||
case 'unstable-cache':
|
||||
case 'generate-static-params':
|
||||
@@ -744,6 +746,7 @@ function captureOuterOwnerStack(
|
||||
case 'unstable-cache':
|
||||
case 'request':
|
||||
case 'prerender':
|
||||
case 'prerender-ppr':
|
||||
case 'prerender-legacy':
|
||||
case 'prerender-runtime':
|
||||
case 'prerender-client':
|
||||
@@ -913,6 +916,7 @@ function propagateCacheEntryMetadata(
|
||||
case 'private-cache':
|
||||
case 'prerender':
|
||||
case 'prerender-runtime':
|
||||
case 'prerender-ppr':
|
||||
case 'prerender-legacy':
|
||||
propagateCacheLifeAndTagsToRevalidateStore(
|
||||
cacheContext.outerWorkUnitStore,
|
||||
@@ -977,7 +981,8 @@ function maybePropagateCacheEntryMetadata(
|
||||
case 'private-cache':
|
||||
case 'cache':
|
||||
case 'unstable-cache':
|
||||
case 'prerender-legacy': {
|
||||
case 'prerender-legacy':
|
||||
case 'prerender-ppr': {
|
||||
propagateCacheEntryMetadata(cacheContext, metadata)
|
||||
break
|
||||
}
|
||||
@@ -1216,6 +1221,7 @@ async function generateCacheEntryImpl(
|
||||
}
|
||||
})
|
||||
break
|
||||
case 'prerender-ppr':
|
||||
case 'prerender-legacy':
|
||||
case 'request':
|
||||
case 'cache':
|
||||
@@ -1471,6 +1477,7 @@ async function generateCacheEntryImpl(
|
||||
}
|
||||
}
|
||||
// fallthrough
|
||||
case 'prerender-ppr':
|
||||
case 'prerender-legacy':
|
||||
case 'cache':
|
||||
case 'private-cache':
|
||||
@@ -1716,6 +1723,12 @@ export async function cache(
|
||||
workStore.route,
|
||||
expression
|
||||
)
|
||||
case 'prerender-ppr':
|
||||
return postponeWithTracking(
|
||||
workStore.route,
|
||||
expression,
|
||||
workUnitStore.dynamicTracking
|
||||
)
|
||||
case 'prerender-legacy':
|
||||
return throwToInterruptStaticGeneration(
|
||||
expression,
|
||||
@@ -1803,6 +1816,7 @@ export async function cache(
|
||||
}
|
||||
case 'prerender':
|
||||
case 'prerender-runtime':
|
||||
case 'prerender-ppr':
|
||||
case 'prerender-legacy':
|
||||
case 'request':
|
||||
case 'private-cache':
|
||||
@@ -2058,6 +2072,7 @@ export async function cache(
|
||||
break
|
||||
}
|
||||
// fallthrough
|
||||
case 'prerender-ppr':
|
||||
case 'prerender-legacy':
|
||||
case 'request':
|
||||
// TODO(restart-on-cache-miss): We need to handle params/searchParams on page components.
|
||||
@@ -2196,6 +2211,7 @@ export async function cache(
|
||||
workStore.route,
|
||||
'dynamic "use cache"'
|
||||
)
|
||||
case 'prerender-ppr':
|
||||
case 'prerender-legacy':
|
||||
case 'request':
|
||||
case 'cache':
|
||||
@@ -2350,6 +2366,7 @@ export async function cache(
|
||||
}
|
||||
break
|
||||
}
|
||||
case 'prerender-ppr':
|
||||
case 'prerender-legacy':
|
||||
case 'cache':
|
||||
case 'private-cache':
|
||||
@@ -2403,6 +2420,7 @@ export async function cache(
|
||||
}
|
||||
break
|
||||
}
|
||||
case 'prerender-ppr':
|
||||
case 'prerender-legacy':
|
||||
case 'cache':
|
||||
case 'private-cache':
|
||||
@@ -2510,6 +2528,7 @@ export async function cache(
|
||||
)
|
||||
}
|
||||
break
|
||||
case 'prerender-ppr':
|
||||
case 'prerender-legacy':
|
||||
case 'request':
|
||||
case 'cache':
|
||||
@@ -2872,6 +2891,7 @@ export async function cache(
|
||||
break
|
||||
}
|
||||
case 'prerender-runtime':
|
||||
case 'prerender-ppr':
|
||||
case 'prerender-legacy':
|
||||
case 'cache':
|
||||
case 'private-cache':
|
||||
@@ -2907,6 +2927,7 @@ export async function cache(
|
||||
}
|
||||
case 'prerender':
|
||||
case 'prerender-runtime':
|
||||
case 'prerender-ppr':
|
||||
case 'prerender-legacy':
|
||||
case 'cache':
|
||||
case 'private-cache':
|
||||
@@ -3282,6 +3303,7 @@ function shouldForceRevalidate(
|
||||
case 'prerender':
|
||||
case 'prerender-client':
|
||||
case 'validation-client':
|
||||
case 'prerender-ppr':
|
||||
case 'prerender-legacy':
|
||||
case 'unstable-cache':
|
||||
case 'generate-static-params':
|
||||
@@ -3325,6 +3347,7 @@ function shouldDiscardCacheEntry(
|
||||
case 'prerender-runtime':
|
||||
case 'prerender-client':
|
||||
case 'validation-client':
|
||||
case 'prerender-ppr':
|
||||
case 'prerender-legacy':
|
||||
case 'request':
|
||||
case 'cache':
|
||||
|
||||
@@ -315,6 +315,7 @@ export async function adapter(
|
||||
// TODO: remove validationLevel and other global config from renderOpts
|
||||
validationLevel: 'warning',
|
||||
experimental: {
|
||||
isRoutePPREnabled: false,
|
||||
authInterrupts:
|
||||
!!params.request.nextConfig?.experimental?.authInterrupts,
|
||||
// Proxy doesn't fill Cache Components entries, so this value
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
import { abortAndThrowOnSynchronousRequestDataAccess } from '../../app-render/dynamic-rendering'
|
||||
import {
|
||||
abortAndThrowOnSynchronousRequestDataAccess,
|
||||
postponeWithTracking,
|
||||
} from '../../app-render/dynamic-rendering'
|
||||
import { isDynamicRoute } from '../../../shared/lib/router/utils'
|
||||
import {
|
||||
NEXT_CACHE_IMPLICIT_TAG_ID,
|
||||
@@ -170,6 +173,12 @@ function revalidate(
|
||||
throw new InvariantError(
|
||||
`${expression} must not be used within a client component. Next.js should be preventing ${expression} from being included in client components statically, but did not in this case.`
|
||||
)
|
||||
case 'prerender-ppr':
|
||||
return postponeWithTracking(
|
||||
store.route,
|
||||
expression,
|
||||
workUnitStore.dynamicTracking
|
||||
)
|
||||
case 'prerender-legacy':
|
||||
workUnitStore.revalidate = 0
|
||||
|
||||
|
||||
@@ -166,6 +166,7 @@ export function unstable_cache<T extends Callback>(
|
||||
case 'private-cache':
|
||||
case 'prerender':
|
||||
case 'prerender-runtime':
|
||||
case 'prerender-ppr':
|
||||
case 'prerender-legacy':
|
||||
// We update the store's revalidate property if the option.revalidate is a higher precedence
|
||||
// options.revalidate === undefined doesn't affect timing.
|
||||
@@ -417,6 +418,7 @@ function getFetchUrlPrefix(
|
||||
case 'prerender-client':
|
||||
case 'validation-client':
|
||||
case 'prerender-runtime':
|
||||
case 'prerender-ppr':
|
||||
case 'prerender-legacy':
|
||||
case 'cache':
|
||||
case 'private-cache':
|
||||
|
||||
@@ -38,6 +38,7 @@ export function unstable_noStore() {
|
||||
case 'prerender-runtime':
|
||||
// unstable_noStore() is a noop in Dynamic I/O.
|
||||
return
|
||||
case 'prerender-ppr':
|
||||
case 'prerender-legacy':
|
||||
case 'request':
|
||||
case 'cache':
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
import { InvariantError } from './invariant-error'
|
||||
|
||||
/**
|
||||
* Throws an InvariantError indicating that a prerender-ppr code path was
|
||||
* reached. The prerender-ppr work unit type has been removed and all code
|
||||
* handling it is dead. Use this in exhaustive switch cases while the
|
||||
* prerender-ppr type is being cleaned up.
|
||||
*/
|
||||
export function throwPrerenderPPRRemovedError(): never {
|
||||
throw new InvariantError(
|
||||
'The prerender-ppr work unit type has been removed. This code path should be unreachable.'
|
||||
)
|
||||
}
|
||||
+42
-42
@@ -319,13 +319,13 @@ describe('Cache Components Errors - Client Hook Abort Reasons', () => {
|
||||
at useSearchParams (webpack:///<next-src>)
|
||||
at UseSearchParams (webpack:///app/client-hook-abort-reasons/client.tsx:27:18)
|
||||
at Page (webpack:///app/client-hook-abort-reasons/normal/use-search-params/[id]/page.tsx:8:7)
|
||||
607 | return
|
||||
608 | case 'prerender-client': {
|
||||
> 609 | React.use(
|
||||
714 | return
|
||||
715 | case 'prerender-client': {
|
||||
> 716 | React.use(
|
||||
| ^
|
||||
610 | makeClientHookHangingPromise(
|
||||
611 | workUnitStore.renderSignal,
|
||||
612 | new ClientHookDynamicError(workStore.route, expression) {
|
||||
717 | makeClientHookHangingPromise(
|
||||
718 | workUnitStore.renderSignal,
|
||||
719 | new ClientHookDynamicError(workStore.route, expression) {
|
||||
digest: 'CLIENT_HOOK_DYNAMIC'
|
||||
}
|
||||
To debug the issue, start the app in development mode by running \`next dev\`, then open "/client-hook-abort-reasons/normal/use-search-params/[id]" in your browser to investigate the error.
|
||||
@@ -613,13 +613,13 @@ describe('Cache Components Errors - Client Hook Abort Reasons', () => {
|
||||
at usePathname (webpack:///<next-src>)
|
||||
at UsePathname (webpack:///app/client-hook-abort-reasons/client.tsx:22:14)
|
||||
at Page (webpack:///app/client-hook-abort-reasons/normal/use-pathname/[id]/page.tsx:7:7)
|
||||
547 | // hang here and never resolve. This will cause the currently
|
||||
548 | // rendering component to effectively be a dynamic hole.
|
||||
> 549 | React.use(
|
||||
643 | // hang here and never resolve. This will cause the currently
|
||||
644 | // rendering component to effectively be a dynamic hole.
|
||||
> 645 | React.use(
|
||||
| ^
|
||||
550 | makeClientHookHangingPromise(
|
||||
551 | workUnitStore.renderSignal,
|
||||
552 | new ClientHookDynamicError(workStore.route, expression) {
|
||||
646 | makeClientHookHangingPromise(
|
||||
647 | workUnitStore.renderSignal,
|
||||
648 | new ClientHookDynamicError(workStore.route, expression) {
|
||||
digest: 'CLIENT_HOOK_DYNAMIC'
|
||||
}
|
||||
To debug the issue, start the app in development mode by running \`next dev\`, then open "/client-hook-abort-reasons/normal/use-pathname/[id]" in your browser to investigate the error.
|
||||
@@ -907,13 +907,13 @@ describe('Cache Components Errors - Client Hook Abort Reasons', () => {
|
||||
at useParams (webpack:///<next-src>)
|
||||
at UseParams (webpack:///app/client-hook-abort-reasons/client.tsx:17:12)
|
||||
at Page (webpack:///app/client-hook-abort-reasons/normal/use-params/[id]/page.tsx:8:7)
|
||||
547 | // hang here and never resolve. This will cause the currently
|
||||
548 | // rendering component to effectively be a dynamic hole.
|
||||
> 549 | React.use(
|
||||
643 | // hang here and never resolve. This will cause the currently
|
||||
644 | // rendering component to effectively be a dynamic hole.
|
||||
> 645 | React.use(
|
||||
| ^
|
||||
550 | makeClientHookHangingPromise(
|
||||
551 | workUnitStore.renderSignal,
|
||||
552 | new ClientHookDynamicError(workStore.route, expression) {
|
||||
646 | makeClientHookHangingPromise(
|
||||
647 | workUnitStore.renderSignal,
|
||||
648 | new ClientHookDynamicError(workStore.route, expression) {
|
||||
digest: 'CLIENT_HOOK_DYNAMIC'
|
||||
}
|
||||
To debug the issue, start the app in development mode by running \`next dev\`, then open "/client-hook-abort-reasons/normal/use-params/[id]" in your browser to investigate the error.
|
||||
@@ -1201,13 +1201,13 @@ describe('Cache Components Errors - Client Hook Abort Reasons', () => {
|
||||
at useSelectedLayoutSegments (webpack:///<next-src>)
|
||||
at UseSelectedLayoutSegments (webpack:///app/client-hook-abort-reasons/client.tsx:37:28)
|
||||
at Page (webpack:///app/client-hook-abort-reasons/normal/use-selected-layout-segments/[id]/page.tsx:7:7)
|
||||
547 | // hang here and never resolve. This will cause the currently
|
||||
548 | // rendering component to effectively be a dynamic hole.
|
||||
> 549 | React.use(
|
||||
643 | // hang here and never resolve. This will cause the currently
|
||||
644 | // rendering component to effectively be a dynamic hole.
|
||||
> 645 | React.use(
|
||||
| ^
|
||||
550 | makeClientHookHangingPromise(
|
||||
551 | workUnitStore.renderSignal,
|
||||
552 | new ClientHookDynamicError(workStore.route, expression) {
|
||||
646 | makeClientHookHangingPromise(
|
||||
647 | workUnitStore.renderSignal,
|
||||
648 | new ClientHookDynamicError(workStore.route, expression) {
|
||||
digest: 'CLIENT_HOOK_DYNAMIC'
|
||||
}
|
||||
To debug the issue, start the app in development mode by running \`next dev\`, then open "/client-hook-abort-reasons/normal/use-selected-layout-segments/[id]" in your browser to investigate the error.
|
||||
@@ -1495,13 +1495,13 @@ describe('Cache Components Errors - Client Hook Abort Reasons', () => {
|
||||
at useSelectedLayoutSegment (webpack:///<next-src>)
|
||||
at UseSelectedLayoutSegment (webpack:///app/client-hook-abort-reasons/client.tsx:32:27)
|
||||
at Page (webpack:///app/client-hook-abort-reasons/normal/use-selected-layout-segment/[id]/page.tsx:8:7)
|
||||
547 | // hang here and never resolve. This will cause the currently
|
||||
548 | // rendering component to effectively be a dynamic hole.
|
||||
> 549 | React.use(
|
||||
643 | // hang here and never resolve. This will cause the currently
|
||||
644 | // rendering component to effectively be a dynamic hole.
|
||||
> 645 | React.use(
|
||||
| ^
|
||||
550 | makeClientHookHangingPromise(
|
||||
551 | workUnitStore.renderSignal,
|
||||
552 | new ClientHookDynamicError(workStore.route, expression) {
|
||||
646 | makeClientHookHangingPromise(
|
||||
647 | workUnitStore.renderSignal,
|
||||
648 | new ClientHookDynamicError(workStore.route, expression) {
|
||||
digest: 'CLIENT_HOOK_DYNAMIC'
|
||||
}
|
||||
To debug the issue, start the app in development mode by running \`next dev\`, then open "/client-hook-abort-reasons/normal/use-selected-layout-segment/[id]" in your browser to investigate the error.
|
||||
@@ -2227,13 +2227,13 @@ describe('Cache Components Errors - Client Hook Abort Reasons', () => {
|
||||
at useParams (webpack:///<next-src>)
|
||||
at UseParams (webpack:///app/client-hook-abort-reasons/client.tsx:17:12)
|
||||
at Page (webpack:///app/client-hook-abort-reasons/sync-io/use-params/[id]/page.tsx:7:7)
|
||||
547 | // hang here and never resolve. This will cause the currently
|
||||
548 | // rendering component to effectively be a dynamic hole.
|
||||
> 549 | React.use(
|
||||
643 | // hang here and never resolve. This will cause the currently
|
||||
644 | // rendering component to effectively be a dynamic hole.
|
||||
> 645 | React.use(
|
||||
| ^
|
||||
550 | makeClientHookHangingPromise(
|
||||
551 | workUnitStore.renderSignal,
|
||||
552 | new ClientHookDynamicError(workStore.route, expression) {
|
||||
646 | makeClientHookHangingPromise(
|
||||
647 | workUnitStore.renderSignal,
|
||||
648 | new ClientHookDynamicError(workStore.route, expression) {
|
||||
digest: 'CLIENT_HOOK_DYNAMIC'
|
||||
}
|
||||
To debug the issue, start the app in development mode by running \`next dev\`, then open "/client-hook-abort-reasons/sync-io/use-params/[id]" in your browser to investigate the error.
|
||||
@@ -2739,13 +2739,13 @@ describe('Cache Components Errors - Client Hook Abort Reasons', () => {
|
||||
at useSelectedLayoutSegment (webpack:///<next-src>)
|
||||
at UseSelectedLayoutSegment (webpack:///app/client-hook-abort-reasons/client.tsx:32:27)
|
||||
at Page (webpack:///app/client-hook-abort-reasons/sync-io/use-selected-layout-segment/[id]/page.tsx:8:7)
|
||||
547 | // hang here and never resolve. This will cause the currently
|
||||
548 | // rendering component to effectively be a dynamic hole.
|
||||
> 549 | React.use(
|
||||
643 | // hang here and never resolve. This will cause the currently
|
||||
644 | // rendering component to effectively be a dynamic hole.
|
||||
> 645 | React.use(
|
||||
| ^
|
||||
550 | makeClientHookHangingPromise(
|
||||
551 | workUnitStore.renderSignal,
|
||||
552 | new ClientHookDynamicError(workStore.route, expression) {
|
||||
646 | makeClientHookHangingPromise(
|
||||
647 | workUnitStore.renderSignal,
|
||||
648 | new ClientHookDynamicError(workStore.route, expression) {
|
||||
digest: 'CLIENT_HOOK_DYNAMIC'
|
||||
}
|
||||
To debug the issue, start the app in development mode by running \`next dev\`, then open "/client-hook-abort-reasons/sync-io/use-selected-layout-segment/[id]" in your browser to investigate the error.
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
import React from 'react'
|
||||
|
||||
export default function Root({ children }) {
|
||||
return (
|
||||
<html>
|
||||
<body>{children}</body>
|
||||
</html>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
import { Suspense } from 'react'
|
||||
import { cookies } from 'next/headers'
|
||||
|
||||
export default async function Page() {
|
||||
return (
|
||||
<Suspense fallback={<div>Loading...</div>}>
|
||||
<Foobar />
|
||||
</Suspense>
|
||||
)
|
||||
}
|
||||
|
||||
async function Foobar() {
|
||||
try {
|
||||
await cookies()
|
||||
} catch (err) {
|
||||
console.log('User land logged error: ' + err.message)
|
||||
}
|
||||
await cookies() // still postpones so doesn't fail build
|
||||
return null
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
import React from 'react'
|
||||
import { cookies } from 'next/headers'
|
||||
|
||||
export default async function Page() {
|
||||
try {
|
||||
await cookies()
|
||||
} catch (err) {
|
||||
throw new Error(
|
||||
"Throwing a new error from 'no-suspense-boundary-re-throwing-error'"
|
||||
)
|
||||
}
|
||||
return <div>Hello World</div>
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
import React from 'react'
|
||||
import { cookies } from 'next/headers'
|
||||
|
||||
export default async function Page() {
|
||||
try {
|
||||
await cookies()
|
||||
} catch (err) {}
|
||||
return <div>Hello World</div>
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
import React, { Suspense } from 'react'
|
||||
import { cookies } from 'next/headers'
|
||||
|
||||
export default async function Page() {
|
||||
return (
|
||||
<Suspense fallback={<div>Loading...</div>}>
|
||||
<Foobar />
|
||||
</Suspense>
|
||||
)
|
||||
}
|
||||
|
||||
async function Foobar() {
|
||||
try {
|
||||
await cookies()
|
||||
} catch (err) {}
|
||||
return null
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
import React, { Suspense } from 'react'
|
||||
import { cookies } from 'next/headers'
|
||||
|
||||
export default async function Page() {
|
||||
return (
|
||||
<Suspense fallback={<div>Loading...</div>}>
|
||||
<Foobar />
|
||||
</Suspense>
|
||||
)
|
||||
}
|
||||
|
||||
async function Foobar() {
|
||||
try {
|
||||
await cookies()
|
||||
} catch (err) {
|
||||
throw new Error('The original error was caught and rethrown.')
|
||||
}
|
||||
return null
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
import React, { Suspense } from 'react'
|
||||
|
||||
export default async function Page() {
|
||||
return (
|
||||
<Suspense fallback={<div>Loading...</div>}>
|
||||
<Foobar />
|
||||
</Suspense>
|
||||
)
|
||||
}
|
||||
|
||||
async function Foobar() {
|
||||
throw new Error('Kaboom')
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
export default async function Page() {
|
||||
throw new Error('Regular Prerender Error')
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
/**
|
||||
* @type {import('next').NextConfig}
|
||||
*/
|
||||
const nextConfig = {
|
||||
cacheComponents: true,
|
||||
experimental: { prerenderEarlyExit: false },
|
||||
}
|
||||
|
||||
module.exports = nextConfig
|
||||
@@ -0,0 +1,48 @@
|
||||
import { isNextStart, nextTestSetup } from 'e2e-utils'
|
||||
|
||||
// TODO(NAR-423): Migrate to Cache Components.
|
||||
describe.skip('ppr build errors', () => {
|
||||
;(isNextStart ? describe : describe.skip)('production only', () => {
|
||||
const { next } = nextTestSetup({
|
||||
files: __dirname,
|
||||
skipStart: true,
|
||||
})
|
||||
|
||||
let cliOutput: string
|
||||
|
||||
beforeAll(async () => {
|
||||
const output = await next.build()
|
||||
cliOutput = output.cliOutput
|
||||
})
|
||||
|
||||
describe('within a suspense boundary', () => {
|
||||
it('should fail the build for uncaught prerender errors', async () => {
|
||||
expect(cliOutput).toContain(
|
||||
'Error occurred prerendering page "/regular-error-suspense-boundary".'
|
||||
)
|
||||
expect(cliOutput).toContain(
|
||||
'Error occurred prerendering page "/re-throwing-error".'
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('outside of a suspense boundary', () => {
|
||||
it('should fail the build for uncaught errors', async () => {
|
||||
expect(cliOutput).toContain(
|
||||
'Error occurred prerendering page "/regular-error".'
|
||||
)
|
||||
expect(cliOutput).toContain(
|
||||
'Error occurred prerendering page "/no-suspense-boundary-re-throwing-error".'
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('when a postpone call is caught and logged it should', () => {
|
||||
it('should include a message telling why', async () => {
|
||||
expect(cliOutput).toContain(
|
||||
'User land logged error: Route /logging-error needs to bail out of prerendering at this point because it used cookies.'
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -2,10 +2,10 @@ import { nextTestSetup } from 'e2e-utils'
|
||||
|
||||
// TODO(NAR-423): Migrate to Cache Components.
|
||||
describe.skip('static-shell-debugging', () => {
|
||||
const cacheComponents = Boolean(process.env.__NEXT_CACHE_COMPONENTS)
|
||||
const ppr = Boolean(process.env.__NEXT_CACHE_COMPONENTS)
|
||||
const context = {
|
||||
cacheComponents,
|
||||
debugging: cacheComponents,
|
||||
ppr,
|
||||
debugging: ppr,
|
||||
}
|
||||
|
||||
const { next, skipped, isNextDev } = nextTestSetup({
|
||||
@@ -19,13 +19,13 @@ describe.skip('static-shell-debugging', () => {
|
||||
: undefined,
|
||||
},
|
||||
nextConfig: {
|
||||
cacheComponents: context.cacheComponents,
|
||||
experimental: { ppr: context.ppr },
|
||||
},
|
||||
})
|
||||
|
||||
if (skipped) return
|
||||
|
||||
if (context.debugging && context.cacheComponents) {
|
||||
if (context.debugging && context.ppr) {
|
||||
it('should only render the static shell', async () => {
|
||||
const res = await next.fetch('/?__nextppronly=1')
|
||||
expect(res.status).toBe(200)
|
||||
|
||||
@@ -159,7 +159,7 @@ describe('Config Experimental Warning', () => {
|
||||
nextConfig: {
|
||||
experimental: {
|
||||
ppr: 'incremental',
|
||||
} as any,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
|
||||
+15
-15
@@ -2,7 +2,7 @@ import React from 'react';
|
||||
import { DynamicServerError } from '../../client/components/hooks-server-context';
|
||||
import { StaticGenBailoutError } from '../../client/components/static-generation-bailout';
|
||||
import { getPathname } from '../../lib/url';
|
||||
const hasUnnamed = typeof React.unstable_unnamed === 'function';
|
||||
const hasPostpone = typeof React.unstable_postpone === 'function';
|
||||
export function createPrerenderState(isDebugSkeleton) {
|
||||
return {
|
||||
isDebugSkeleton,
|
||||
@@ -16,7 +16,7 @@ export function markCurrentScopeAsDynamic(store, expression) {
|
||||
} else if (store.dynamicShouldError) {
|
||||
throw new StaticGenBailoutError(`Route ${pathname} with \`dynamic = "error"\` couldn't be rendered statically because it used \`${expression}\`. See more info here: https://nextjs.org/docs/app/building-your-application/rendering/static-and-dynamic#dynamic-rendering`);
|
||||
} else if (store.prerenderState) {
|
||||
markPrerenderState(store.prerenderState, expression, pathname);
|
||||
postponeWithTracking(store.prerenderState, expression, pathname);
|
||||
} else {
|
||||
store.revalidate = 0;
|
||||
if (store.isStaticGeneration) {
|
||||
@@ -34,7 +34,7 @@ export function trackDynamicDataAccessed(store, expression) {
|
||||
} else if (store.dynamicShouldError) {
|
||||
throw new StaticGenBailoutError(`Route ${pathname} with \`dynamic = "error"\` couldn't be rendered statically because it used \`${expression}\`. See more info here: https://nextjs.org/docs/app/building-your-application/rendering/static-and-dynamic#dynamic-rendering`);
|
||||
} else if (store.prerenderState) {
|
||||
markPrerenderState(store.prerenderState, expression, pathname);
|
||||
postponeWithTracking(store.prerenderState, expression, pathname);
|
||||
} else {
|
||||
store.revalidate = 0;
|
||||
if (store.isStaticGeneration) {
|
||||
@@ -45,21 +45,21 @@ export function trackDynamicDataAccessed(store, expression) {
|
||||
}
|
||||
}
|
||||
}
|
||||
export function ApiThatShallNotBeNamed({ reason, prerenderState, pathname }) {
|
||||
markPrerenderState(prerenderState, reason, pathname);
|
||||
export function Postpone({ reason, prerenderState, pathname }) {
|
||||
postponeWithTracking(prerenderState, reason, pathname);
|
||||
}
|
||||
export function trackDynamicFetch(store, expression) {
|
||||
if (!store.prerenderState || store.isUnstableCacheCallback) return;
|
||||
markPrerenderState(store.prerenderState, expression, store.urlPathname);
|
||||
postponeWithTracking(store.prerenderState, expression, store.urlPathname);
|
||||
}
|
||||
function markPrerenderState(prerenderState, expression, pathname) {
|
||||
assertUnnamed();
|
||||
function postponeWithTracking(prerenderState, expression, pathname) {
|
||||
assertPostpone();
|
||||
const reason = `Route ${pathname} needs to bail out of prerendering at this point because it used ${expression}. ` + `React throws this special object to indicate where. It should not be caught by ` + `your own try/catch. Learn more: https://nextjs.org/docs/messages/ppr-caught-error`;
|
||||
prerenderState.dynamicAccesses.push({
|
||||
stack: prerenderState.isDebugSkeleton ? new Error().stack : undefined,
|
||||
expression
|
||||
});
|
||||
React.unstable_unnamed(reason);
|
||||
React.unstable_postpone(reason);
|
||||
}
|
||||
export function usedDynamicAPIs(prerenderState) {
|
||||
return prerenderState.dynamicAccesses.length > 0;
|
||||
@@ -81,16 +81,16 @@ export function formatDynamicAPIAccesses(prerenderState) {
|
||||
return `Dynamic API Usage Debug - ${expression}:\n${stack}`;
|
||||
});
|
||||
}
|
||||
function assertUnnamed() {
|
||||
if (!hasUnnamed) {
|
||||
throw new Error(`Invariant: React.unstable_unnamed is not defined. This suggests the wrong version of React was loaded. This is a bug in Next.js`);
|
||||
function assertPostpone() {
|
||||
if (!hasPostpone) {
|
||||
throw new Error(`Invariant: React.unstable_postpone is not defined. This suggests the wrong version of React was loaded. This is a bug in Next.js`);
|
||||
}
|
||||
}
|
||||
export function createUnnamedAbortSignal(reason) {
|
||||
assertUnnamed();
|
||||
export function createPostponedAbortSignal(reason) {
|
||||
assertPostpone();
|
||||
const controller = new AbortController();
|
||||
try {
|
||||
React.unstable_unnamed(reason);
|
||||
React.unstable_postpone(reason);
|
||||
} catch (x) {
|
||||
controller.abort(x);
|
||||
}
|
||||
|
||||
+126
-126
@@ -85,14 +85,14 @@ import { getPathname } from '../../lib/url';
|
||||
## Item 9: Stmt 4, `VarDeclarator(0)`
|
||||
|
||||
```js
|
||||
const hasUnnamed = typeof React.unstable_unnamed === 'function';
|
||||
const hasPostpone = typeof React.unstable_postpone === 'function';
|
||||
|
||||
```
|
||||
|
||||
- Side effects
|
||||
- Declares: `hasUnnamed`
|
||||
- Declares: `hasPostpone`
|
||||
- Reads: `React`
|
||||
- Write: `React`, `hasUnnamed`
|
||||
- Write: `React`, `hasPostpone`
|
||||
|
||||
## Item 10: Stmt 5, `Normal`
|
||||
|
||||
@@ -120,7 +120,7 @@ export function markCurrentScopeAsDynamic(store, expression) {
|
||||
} else if (store.dynamicShouldError) {
|
||||
throw new StaticGenBailoutError(`Route ${pathname} with \`dynamic = "error"\` couldn't be rendered statically because it used \`${expression}\`. See more info here: https://nextjs.org/docs/app/building-your-application/rendering/static-and-dynamic#dynamic-rendering`);
|
||||
} else if (store.prerenderState) {
|
||||
markPrerenderState(store.prerenderState, expression, pathname);
|
||||
postponeWithTracking(store.prerenderState, expression, pathname);
|
||||
} else {
|
||||
store.revalidate = 0;
|
||||
if (store.isStaticGeneration) {
|
||||
@@ -136,7 +136,7 @@ export function markCurrentScopeAsDynamic(store, expression) {
|
||||
|
||||
- Hoisted
|
||||
- Declares: `markCurrentScopeAsDynamic`
|
||||
- Reads (eventual): `getPathname`, `StaticGenBailoutError`, `markPrerenderState`, `DynamicServerError`
|
||||
- Reads (eventual): `getPathname`, `StaticGenBailoutError`, `postponeWithTracking`, `DynamicServerError`
|
||||
- Write: `markCurrentScopeAsDynamic`
|
||||
|
||||
## Item 12: Stmt 7, `Normal`
|
||||
@@ -149,7 +149,7 @@ export function trackDynamicDataAccessed(store, expression) {
|
||||
} else if (store.dynamicShouldError) {
|
||||
throw new StaticGenBailoutError(`Route ${pathname} with \`dynamic = "error"\` couldn't be rendered statically because it used \`${expression}\`. See more info here: https://nextjs.org/docs/app/building-your-application/rendering/static-and-dynamic#dynamic-rendering`);
|
||||
} else if (store.prerenderState) {
|
||||
markPrerenderState(store.prerenderState, expression, pathname);
|
||||
postponeWithTracking(store.prerenderState, expression, pathname);
|
||||
} else {
|
||||
store.revalidate = 0;
|
||||
if (store.isStaticGeneration) {
|
||||
@@ -165,57 +165,57 @@ export function trackDynamicDataAccessed(store, expression) {
|
||||
|
||||
- Hoisted
|
||||
- Declares: `trackDynamicDataAccessed`
|
||||
- Reads (eventual): `getPathname`, `StaticGenBailoutError`, `markPrerenderState`, `DynamicServerError`
|
||||
- Reads (eventual): `getPathname`, `StaticGenBailoutError`, `postponeWithTracking`, `DynamicServerError`
|
||||
- Write: `trackDynamicDataAccessed`
|
||||
|
||||
## Item 13: Stmt 8, `Normal`
|
||||
|
||||
```js
|
||||
export function ApiThatShallNotBeNamed({ reason, prerenderState, pathname }) {
|
||||
markPrerenderState(prerenderState, reason, pathname);
|
||||
export function Postpone({ reason, prerenderState, pathname }) {
|
||||
postponeWithTracking(prerenderState, reason, pathname);
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
- Hoisted
|
||||
- Declares: `ApiThatShallNotBeNamed`
|
||||
- Reads (eventual): `markPrerenderState`
|
||||
- Write: `ApiThatShallNotBeNamed`
|
||||
- Declares: `Postpone`
|
||||
- Reads (eventual): `postponeWithTracking`
|
||||
- Write: `Postpone`
|
||||
|
||||
## Item 14: Stmt 9, `Normal`
|
||||
|
||||
```js
|
||||
export function trackDynamicFetch(store, expression) {
|
||||
if (!store.prerenderState || store.isUnstableCacheCallback) return;
|
||||
markPrerenderState(store.prerenderState, expression, store.urlPathname);
|
||||
postponeWithTracking(store.prerenderState, expression, store.urlPathname);
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
- Hoisted
|
||||
- Declares: `trackDynamicFetch`
|
||||
- Reads (eventual): `markPrerenderState`
|
||||
- Reads (eventual): `postponeWithTracking`
|
||||
- Write: `trackDynamicFetch`
|
||||
|
||||
## Item 15: Stmt 10, `Normal`
|
||||
|
||||
```js
|
||||
function markPrerenderState(prerenderState, expression, pathname) {
|
||||
assertUnnamed();
|
||||
function postponeWithTracking(prerenderState, expression, pathname) {
|
||||
assertPostpone();
|
||||
const reason = `Route ${pathname} needs to bail out of prerendering at this point because it used ${expression}. ` + `React throws this special object to indicate where. It should not be caught by ` + `your own try/catch. Learn more: https://nextjs.org/docs/messages/ppr-caught-error`;
|
||||
prerenderState.dynamicAccesses.push({
|
||||
stack: prerenderState.isDebugSkeleton ? new Error().stack : undefined,
|
||||
expression
|
||||
});
|
||||
React.unstable_unnamed(reason);
|
||||
React.unstable_postpone(reason);
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
- Hoisted
|
||||
- Declares: `markPrerenderState`
|
||||
- Reads (eventual): `assertUnnamed`, `React`
|
||||
- Write: `markPrerenderState`
|
||||
- Declares: `postponeWithTracking`
|
||||
- Reads (eventual): `assertPostpone`, `React`
|
||||
- Write: `postponeWithTracking`
|
||||
- Write (eventual): `React`
|
||||
|
||||
## Item 16: Stmt 11, `Normal`
|
||||
@@ -261,27 +261,27 @@ export function formatDynamicAPIAccesses(prerenderState) {
|
||||
## Item 18: Stmt 13, `Normal`
|
||||
|
||||
```js
|
||||
function assertUnnamed() {
|
||||
if (!hasUnnamed) {
|
||||
throw new Error(`Invariant: React.unstable_unnamed is not defined. This suggests the wrong version of React was loaded. This is a bug in Next.js`);
|
||||
function assertPostpone() {
|
||||
if (!hasPostpone) {
|
||||
throw new Error(`Invariant: React.unstable_postpone is not defined. This suggests the wrong version of React was loaded. This is a bug in Next.js`);
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
- Hoisted
|
||||
- Declares: `assertUnnamed`
|
||||
- Reads (eventual): `hasUnnamed`
|
||||
- Write: `assertUnnamed`
|
||||
- Declares: `assertPostpone`
|
||||
- Reads (eventual): `hasPostpone`
|
||||
- Write: `assertPostpone`
|
||||
|
||||
## Item 19: Stmt 14, `Normal`
|
||||
|
||||
```js
|
||||
export function createUnnamedAbortSignal(reason) {
|
||||
assertUnnamed();
|
||||
export function createPostponedAbortSignal(reason) {
|
||||
assertPostpone();
|
||||
const controller = new AbortController();
|
||||
try {
|
||||
React.unstable_unnamed(reason);
|
||||
React.unstable_postpone(reason);
|
||||
} catch (x) {
|
||||
controller.abort(x);
|
||||
}
|
||||
@@ -291,9 +291,9 @@ export function createUnnamedAbortSignal(reason) {
|
||||
```
|
||||
|
||||
- Hoisted
|
||||
- Declares: `createUnnamedAbortSignal`
|
||||
- Reads (eventual): `assertUnnamed`, `React`
|
||||
- Write: `createUnnamedAbortSignal`
|
||||
- Declares: `createPostponedAbortSignal`
|
||||
- Reads (eventual): `assertPostpone`, `React`
|
||||
- Write: `createPostponedAbortSignal`
|
||||
- Write (eventual): `React`
|
||||
|
||||
# Phase 1
|
||||
@@ -325,7 +325,7 @@ graph TD
|
||||
Item22;
|
||||
Item22["export trackDynamicDataAccessed"];
|
||||
Item23;
|
||||
Item23["export ApiThatShallNotBeNamed"];
|
||||
Item23["export Postpone"];
|
||||
Item24;
|
||||
Item24["export trackDynamicFetch"];
|
||||
Item25;
|
||||
@@ -333,7 +333,7 @@ graph TD
|
||||
Item26;
|
||||
Item26["export formatDynamicAPIAccesses"];
|
||||
Item27;
|
||||
Item27["export createUnnamedAbortSignal"];
|
||||
Item27["export createPostponedAbortSignal"];
|
||||
Item2 --> Item1;
|
||||
Item3 --> Item2;
|
||||
Item4 --> Item3;
|
||||
@@ -367,7 +367,7 @@ graph TD
|
||||
Item22;
|
||||
Item22["export trackDynamicDataAccessed"];
|
||||
Item23;
|
||||
Item23["export ApiThatShallNotBeNamed"];
|
||||
Item23["export Postpone"];
|
||||
Item24;
|
||||
Item24["export trackDynamicFetch"];
|
||||
Item25;
|
||||
@@ -375,7 +375,7 @@ graph TD
|
||||
Item26;
|
||||
Item26["export formatDynamicAPIAccesses"];
|
||||
Item27;
|
||||
Item27["export createUnnamedAbortSignal"];
|
||||
Item27["export createPostponedAbortSignal"];
|
||||
Item2 --> Item1;
|
||||
Item3 --> Item2;
|
||||
Item4 --> Item3;
|
||||
@@ -424,7 +424,7 @@ graph TD
|
||||
Item22;
|
||||
Item22["export trackDynamicDataAccessed"];
|
||||
Item23;
|
||||
Item23["export ApiThatShallNotBeNamed"];
|
||||
Item23["export Postpone"];
|
||||
Item24;
|
||||
Item24["export trackDynamicFetch"];
|
||||
Item25;
|
||||
@@ -432,7 +432,7 @@ graph TD
|
||||
Item26;
|
||||
Item26["export formatDynamicAPIAccesses"];
|
||||
Item27;
|
||||
Item27["export createUnnamedAbortSignal"];
|
||||
Item27["export createPostponedAbortSignal"];
|
||||
Item2 --> Item1;
|
||||
Item3 --> Item2;
|
||||
Item4 --> Item3;
|
||||
@@ -498,7 +498,7 @@ graph TD
|
||||
Item22;
|
||||
Item22["export trackDynamicDataAccessed"];
|
||||
Item23;
|
||||
Item23["export ApiThatShallNotBeNamed"];
|
||||
Item23["export Postpone"];
|
||||
Item24;
|
||||
Item24["export trackDynamicFetch"];
|
||||
Item25;
|
||||
@@ -506,7 +506,7 @@ graph TD
|
||||
Item26;
|
||||
Item26["export formatDynamicAPIAccesses"];
|
||||
Item27;
|
||||
Item27["export createUnnamedAbortSignal"];
|
||||
Item27["export createPostponedAbortSignal"];
|
||||
Item2 --> Item1;
|
||||
Item3 --> Item2;
|
||||
Item4 --> Item3;
|
||||
@@ -558,11 +558,11 @@ graph TD
|
||||
N9["Items: [ItemId(5, Normal), ItemId(Export(("createPrerenderState", #2), "createPrerenderState"))]"];
|
||||
N10["Items: [ItemId(6, Normal), ItemId(Export(("markCurrentScopeAsDynamic", #2), "markCurrentScopeAsDynamic"))]"];
|
||||
N11["Items: [ItemId(7, Normal), ItemId(Export(("trackDynamicDataAccessed", #2), "trackDynamicDataAccessed"))]"];
|
||||
N12["Items: [ItemId(8, Normal), ItemId(Export(("ApiThatShallNotBeNamed", #2), "ApiThatShallNotBeNamed"))]"];
|
||||
N12["Items: [ItemId(8, Normal), ItemId(Export(("Postpone", #2), "Postpone"))]"];
|
||||
N13["Items: [ItemId(9, Normal), ItemId(Export(("trackDynamicFetch", #2), "trackDynamicFetch"))]"];
|
||||
N14["Items: [ItemId(11, Normal), ItemId(Export(("usedDynamicAPIs", #2), "usedDynamicAPIs"))]"];
|
||||
N15["Items: [ItemId(12, Normal), ItemId(Export(("formatDynamicAPIAccesses", #2), "formatDynamicAPIAccesses"))]"];
|
||||
N16["Items: [ItemId(14, Normal), ItemId(Export(("createUnnamedAbortSignal", #2), "createUnnamedAbortSignal"))]"];
|
||||
N16["Items: [ItemId(14, Normal), ItemId(Export(("createPostponedAbortSignal", #2), "createPostponedAbortSignal"))]"];
|
||||
N2 --> N0;
|
||||
N4 --> N2;
|
||||
N6 --> N4;
|
||||
@@ -594,14 +594,14 @@ graph TD
|
||||
{
|
||||
ModuleEvaluation: 8,
|
||||
Export(
|
||||
"ApiThatShallNotBeNamed",
|
||||
"Postpone",
|
||||
): 12,
|
||||
Export(
|
||||
"createPostponedAbortSignal",
|
||||
): 16,
|
||||
Export(
|
||||
"createPrerenderState",
|
||||
): 9,
|
||||
Export(
|
||||
"createUnnamedAbortSignal",
|
||||
): 16,
|
||||
Export(
|
||||
"formatDynamicAPIAccesses",
|
||||
): 15,
|
||||
@@ -698,28 +698,28 @@ import "__TURBOPACK_PART__" assert {
|
||||
import "__TURBOPACK_PART__" assert {
|
||||
__turbopack_part__: 3
|
||||
};
|
||||
const hasUnnamed = typeof React.unstable_unnamed === 'function';
|
||||
function markPrerenderState(prerenderState, expression, pathname) {
|
||||
assertUnnamed();
|
||||
const hasPostpone = typeof React.unstable_postpone === 'function';
|
||||
function postponeWithTracking(prerenderState, expression, pathname) {
|
||||
assertPostpone();
|
||||
const reason = `Route ${pathname} needs to bail out of prerendering at this point because it used ${expression}. ` + `React throws this special object to indicate where. It should not be caught by ` + `your own try/catch. Learn more: https://nextjs.org/docs/messages/ppr-caught-error`;
|
||||
prerenderState.dynamicAccesses.push({
|
||||
stack: prerenderState.isDebugSkeleton ? new Error().stack : undefined,
|
||||
expression
|
||||
});
|
||||
React.unstable_unnamed(reason);
|
||||
React.unstable_postpone(reason);
|
||||
}
|
||||
function assertUnnamed() {
|
||||
if (!hasUnnamed) {
|
||||
throw new Error(`Invariant: React.unstable_unnamed is not defined. This suggests the wrong version of React was loaded. This is a bug in Next.js`);
|
||||
function assertPostpone() {
|
||||
if (!hasPostpone) {
|
||||
throw new Error(`Invariant: React.unstable_postpone is not defined. This suggests the wrong version of React was loaded. This is a bug in Next.js`);
|
||||
}
|
||||
}
|
||||
export { hasUnnamed as a } from "__TURBOPACK_VAR__" assert {
|
||||
export { hasPostpone as a } from "__TURBOPACK_VAR__" assert {
|
||||
__turbopack_var__: true
|
||||
};
|
||||
export { markPrerenderState as b } from "__TURBOPACK_VAR__" assert {
|
||||
export { postponeWithTracking as b } from "__TURBOPACK_VAR__" assert {
|
||||
__turbopack_var__: true
|
||||
};
|
||||
export { assertUnnamed as c } from "__TURBOPACK_VAR__" assert {
|
||||
export { assertPostpone as c } from "__TURBOPACK_VAR__" assert {
|
||||
__turbopack_var__: true
|
||||
};
|
||||
export { };
|
||||
@@ -753,7 +753,7 @@ import "__TURBOPACK_PART__" assert {
|
||||
__turbopack_part__: 4
|
||||
};
|
||||
import { StaticGenBailoutError } from '../../client/components/static-generation-bailout';
|
||||
import { b as markPrerenderState } from "__TURBOPACK_PART__" assert {
|
||||
import { b as postponeWithTracking } from "__TURBOPACK_PART__" assert {
|
||||
__turbopack_part__: -8
|
||||
};
|
||||
function markCurrentScopeAsDynamic(store, expression) {
|
||||
@@ -763,7 +763,7 @@ function markCurrentScopeAsDynamic(store, expression) {
|
||||
} else if (store.dynamicShouldError) {
|
||||
throw new StaticGenBailoutError(`Route ${pathname} with \`dynamic = "error"\` couldn't be rendered statically because it used \`${expression}\`. See more info here: https://nextjs.org/docs/app/building-your-application/rendering/static-and-dynamic#dynamic-rendering`);
|
||||
} else if (store.prerenderState) {
|
||||
markPrerenderState(store.prerenderState, expression, pathname);
|
||||
postponeWithTracking(store.prerenderState, expression, pathname);
|
||||
} else {
|
||||
store.revalidate = 0;
|
||||
if (store.isStaticGeneration) {
|
||||
@@ -794,7 +794,7 @@ import "__TURBOPACK_PART__" assert {
|
||||
__turbopack_part__: 4
|
||||
};
|
||||
import { StaticGenBailoutError } from '../../client/components/static-generation-bailout';
|
||||
import { b as markPrerenderState } from "__TURBOPACK_PART__" assert {
|
||||
import { b as postponeWithTracking } from "__TURBOPACK_PART__" assert {
|
||||
__turbopack_part__: -8
|
||||
};
|
||||
function trackDynamicDataAccessed(store, expression) {
|
||||
@@ -804,7 +804,7 @@ function trackDynamicDataAccessed(store, expression) {
|
||||
} else if (store.dynamicShouldError) {
|
||||
throw new StaticGenBailoutError(`Route ${pathname} with \`dynamic = "error"\` couldn't be rendered statically because it used \`${expression}\`. See more info here: https://nextjs.org/docs/app/building-your-application/rendering/static-and-dynamic#dynamic-rendering`);
|
||||
} else if (store.prerenderState) {
|
||||
markPrerenderState(store.prerenderState, expression, pathname);
|
||||
postponeWithTracking(store.prerenderState, expression, pathname);
|
||||
} else {
|
||||
store.revalidate = 0;
|
||||
if (store.isStaticGeneration) {
|
||||
@@ -823,26 +823,26 @@ export { trackDynamicDataAccessed as f } from "__TURBOPACK_VAR__" assert {
|
||||
```
|
||||
## Part 12
|
||||
```js
|
||||
import { b as markPrerenderState } from "__TURBOPACK_PART__" assert {
|
||||
import { b as postponeWithTracking } from "__TURBOPACK_PART__" assert {
|
||||
__turbopack_part__: -8
|
||||
};
|
||||
function ApiThatShallNotBeNamed({ reason, prerenderState, pathname }) {
|
||||
markPrerenderState(prerenderState, reason, pathname);
|
||||
function Postpone({ reason, prerenderState, pathname }) {
|
||||
postponeWithTracking(prerenderState, reason, pathname);
|
||||
}
|
||||
export { ApiThatShallNotBeNamed };
|
||||
export { ApiThatShallNotBeNamed as g } from "__TURBOPACK_VAR__" assert {
|
||||
export { Postpone };
|
||||
export { Postpone as g } from "__TURBOPACK_VAR__" assert {
|
||||
__turbopack_var__: true
|
||||
};
|
||||
|
||||
```
|
||||
## Part 13
|
||||
```js
|
||||
import { b as markPrerenderState } from "__TURBOPACK_PART__" assert {
|
||||
import { b as postponeWithTracking } from "__TURBOPACK_PART__" assert {
|
||||
__turbopack_part__: -8
|
||||
};
|
||||
function trackDynamicFetch(store, expression) {
|
||||
if (!store.prerenderState || store.isUnstableCacheCallback) return;
|
||||
markPrerenderState(store.prerenderState, expression, store.urlPathname);
|
||||
postponeWithTracking(store.prerenderState, expression, store.urlPathname);
|
||||
}
|
||||
export { trackDynamicFetch };
|
||||
export { trackDynamicFetch as h } from "__TURBOPACK_VAR__" assert {
|
||||
@@ -892,21 +892,21 @@ import "__TURBOPACK_PART__" assert {
|
||||
__turbopack_part__: 0
|
||||
};
|
||||
import React from 'react';
|
||||
import { c as assertUnnamed } from "__TURBOPACK_PART__" assert {
|
||||
import { c as assertPostpone } from "__TURBOPACK_PART__" assert {
|
||||
__turbopack_part__: -8
|
||||
};
|
||||
function createUnnamedAbortSignal(reason) {
|
||||
assertUnnamed();
|
||||
function createPostponedAbortSignal(reason) {
|
||||
assertPostpone();
|
||||
const controller = new AbortController();
|
||||
try {
|
||||
React.unstable_unnamed(reason);
|
||||
React.unstable_postpone(reason);
|
||||
} catch (x) {
|
||||
controller.abort(x);
|
||||
}
|
||||
return controller.signal;
|
||||
}
|
||||
export { createUnnamedAbortSignal };
|
||||
export { createUnnamedAbortSignal as k } from "__TURBOPACK_VAR__" assert {
|
||||
export { createPostponedAbortSignal };
|
||||
export { createPostponedAbortSignal as k } from "__TURBOPACK_VAR__" assert {
|
||||
__turbopack_var__: true
|
||||
};
|
||||
|
||||
@@ -922,8 +922,8 @@ export { markCurrentScopeAsDynamic } from "__TURBOPACK_PART__" assert {
|
||||
export { trackDynamicDataAccessed } from "__TURBOPACK_PART__" assert {
|
||||
__turbopack_part__: "export trackDynamicDataAccessed"
|
||||
};
|
||||
export { ApiThatShallNotBeNamed } from "__TURBOPACK_PART__" assert {
|
||||
__turbopack_part__: "export ApiThatShallNotBeNamed"
|
||||
export { Postpone } from "__TURBOPACK_PART__" assert {
|
||||
__turbopack_part__: "export Postpone"
|
||||
};
|
||||
export { trackDynamicFetch } from "__TURBOPACK_PART__" assert {
|
||||
__turbopack_part__: "export trackDynamicFetch"
|
||||
@@ -934,8 +934,8 @@ export { usedDynamicAPIs } from "__TURBOPACK_PART__" assert {
|
||||
export { formatDynamicAPIAccesses } from "__TURBOPACK_PART__" assert {
|
||||
__turbopack_part__: "export formatDynamicAPIAccesses"
|
||||
};
|
||||
export { createUnnamedAbortSignal } from "__TURBOPACK_PART__" assert {
|
||||
__turbopack_part__: "export createUnnamedAbortSignal"
|
||||
export { createPostponedAbortSignal } from "__TURBOPACK_PART__" assert {
|
||||
__turbopack_part__: "export createPostponedAbortSignal"
|
||||
};
|
||||
|
||||
```
|
||||
@@ -957,28 +957,28 @@ import "__TURBOPACK_PART__" assert {
|
||||
import "__TURBOPACK_PART__" assert {
|
||||
__turbopack_part__: 3
|
||||
};
|
||||
const hasUnnamed = typeof React.unstable_unnamed === 'function';
|
||||
function markPrerenderState(prerenderState, expression, pathname) {
|
||||
assertUnnamed();
|
||||
const hasPostpone = typeof React.unstable_postpone === 'function';
|
||||
function postponeWithTracking(prerenderState, expression, pathname) {
|
||||
assertPostpone();
|
||||
const reason = `Route ${pathname} needs to bail out of prerendering at this point because it used ${expression}. ` + `React throws this special object to indicate where. It should not be caught by ` + `your own try/catch. Learn more: https://nextjs.org/docs/messages/ppr-caught-error`;
|
||||
prerenderState.dynamicAccesses.push({
|
||||
stack: prerenderState.isDebugSkeleton ? new Error().stack : undefined,
|
||||
expression
|
||||
});
|
||||
React.unstable_unnamed(reason);
|
||||
React.unstable_postpone(reason);
|
||||
}
|
||||
function assertUnnamed() {
|
||||
if (!hasUnnamed) {
|
||||
throw new Error(`Invariant: React.unstable_unnamed is not defined. This suggests the wrong version of React was loaded. This is a bug in Next.js`);
|
||||
function assertPostpone() {
|
||||
if (!hasPostpone) {
|
||||
throw new Error(`Invariant: React.unstable_postpone is not defined. This suggests the wrong version of React was loaded. This is a bug in Next.js`);
|
||||
}
|
||||
}
|
||||
export { hasUnnamed as a } from "__TURBOPACK_VAR__" assert {
|
||||
export { hasPostpone as a } from "__TURBOPACK_VAR__" assert {
|
||||
__turbopack_var__: true
|
||||
};
|
||||
export { markPrerenderState as b } from "__TURBOPACK_VAR__" assert {
|
||||
export { postponeWithTracking as b } from "__TURBOPACK_VAR__" assert {
|
||||
__turbopack_var__: true
|
||||
};
|
||||
export { assertUnnamed as c } from "__TURBOPACK_VAR__" assert {
|
||||
export { assertPostpone as c } from "__TURBOPACK_VAR__" assert {
|
||||
__turbopack_var__: true
|
||||
};
|
||||
export { };
|
||||
@@ -990,14 +990,14 @@ export { };
|
||||
{
|
||||
ModuleEvaluation: 8,
|
||||
Export(
|
||||
"ApiThatShallNotBeNamed",
|
||||
"Postpone",
|
||||
): 12,
|
||||
Export(
|
||||
"createPostponedAbortSignal",
|
||||
): 18,
|
||||
Export(
|
||||
"createPrerenderState",
|
||||
): 9,
|
||||
Export(
|
||||
"createUnnamedAbortSignal",
|
||||
): 18,
|
||||
Export(
|
||||
"formatDynamicAPIAccesses",
|
||||
): 16,
|
||||
@@ -1085,8 +1085,8 @@ import React from 'react';
|
||||
import "__TURBOPACK_PART__" assert {
|
||||
__turbopack_part__: 6
|
||||
};
|
||||
const hasUnnamed = typeof React.unstable_unnamed === 'function';
|
||||
export { hasUnnamed as a } from "__TURBOPACK_VAR__" assert {
|
||||
const hasPostpone = typeof React.unstable_postpone === 'function';
|
||||
export { hasPostpone as a } from "__TURBOPACK_VAR__" assert {
|
||||
__turbopack_var__: true
|
||||
};
|
||||
export { };
|
||||
@@ -1120,7 +1120,7 @@ import "__TURBOPACK_PART__" assert {
|
||||
__turbopack_part__: 4
|
||||
};
|
||||
import { StaticGenBailoutError } from '../../client/components/static-generation-bailout';
|
||||
import { c as markPrerenderState } from "__TURBOPACK_PART__" assert {
|
||||
import { c as postponeWithTracking } from "__TURBOPACK_PART__" assert {
|
||||
__turbopack_part__: -14
|
||||
};
|
||||
function markCurrentScopeAsDynamic(store, expression) {
|
||||
@@ -1130,7 +1130,7 @@ function markCurrentScopeAsDynamic(store, expression) {
|
||||
} else if (store.dynamicShouldError) {
|
||||
throw new StaticGenBailoutError(`Route ${pathname} with \`dynamic = "error"\` couldn't be rendered statically because it used \`${expression}\`. See more info here: https://nextjs.org/docs/app/building-your-application/rendering/static-and-dynamic#dynamic-rendering`);
|
||||
} else if (store.prerenderState) {
|
||||
markPrerenderState(store.prerenderState, expression, pathname);
|
||||
postponeWithTracking(store.prerenderState, expression, pathname);
|
||||
} else {
|
||||
store.revalidate = 0;
|
||||
if (store.isStaticGeneration) {
|
||||
@@ -1161,7 +1161,7 @@ import "__TURBOPACK_PART__" assert {
|
||||
__turbopack_part__: 4
|
||||
};
|
||||
import { StaticGenBailoutError } from '../../client/components/static-generation-bailout';
|
||||
import { c as markPrerenderState } from "__TURBOPACK_PART__" assert {
|
||||
import { c as postponeWithTracking } from "__TURBOPACK_PART__" assert {
|
||||
__turbopack_part__: -14
|
||||
};
|
||||
function trackDynamicDataAccessed(store, expression) {
|
||||
@@ -1171,7 +1171,7 @@ function trackDynamicDataAccessed(store, expression) {
|
||||
} else if (store.dynamicShouldError) {
|
||||
throw new StaticGenBailoutError(`Route ${pathname} with \`dynamic = "error"\` couldn't be rendered statically because it used \`${expression}\`. See more info here: https://nextjs.org/docs/app/building-your-application/rendering/static-and-dynamic#dynamic-rendering`);
|
||||
} else if (store.prerenderState) {
|
||||
markPrerenderState(store.prerenderState, expression, pathname);
|
||||
postponeWithTracking(store.prerenderState, expression, pathname);
|
||||
} else {
|
||||
store.revalidate = 0;
|
||||
if (store.isStaticGeneration) {
|
||||
@@ -1190,26 +1190,26 @@ export { trackDynamicDataAccessed as e } from "__TURBOPACK_VAR__" assert {
|
||||
```
|
||||
## Part 12
|
||||
```js
|
||||
import { c as markPrerenderState } from "__TURBOPACK_PART__" assert {
|
||||
import { c as postponeWithTracking } from "__TURBOPACK_PART__" assert {
|
||||
__turbopack_part__: -14
|
||||
};
|
||||
function ApiThatShallNotBeNamed({ reason, prerenderState, pathname }) {
|
||||
markPrerenderState(prerenderState, reason, pathname);
|
||||
function Postpone({ reason, prerenderState, pathname }) {
|
||||
postponeWithTracking(prerenderState, reason, pathname);
|
||||
}
|
||||
export { ApiThatShallNotBeNamed };
|
||||
export { ApiThatShallNotBeNamed as f } from "__TURBOPACK_VAR__" assert {
|
||||
export { Postpone };
|
||||
export { Postpone as f } from "__TURBOPACK_VAR__" assert {
|
||||
__turbopack_var__: true
|
||||
};
|
||||
|
||||
```
|
||||
## Part 13
|
||||
```js
|
||||
import { c as markPrerenderState } from "__TURBOPACK_PART__" assert {
|
||||
import { c as postponeWithTracking } from "__TURBOPACK_PART__" assert {
|
||||
__turbopack_part__: -14
|
||||
};
|
||||
function trackDynamicFetch(store, expression) {
|
||||
if (!store.prerenderState || store.isUnstableCacheCallback) return;
|
||||
markPrerenderState(store.prerenderState, expression, store.urlPathname);
|
||||
postponeWithTracking(store.prerenderState, expression, store.urlPathname);
|
||||
}
|
||||
export { trackDynamicFetch };
|
||||
export { trackDynamicFetch as g } from "__TURBOPACK_VAR__" assert {
|
||||
@@ -1223,22 +1223,22 @@ import "__TURBOPACK_PART__" assert {
|
||||
__turbopack_part__: 0
|
||||
};
|
||||
import React from 'react';
|
||||
import { h as assertUnnamed } from "__TURBOPACK_PART__" assert {
|
||||
import { h as assertPostpone } from "__TURBOPACK_PART__" assert {
|
||||
__turbopack_part__: -17
|
||||
};
|
||||
import "__TURBOPACK_PART__" assert {
|
||||
__turbopack_part__: 8
|
||||
};
|
||||
function markPrerenderState(prerenderState, expression, pathname) {
|
||||
assertUnnamed();
|
||||
function postponeWithTracking(prerenderState, expression, pathname) {
|
||||
assertPostpone();
|
||||
const reason = `Route ${pathname} needs to bail out of prerendering at this point because it used ${expression}. ` + `React throws this special object to indicate where. It should not be caught by ` + `your own try/catch. Learn more: https://nextjs.org/docs/messages/ppr-caught-error`;
|
||||
prerenderState.dynamicAccesses.push({
|
||||
stack: prerenderState.isDebugSkeleton ? new Error().stack : undefined,
|
||||
expression
|
||||
});
|
||||
React.unstable_unnamed(reason);
|
||||
React.unstable_postpone(reason);
|
||||
}
|
||||
export { markPrerenderState as c } from "__TURBOPACK_VAR__" assert {
|
||||
export { postponeWithTracking as c } from "__TURBOPACK_VAR__" assert {
|
||||
__turbopack_var__: true
|
||||
};
|
||||
|
||||
@@ -1281,15 +1281,15 @@ export { formatDynamicAPIAccesses as j } from "__TURBOPACK_VAR__" assert {
|
||||
```
|
||||
## Part 17
|
||||
```js
|
||||
import { a as hasUnnamed } from "__TURBOPACK_PART__" assert {
|
||||
import { a as hasPostpone } from "__TURBOPACK_PART__" assert {
|
||||
__turbopack_part__: -8
|
||||
};
|
||||
function assertUnnamed() {
|
||||
if (!hasUnnamed) {
|
||||
throw new Error(`Invariant: React.unstable_unnamed is not defined. This suggests the wrong version of React was loaded. This is a bug in Next.js`);
|
||||
function assertPostpone() {
|
||||
if (!hasPostpone) {
|
||||
throw new Error(`Invariant: React.unstable_postpone is not defined. This suggests the wrong version of React was loaded. This is a bug in Next.js`);
|
||||
}
|
||||
}
|
||||
export { assertUnnamed as h } from "__TURBOPACK_VAR__" assert {
|
||||
export { assertPostpone as h } from "__TURBOPACK_VAR__" assert {
|
||||
__turbopack_var__: true
|
||||
};
|
||||
|
||||
@@ -1300,24 +1300,24 @@ import "__TURBOPACK_PART__" assert {
|
||||
__turbopack_part__: 0
|
||||
};
|
||||
import React from 'react';
|
||||
import { h as assertUnnamed } from "__TURBOPACK_PART__" assert {
|
||||
import { h as assertPostpone } from "__TURBOPACK_PART__" assert {
|
||||
__turbopack_part__: -17
|
||||
};
|
||||
import "__TURBOPACK_PART__" assert {
|
||||
__turbopack_part__: 8
|
||||
};
|
||||
function createUnnamedAbortSignal(reason) {
|
||||
assertUnnamed();
|
||||
function createPostponedAbortSignal(reason) {
|
||||
assertPostpone();
|
||||
const controller = new AbortController();
|
||||
try {
|
||||
React.unstable_unnamed(reason);
|
||||
React.unstable_postpone(reason);
|
||||
} catch (x) {
|
||||
controller.abort(x);
|
||||
}
|
||||
return controller.signal;
|
||||
}
|
||||
export { createUnnamedAbortSignal };
|
||||
export { createUnnamedAbortSignal as k } from "__TURBOPACK_VAR__" assert {
|
||||
export { createPostponedAbortSignal };
|
||||
export { createPostponedAbortSignal as k } from "__TURBOPACK_VAR__" assert {
|
||||
__turbopack_var__: true
|
||||
};
|
||||
|
||||
@@ -1333,8 +1333,8 @@ export { markCurrentScopeAsDynamic } from "__TURBOPACK_PART__" assert {
|
||||
export { trackDynamicDataAccessed } from "__TURBOPACK_PART__" assert {
|
||||
__turbopack_part__: "export trackDynamicDataAccessed"
|
||||
};
|
||||
export { ApiThatShallNotBeNamed } from "__TURBOPACK_PART__" assert {
|
||||
__turbopack_part__: "export ApiThatShallNotBeNamed"
|
||||
export { Postpone } from "__TURBOPACK_PART__" assert {
|
||||
__turbopack_part__: "export Postpone"
|
||||
};
|
||||
export { trackDynamicFetch } from "__TURBOPACK_PART__" assert {
|
||||
__turbopack_part__: "export trackDynamicFetch"
|
||||
@@ -1345,8 +1345,8 @@ export { usedDynamicAPIs } from "__TURBOPACK_PART__" assert {
|
||||
export { formatDynamicAPIAccesses } from "__TURBOPACK_PART__" assert {
|
||||
__turbopack_part__: "export formatDynamicAPIAccesses"
|
||||
};
|
||||
export { createUnnamedAbortSignal } from "__TURBOPACK_PART__" assert {
|
||||
__turbopack_part__: "export createUnnamedAbortSignal"
|
||||
export { createPostponedAbortSignal } from "__TURBOPACK_PART__" assert {
|
||||
__turbopack_part__: "export createPostponedAbortSignal"
|
||||
};
|
||||
|
||||
```
|
||||
@@ -1359,8 +1359,8 @@ import React from 'react';
|
||||
import "__TURBOPACK_PART__" assert {
|
||||
__turbopack_part__: 6
|
||||
};
|
||||
const hasUnnamed = typeof React.unstable_unnamed === 'function';
|
||||
export { hasUnnamed as a } from "__TURBOPACK_VAR__" assert {
|
||||
const hasPostpone = typeof React.unstable_postpone === 'function';
|
||||
export { hasPostpone as a } from "__TURBOPACK_VAR__" assert {
|
||||
__turbopack_var__: true
|
||||
};
|
||||
export { };
|
||||
|
||||
Reference in New Issue
Block a user