| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| |
|
| | import type { WorkStore } from '../app-render/work-async-storage.external' |
| | import type { |
| | WorkUnitStore, |
| | PrerenderStoreLegacy, |
| | PrerenderStoreModern, |
| | PrerenderStoreModernRuntime, |
| | } from '../app-render/work-unit-async-storage.external' |
| |
|
| | |
| | import React from 'react' |
| |
|
| | import { DynamicServerError } from '../../client/components/hooks-server-context' |
| | import { StaticGenBailoutError } from '../../client/components/static-generation-bailout' |
| | import { |
| | getRuntimeStagePromise, |
| | throwForMissingRequestStore, |
| | workUnitAsyncStorage, |
| | } from './work-unit-async-storage.external' |
| | import { workAsyncStorage } from '../app-render/work-async-storage.external' |
| | import { makeHangingPromise } from '../dynamic-rendering-utils' |
| | import { |
| | METADATA_BOUNDARY_NAME, |
| | VIEWPORT_BOUNDARY_NAME, |
| | OUTLET_BOUNDARY_NAME, |
| | ROOT_LAYOUT_BOUNDARY_NAME, |
| | } from '../../lib/framework/boundary-constants' |
| | import { scheduleOnNextTick } from '../../lib/scheduler' |
| | import { BailoutToCSRError } from '../../shared/lib/lazy-dynamic/bailout-to-csr' |
| | import { InvariantError } from '../../shared/lib/invariant-error' |
| |
|
| | const hasPostpone = typeof React.unstable_postpone === 'function' |
| |
|
| | export type DynamicAccess = { |
| | |
| | |
| | |
| | |
| | |
| | stack?: string |
| |
|
| | |
| | |
| | |
| | expression: string |
| | } |
| |
|
| | |
| | export type DynamicTrackingState = { |
| | |
| | |
| | |
| | readonly isDebugDynamicAccesses: boolean | undefined |
| |
|
| | |
| | |
| | |
| | readonly dynamicAccesses: Array<DynamicAccess> |
| |
|
| | syncDynamicErrorWithStack: null | Error |
| | } |
| |
|
| | |
| | export type DynamicValidationState = { |
| | hasSuspenseAboveBody: boolean |
| | hasDynamicMetadata: boolean |
| | dynamicMetadata: null | Error |
| | hasDynamicViewport: boolean |
| | hasAllowedDynamic: boolean |
| | dynamicErrors: Array<Error> |
| | } |
| |
|
| | export function createDynamicTrackingState( |
| | isDebugDynamicAccesses: boolean | undefined |
| | ): DynamicTrackingState { |
| | return { |
| | isDebugDynamicAccesses, |
| | dynamicAccesses: [], |
| | syncDynamicErrorWithStack: null, |
| | } |
| | } |
| |
|
| | export function createDynamicValidationState(): DynamicValidationState { |
| | return { |
| | hasSuspenseAboveBody: false, |
| | hasDynamicMetadata: false, |
| | dynamicMetadata: null, |
| | hasDynamicViewport: false, |
| | hasAllowedDynamic: false, |
| | dynamicErrors: [], |
| | } |
| | } |
| |
|
| | export function getFirstDynamicReason( |
| | trackingState: DynamicTrackingState |
| | ): undefined | string { |
| | return trackingState.dynamicAccesses[0]?.expression |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | export function markCurrentScopeAsDynamic( |
| | store: WorkStore, |
| | workUnitStore: undefined | Exclude<WorkUnitStore, PrerenderStoreModern>, |
| | expression: string |
| | ): void { |
| | if (workUnitStore) { |
| | switch (workUnitStore.type) { |
| | case 'cache': |
| | case 'unstable-cache': |
| | |
| | |
| | |
| | |
| | return |
| | case 'private-cache': |
| | |
| | return |
| | case 'prerender-legacy': |
| | case 'prerender-ppr': |
| | case 'request': |
| | break |
| | default: |
| | workUnitStore satisfies never |
| | } |
| | } |
| |
|
| | |
| | |
| | |
| | if (store.forceDynamic || store.forceStatic) return |
| |
|
| | if (store.dynamicShouldError) { |
| | throw new StaticGenBailoutError( |
| | `Route ${store.route} 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` |
| | ) |
| | } |
| |
|
| | if (workUnitStore) { |
| | switch (workUnitStore.type) { |
| | case 'prerender-ppr': |
| | return postponeWithTracking( |
| | store.route, |
| | expression, |
| | workUnitStore.dynamicTracking |
| | ) |
| | case 'prerender-legacy': |
| | workUnitStore.revalidate = 0 |
| |
|
| | |
| | |
| | const err = new DynamicServerError( |
| | `Route ${store.route} couldn't be rendered statically because it used ${expression}. See more info here: https://nextjs.org/docs/messages/dynamic-server-error` |
| | ) |
| | store.dynamicUsageDescription = expression |
| | store.dynamicUsageStack = err.stack |
| |
|
| | throw err |
| | case 'request': |
| | if (process.env.NODE_ENV !== 'production') { |
| | workUnitStore.usedDynamic = true |
| | } |
| | break |
| | default: |
| | workUnitStore satisfies never |
| | } |
| | } |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | export function throwToInterruptStaticGeneration( |
| | expression: string, |
| | store: WorkStore, |
| | prerenderStore: PrerenderStoreLegacy |
| | ): never { |
| | |
| | const err = new DynamicServerError( |
| | `Route ${store.route} couldn't be rendered statically because it used \`${expression}\`. See more info here: https://nextjs.org/docs/messages/dynamic-server-error` |
| | ) |
| |
|
| | prerenderStore.revalidate = 0 |
| |
|
| | store.dynamicUsageDescription = expression |
| | store.dynamicUsageStack = err.stack |
| |
|
| | throw err |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | export function trackDynamicDataInDynamicRender(workUnitStore: WorkUnitStore) { |
| | switch (workUnitStore.type) { |
| | case 'cache': |
| | case 'unstable-cache': |
| | |
| | |
| | |
| | |
| | return |
| | case 'private-cache': |
| | |
| | return |
| | case 'prerender': |
| | case 'prerender-runtime': |
| | case 'prerender-legacy': |
| | case 'prerender-ppr': |
| | case 'prerender-client': |
| | break |
| | case 'request': |
| | if (process.env.NODE_ENV !== 'production') { |
| | workUnitStore.usedDynamic = true |
| | } |
| | break |
| | default: |
| | workUnitStore satisfies never |
| | } |
| | } |
| |
|
| | function abortOnSynchronousDynamicDataAccess( |
| | route: string, |
| | expression: string, |
| | prerenderStore: PrerenderStoreModern |
| | ): void { |
| | const reason = `Route ${route} needs to bail out of prerendering at this point because it used ${expression}.` |
| |
|
| | const error = createPrerenderInterruptedError(reason) |
| |
|
| | prerenderStore.controller.abort(error) |
| |
|
| | const dynamicTracking = prerenderStore.dynamicTracking |
| | if (dynamicTracking) { |
| | dynamicTracking.dynamicAccesses.push({ |
| | |
| | |
| | stack: dynamicTracking.isDebugDynamicAccesses |
| | ? new Error().stack |
| | : undefined, |
| | expression, |
| | }) |
| | } |
| | } |
| |
|
| | export function abortOnSynchronousPlatformIOAccess( |
| | route: string, |
| | expression: string, |
| | errorWithStack: Error, |
| | prerenderStore: PrerenderStoreModern |
| | ): void { |
| | const dynamicTracking = prerenderStore.dynamicTracking |
| | abortOnSynchronousDynamicDataAccess(route, expression, prerenderStore) |
| | |
| | |
| | |
| | |
| | if (dynamicTracking) { |
| | if (dynamicTracking.syncDynamicErrorWithStack === null) { |
| | dynamicTracking.syncDynamicErrorWithStack = errorWithStack |
| | } |
| | } |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | export function abortAndThrowOnSynchronousRequestDataAccess( |
| | route: string, |
| | expression: string, |
| | errorWithStack: Error, |
| | prerenderStore: PrerenderStoreModern |
| | ): never { |
| | const prerenderSignal = prerenderStore.controller.signal |
| | if (prerenderSignal.aborted === false) { |
| | |
| | |
| | |
| | |
| | |
| | abortOnSynchronousDynamicDataAccess(route, expression, prerenderStore) |
| | |
| | |
| | |
| | |
| | const dynamicTracking = prerenderStore.dynamicTracking |
| | if (dynamicTracking) { |
| | if (dynamicTracking.syncDynamicErrorWithStack === null) { |
| | dynamicTracking.syncDynamicErrorWithStack = errorWithStack |
| | } |
| | } |
| | } |
| | throw createPrerenderInterruptedError( |
| | `Route ${route} needs to bail out of prerendering at this point because it used ${expression}.` |
| | ) |
| | } |
| |
|
| | |
| | |
| | |
| | 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({ |
| | |
| | |
| | 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 { |
| | const error = new Error(message) |
| | ;(error as any).digest = NEXT_PRERENDER_INTERRUPTED |
| | return error |
| | } |
| |
|
| | type DigestError = Error & { |
| | digest: string |
| | } |
| |
|
| | export function isPrerenderInterruptedError( |
| | error: unknown |
| | ): error is DigestError { |
| | return ( |
| | typeof error === 'object' && |
| | error !== null && |
| | (error as any).digest === NEXT_PRERENDER_INTERRUPTED && |
| | 'name' in error && |
| | 'message' in error && |
| | error instanceof Error |
| | ) |
| | } |
| |
|
| | export function accessedDynamicData( |
| | dynamicAccesses: Array<DynamicAccess> |
| | ): boolean { |
| | return dynamicAccesses.length > 0 |
| | } |
| |
|
| | export function consumeDynamicAccess( |
| | serverDynamic: DynamicTrackingState, |
| | clientDynamic: DynamicTrackingState |
| | ): DynamicTrackingState['dynamicAccesses'] { |
| | |
| | |
| | |
| | serverDynamic.dynamicAccesses.push(...clientDynamic.dynamicAccesses) |
| | return serverDynamic.dynamicAccesses |
| | } |
| |
|
| | export function formatDynamicAPIAccesses( |
| | dynamicAccesses: Array<DynamicAccess> |
| | ): string[] { |
| | return dynamicAccesses |
| | .filter( |
| | (access): access is Required<DynamicAccess> => |
| | typeof access.stack === 'string' && access.stack.length > 0 |
| | ) |
| | .map(({ expression, stack }) => { |
| | stack = stack |
| | .split('\n') |
| | |
| | |
| | |
| | .slice(4) |
| | .filter((line) => { |
| | |
| | if (line.includes('node_modules/next/')) { |
| | return false |
| | } |
| |
|
| | |
| | if (line.includes(' (<anonymous>)')) { |
| | return false |
| | } |
| |
|
| | |
| | if (line.includes(' (node:')) { |
| | return false |
| | } |
| |
|
| | return true |
| | }) |
| | .join('\n') |
| | return `Dynamic API Usage Debug - ${expression}:\n${stack}` |
| | }) |
| | } |
| |
|
| | 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 createRenderInBrowserAbortSignal(): AbortSignal { |
| | const controller = new AbortController() |
| | controller.abort(new BailoutToCSRError('Render in Browser')) |
| | return controller.signal |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | |
| | export function createHangingInputAbortSignal( |
| | workUnitStore: WorkUnitStore |
| | ): AbortSignal | undefined { |
| | switch (workUnitStore.type) { |
| | case 'prerender': |
| | case 'prerender-runtime': |
| | const controller = new AbortController() |
| |
|
| | if (workUnitStore.cacheSignal) { |
| | |
| | |
| | |
| | workUnitStore.cacheSignal.inputReady().then(() => { |
| | controller.abort() |
| | }) |
| | } else { |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | const runtimeStagePromise = getRuntimeStagePromise(workUnitStore) |
| | if (runtimeStagePromise) { |
| | runtimeStagePromise.then(() => |
| | scheduleOnNextTick(() => controller.abort()) |
| | ) |
| | } else { |
| | scheduleOnNextTick(() => controller.abort()) |
| | } |
| | } |
| |
|
| | return controller.signal |
| | case 'prerender-client': |
| | case 'prerender-ppr': |
| | case 'prerender-legacy': |
| | case 'request': |
| | case 'cache': |
| | case 'private-cache': |
| | case 'unstable-cache': |
| | return undefined |
| | default: |
| | workUnitStore satisfies never |
| | } |
| | } |
| |
|
| | export function annotateDynamicAccess( |
| | expression: string, |
| | prerenderStore: PrerenderStoreModern |
| | ) { |
| | const dynamicTracking = prerenderStore.dynamicTracking |
| | if (dynamicTracking) { |
| | dynamicTracking.dynamicAccesses.push({ |
| | stack: dynamicTracking.isDebugDynamicAccesses |
| | ? new Error().stack |
| | : undefined, |
| | expression, |
| | }) |
| | } |
| | } |
| |
|
| | export function useDynamicRouteParams(expression: string) { |
| | const workStore = workAsyncStorage.getStore() |
| | const workUnitStore = workUnitAsyncStorage.getStore() |
| | if (workStore && workUnitStore) { |
| | switch (workUnitStore.type) { |
| | case 'prerender-client': |
| | case 'prerender': { |
| | const fallbackParams = workUnitStore.fallbackRouteParams |
| |
|
| | if (fallbackParams && fallbackParams.size > 0) { |
| | |
| | |
| | |
| | React.use( |
| | makeHangingPromise( |
| | workUnitStore.renderSignal, |
| | workStore.route, |
| | expression |
| | ) |
| | ) |
| | } |
| | break |
| | } |
| | case 'prerender-ppr': { |
| | const fallbackParams = workUnitStore.fallbackRouteParams |
| | if (fallbackParams && fallbackParams.size > 0) { |
| | return postponeWithTracking( |
| | workStore.route, |
| | expression, |
| | workUnitStore.dynamicTracking |
| | ) |
| | } |
| | break |
| | } |
| | case 'prerender-runtime': |
| | throw new InvariantError( |
| | `\`${expression}\` was called during a runtime prerender. Next.js should be preventing ${expression} from being included in server components statically, but did not in this case.` |
| | ) |
| | case 'cache': |
| | case 'private-cache': |
| | throw new InvariantError( |
| | `\`${expression}\` was called inside a cache scope. Next.js should be preventing ${expression} from being included in server components statically, but did not in this case.` |
| | ) |
| | case 'prerender-legacy': |
| | case 'request': |
| | case 'unstable-cache': |
| | break |
| | default: |
| | workUnitStore satisfies never |
| | } |
| | } |
| | } |
| |
|
| | export function useDynamicSearchParams(expression: string) { |
| | const workStore = workAsyncStorage.getStore() |
| | const workUnitStore = workUnitAsyncStorage.getStore() |
| |
|
| | if (!workStore) { |
| | |
| | return |
| | } |
| |
|
| | if (!workUnitStore) { |
| | throwForMissingRequestStore(expression) |
| | } |
| |
|
| | switch (workUnitStore.type) { |
| | case 'prerender-client': { |
| | React.use( |
| | makeHangingPromise( |
| | workUnitStore.renderSignal, |
| | workStore.route, |
| | expression |
| | ) |
| | ) |
| | break |
| | } |
| | case 'prerender-legacy': |
| | case 'prerender-ppr': { |
| | if (workStore.forceStatic) { |
| | return |
| | } |
| | throw new BailoutToCSRError(expression) |
| | } |
| | case 'prerender': |
| | case 'prerender-runtime': |
| | 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 'cache': |
| | case 'unstable-cache': |
| | case 'private-cache': |
| | throw new InvariantError( |
| | `\`${expression}\` was called inside a cache scope. Next.js should be preventing ${expression} from being included in server components statically, but did not in this case.` |
| | ) |
| | case 'request': |
| | return |
| | default: |
| | workUnitStore satisfies never |
| | } |
| | } |
| |
|
| | const hasSuspenseRegex = /\n\s+at Suspense \(<anonymous>\)/ |
| |
|
| | |
| | const bodyAndImplicitTags = |
| | 'body|div|main|section|article|aside|header|footer|nav|form|p|span|h1|h2|h3|h4|h5|h6' |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | const hasSuspenseBeforeRootLayoutWithoutBodyOrImplicitBodyRegex = new RegExp( |
| | `\\n\\s+at Suspense \\(<anonymous>\\)(?:(?!\\n\\s+at (?:${bodyAndImplicitTags}) \\(<anonymous>\\))[\\s\\S])*?\\n\\s+at ${ROOT_LAYOUT_BOUNDARY_NAME} \\([^\\n]*\\)` |
| | ) |
| |
|
| | const hasMetadataRegex = new RegExp( |
| | `\\n\\s+at ${METADATA_BOUNDARY_NAME}[\\n\\s]` |
| | ) |
| | const hasViewportRegex = new RegExp( |
| | `\\n\\s+at ${VIEWPORT_BOUNDARY_NAME}[\\n\\s]` |
| | ) |
| | const hasOutletRegex = new RegExp(`\\n\\s+at ${OUTLET_BOUNDARY_NAME}[\\n\\s]`) |
| |
|
| | export function trackAllowedDynamicAccess( |
| | workStore: WorkStore, |
| | componentStack: string, |
| | dynamicValidation: DynamicValidationState, |
| | clientDynamic: DynamicTrackingState |
| | ) { |
| | if (hasOutletRegex.test(componentStack)) { |
| | |
| | return |
| | } else if (hasMetadataRegex.test(componentStack)) { |
| | dynamicValidation.hasDynamicMetadata = true |
| | return |
| | } else if (hasViewportRegex.test(componentStack)) { |
| | dynamicValidation.hasDynamicViewport = true |
| | return |
| | } else if ( |
| | hasSuspenseBeforeRootLayoutWithoutBodyOrImplicitBodyRegex.test( |
| | componentStack |
| | ) |
| | ) { |
| | |
| | |
| | |
| | dynamicValidation.hasAllowedDynamic = true |
| | dynamicValidation.hasSuspenseAboveBody = true |
| | return |
| | } else if (hasSuspenseRegex.test(componentStack)) { |
| | |
| | |
| | dynamicValidation.hasAllowedDynamic = true |
| | return |
| | } else if (clientDynamic.syncDynamicErrorWithStack) { |
| | |
| | dynamicValidation.dynamicErrors.push( |
| | clientDynamic.syncDynamicErrorWithStack |
| | ) |
| | return |
| | } else { |
| | const message = |
| | `Route "${workStore.route}": Uncached data was accessed outside of ` + |
| | '<Suspense>. This delays the entire page from rendering, resulting in a ' + |
| | 'slow user experience. Learn more: ' + |
| | 'https://nextjs.org/docs/messages/blocking-route' |
| | const error = createErrorWithComponentOrOwnerStack(message, componentStack) |
| | dynamicValidation.dynamicErrors.push(error) |
| | return |
| | } |
| | } |
| |
|
| | export function trackDynamicHoleInRuntimeShell( |
| | workStore: WorkStore, |
| | componentStack: string, |
| | dynamicValidation: DynamicValidationState, |
| | clientDynamic: DynamicTrackingState |
| | ) { |
| | if (hasOutletRegex.test(componentStack)) { |
| | |
| | return |
| | } else if (hasMetadataRegex.test(componentStack)) { |
| | const message = `Route "${workStore.route}": Uncached data or \`connection()\` was accessed inside \`generateMetadata\`. Except for this instance, the page would have been entirely prerenderable which may have been the intended behavior. See more info here: https://nextjs.org/docs/messages/next-prerender-dynamic-metadata` |
| | const error = createErrorWithComponentOrOwnerStack(message, componentStack) |
| | dynamicValidation.dynamicMetadata = error |
| | return |
| | } else if (hasViewportRegex.test(componentStack)) { |
| | const message = `Route "${workStore.route}": Uncached data or \`connection()\` was accessed inside \`generateViewport\`. This delays the entire page from rendering, resulting in a slow user experience. Learn more: https://nextjs.org/docs/messages/next-prerender-dynamic-viewport` |
| | const error = createErrorWithComponentOrOwnerStack(message, componentStack) |
| | dynamicValidation.dynamicErrors.push(error) |
| | return |
| | } else if ( |
| | hasSuspenseBeforeRootLayoutWithoutBodyOrImplicitBodyRegex.test( |
| | componentStack |
| | ) |
| | ) { |
| | |
| | |
| | |
| | dynamicValidation.hasAllowedDynamic = true |
| | dynamicValidation.hasSuspenseAboveBody = true |
| | return |
| | } else if (hasSuspenseRegex.test(componentStack)) { |
| | |
| | |
| | dynamicValidation.hasAllowedDynamic = true |
| | return |
| | } else if (clientDynamic.syncDynamicErrorWithStack) { |
| | |
| | dynamicValidation.dynamicErrors.push( |
| | clientDynamic.syncDynamicErrorWithStack |
| | ) |
| | return |
| | } else { |
| | const message = `Route "${workStore.route}": Uncached data or \`connection()\` was accessed outside of \`<Suspense>\`. This delays the entire page from rendering, resulting in a slow user experience. Learn more: https://nextjs.org/docs/messages/blocking-route` |
| | const error = createErrorWithComponentOrOwnerStack(message, componentStack) |
| | dynamicValidation.dynamicErrors.push(error) |
| | return |
| | } |
| | } |
| |
|
| | export function trackDynamicHoleInStaticShell( |
| | workStore: WorkStore, |
| | componentStack: string, |
| | dynamicValidation: DynamicValidationState, |
| | clientDynamic: DynamicTrackingState |
| | ) { |
| | if (hasOutletRegex.test(componentStack)) { |
| | |
| | return |
| | } else if (hasMetadataRegex.test(componentStack)) { |
| | const message = `Route "${workStore.route}": Runtime data such as \`cookies()\`, \`headers()\`, \`params\`, or \`searchParams\` was accessed inside \`generateMetadata\` or you have file-based metadata such as icons that depend on dynamic params segments. Except for this instance, the page would have been entirely prerenderable which may have been the intended behavior. See more info here: https://nextjs.org/docs/messages/next-prerender-dynamic-metadata` |
| | const error = createErrorWithComponentOrOwnerStack(message, componentStack) |
| | dynamicValidation.dynamicMetadata = error |
| | return |
| | } else if (hasViewportRegex.test(componentStack)) { |
| | const message = `Route "${workStore.route}": Runtime data such as \`cookies()\`, \`headers()\`, \`params\`, or \`searchParams\` was accessed inside \`generateViewport\`. This delays the entire page from rendering, resulting in a slow user experience. Learn more: https://nextjs.org/docs/messages/next-prerender-dynamic-viewport` |
| | const error = createErrorWithComponentOrOwnerStack(message, componentStack) |
| | dynamicValidation.dynamicErrors.push(error) |
| | return |
| | } else if ( |
| | hasSuspenseBeforeRootLayoutWithoutBodyOrImplicitBodyRegex.test( |
| | componentStack |
| | ) |
| | ) { |
| | |
| | |
| | |
| | dynamicValidation.hasAllowedDynamic = true |
| | dynamicValidation.hasSuspenseAboveBody = true |
| | return |
| | } else if (hasSuspenseRegex.test(componentStack)) { |
| | |
| | |
| | dynamicValidation.hasAllowedDynamic = true |
| | return |
| | } else if (clientDynamic.syncDynamicErrorWithStack) { |
| | |
| | dynamicValidation.dynamicErrors.push( |
| | clientDynamic.syncDynamicErrorWithStack |
| | ) |
| | return |
| | } else { |
| | const message = `Route "${workStore.route}": Runtime data such as \`cookies()\`, \`headers()\`, \`params\`, or \`searchParams\` was accessed outside of \`<Suspense>\`. This delays the entire page from rendering, resulting in a slow user experience. Learn more: https://nextjs.org/docs/messages/blocking-route` |
| | const error = createErrorWithComponentOrOwnerStack(message, componentStack) |
| | dynamicValidation.dynamicErrors.push(error) |
| | return |
| | } |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | function createErrorWithComponentOrOwnerStack( |
| | message: string, |
| | componentStack: string |
| | ) { |
| | const ownerStack = |
| | process.env.NODE_ENV !== 'production' && React.captureOwnerStack |
| | ? React.captureOwnerStack() |
| | : null |
| |
|
| | const error = new Error(message) |
| | |
| | |
| | error.stack = error.name + ': ' + message + (ownerStack || componentStack) |
| | return error |
| | } |
| |
|
| | export enum PreludeState { |
| | Full = 0, |
| | Empty = 1, |
| | Errored = 2, |
| | } |
| |
|
| | export function logDisallowedDynamicError( |
| | workStore: WorkStore, |
| | error: Error |
| | ): void { |
| | console.error(error) |
| |
|
| | if (!workStore.dev) { |
| | if (workStore.hasReadableErrorStacks) { |
| | console.error( |
| | `To get a more detailed stack trace and pinpoint the issue, start the app in development mode by running \`next dev\`, then open "${workStore.route}" in your browser to investigate the error.` |
| | ) |
| | } else { |
| | console.error(`To get a more detailed stack trace and pinpoint the issue, try one of the following: |
| | - Start the app in development mode by running \`next dev\`, then open "${workStore.route}" in your browser to investigate the error. |
| | - Rerun the production build with \`next build --debug-prerender\` to generate better stack traces.`) |
| | } |
| | } |
| | } |
| |
|
| | export function throwIfDisallowedDynamic( |
| | workStore: WorkStore, |
| | prelude: PreludeState, |
| | dynamicValidation: DynamicValidationState, |
| | serverDynamic: DynamicTrackingState |
| | ): void { |
| | if (serverDynamic.syncDynamicErrorWithStack) { |
| | logDisallowedDynamicError( |
| | workStore, |
| | serverDynamic.syncDynamicErrorWithStack |
| | ) |
| | throw new StaticGenBailoutError() |
| | } |
| |
|
| | if (prelude !== PreludeState.Full) { |
| | if (dynamicValidation.hasSuspenseAboveBody) { |
| | |
| | |
| | |
| | return |
| | } |
| |
|
| | |
| | |
| | |
| | const dynamicErrors = dynamicValidation.dynamicErrors |
| | if (dynamicErrors.length > 0) { |
| | for (let i = 0; i < dynamicErrors.length; i++) { |
| | logDisallowedDynamicError(workStore, dynamicErrors[i]) |
| | } |
| |
|
| | throw new StaticGenBailoutError() |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | if (dynamicValidation.hasDynamicViewport) { |
| | console.error( |
| | `Route "${workStore.route}" has a \`generateViewport\` that depends on Request data (\`cookies()\`, etc...) or uncached external data (\`fetch(...)\`, etc...) without explicitly allowing fully dynamic rendering. See more info here: https://nextjs.org/docs/messages/next-prerender-dynamic-viewport` |
| | ) |
| | throw new StaticGenBailoutError() |
| | } |
| |
|
| | if (prelude === PreludeState.Empty) { |
| | |
| | |
| | |
| | console.error( |
| | `Route "${workStore.route}" did not produce a static shell and Next.js was unable to determine a reason. This is a bug in Next.js.` |
| | ) |
| | throw new StaticGenBailoutError() |
| | } |
| | } else { |
| | if ( |
| | dynamicValidation.hasAllowedDynamic === false && |
| | dynamicValidation.hasDynamicMetadata |
| | ) { |
| | console.error( |
| | `Route "${workStore.route}" has a \`generateMetadata\` that depends on Request data (\`cookies()\`, etc...) or uncached external data (\`fetch(...)\`, etc...) when the rest of the route does not. See more info here: https://nextjs.org/docs/messages/next-prerender-dynamic-metadata` |
| | ) |
| | throw new StaticGenBailoutError() |
| | } |
| | } |
| | } |
| |
|
| | export function getStaticShellDisallowedDynamicReasons( |
| | workStore: WorkStore, |
| | prelude: PreludeState, |
| | dynamicValidation: DynamicValidationState |
| | ): Array<Error> { |
| | if (dynamicValidation.hasSuspenseAboveBody) { |
| | |
| | |
| | |
| | return [] |
| | } |
| |
|
| | if (prelude !== PreludeState.Full) { |
| | |
| | |
| | |
| | const dynamicErrors = dynamicValidation.dynamicErrors |
| | if (dynamicErrors.length > 0) { |
| | return dynamicErrors |
| | } |
| |
|
| | if (prelude === PreludeState.Empty) { |
| | |
| | |
| | |
| | return [ |
| | new InvariantError( |
| | `Route "${workStore.route}" did not produce a static shell and Next.js was unable to determine a reason.` |
| | ), |
| | ] |
| | } |
| | } else { |
| | |
| | if ( |
| | dynamicValidation.hasAllowedDynamic === false && |
| | dynamicValidation.dynamicErrors.length === 0 && |
| | dynamicValidation.dynamicMetadata |
| | ) { |
| | return [dynamicValidation.dynamicMetadata] |
| | } |
| | } |
| | |
| | return [] |
| | } |
| |
|
| | export function delayUntilRuntimeStage<T>( |
| | prerenderStore: PrerenderStoreModernRuntime, |
| | result: Promise<T> |
| | ): Promise<T> { |
| | if (prerenderStore.runtimeStagePromise) { |
| | return prerenderStore.runtimeStagePromise.then(() => result) |
| | } |
| | return result |
| | } |
| |
|