| | import { workAsyncStorage } from '../app-render/work-async-storage.external' |
| | import { workUnitAsyncStorage } from '../app-render/work-unit-async-storage.external' |
| | import { abortOnSynchronousPlatformIOAccess } from '../app-render/dynamic-rendering' |
| | import { InvariantError } from '../../shared/lib/invariant-error' |
| | import { RenderStage } from '../app-render/staged-rendering' |
| |
|
| | import { getServerReact, getClientReact } from '../runtime-reacts.external' |
| |
|
| | type ApiType = 'time' | 'random' | 'crypto' |
| |
|
| | export function io(expression: string, type: ApiType) { |
| | const workUnitStore = workUnitAsyncStorage.getStore() |
| | const workStore = workAsyncStorage.getStore() |
| |
|
| | if (!workUnitStore || !workStore) { |
| | return |
| | } |
| |
|
| | switch (workUnitStore.type) { |
| | case 'prerender': |
| | case 'prerender-runtime': { |
| | const prerenderSignal = workUnitStore.controller.signal |
| |
|
| | if (prerenderSignal.aborted === false) { |
| | |
| | |
| | let message: string |
| | switch (type) { |
| | case 'time': |
| | message = `Route "${workStore.route}" used ${expression} before accessing either uncached data (e.g. \`fetch()\`) or Request data (e.g. \`cookies()\`, \`headers()\`, \`connection()\`, and \`searchParams\`). Accessing the current time in a Server Component requires reading one of these data sources first. Alternatively, consider moving this expression into a Client Component or Cache Component. See more info here: https://nextjs.org/docs/messages/next-prerender-current-time` |
| | break |
| | case 'random': |
| | message = `Route "${workStore.route}" used ${expression} before accessing either uncached data (e.g. \`fetch()\`) or Request data (e.g. \`cookies()\`, \`headers()\`, \`connection()\`, and \`searchParams\`). Accessing random values synchronously in a Server Component requires reading one of these data sources first. Alternatively, consider moving this expression into a Client Component or Cache Component. See more info here: https://nextjs.org/docs/messages/next-prerender-random` |
| | break |
| | case 'crypto': |
| | message = `Route "${workStore.route}" used ${expression} before accessing either uncached data (e.g. \`fetch()\`) or Request data (e.g. \`cookies()\`, \`headers()\`, \`connection()\`, and \`searchParams\`). Accessing random cryptographic values synchronously in a Server Component requires reading one of these data sources first. Alternatively, consider moving this expression into a Client Component or Cache Component. See more info here: https://nextjs.org/docs/messages/next-prerender-crypto` |
| | break |
| | default: |
| | throw new InvariantError( |
| | 'Unknown expression type in abortOnSynchronousPlatformIOAccess.' |
| | ) |
| | } |
| |
|
| | abortOnSynchronousPlatformIOAccess( |
| | workStore.route, |
| | expression, |
| | applyOwnerStack(new Error(message)), |
| | workUnitStore |
| | ) |
| | } |
| | break |
| | } |
| | case 'prerender-client': { |
| | const prerenderSignal = workUnitStore.controller.signal |
| |
|
| | if (prerenderSignal.aborted === false) { |
| | |
| | |
| | let message: string |
| | switch (type) { |
| | case 'time': |
| | message = `Route "${workStore.route}" used ${expression} inside a Client Component without a Suspense boundary above it. See more info here: https://nextjs.org/docs/messages/next-prerender-current-time-client` |
| | break |
| | case 'random': |
| | message = `Route "${workStore.route}" used ${expression} inside a Client Component without a Suspense boundary above it. See more info here: https://nextjs.org/docs/messages/next-prerender-random-client` |
| | break |
| | case 'crypto': |
| | message = `Route "${workStore.route}" used ${expression} inside a Client Component without a Suspense boundary above it. See more info here: https://nextjs.org/docs/messages/next-prerender-crypto-client` |
| | break |
| | default: |
| | throw new InvariantError( |
| | 'Unknown expression type in abortOnSynchronousPlatformIOAccess.' |
| | ) |
| | } |
| |
|
| | abortOnSynchronousPlatformIOAccess( |
| | workStore.route, |
| | expression, |
| | applyOwnerStack(new Error(message)), |
| | workUnitStore |
| | ) |
| | } |
| | break |
| | } |
| | case 'request': |
| | if (process.env.NODE_ENV === 'development') { |
| | const stageController = workUnitStore.stagedRendering |
| | if (stageController && stageController.canSyncInterrupt()) { |
| | let message: string |
| | if (stageController.currentStage === RenderStage.Static) { |
| | switch (type) { |
| | case 'time': |
| | message = `Route "${workStore.route}" used ${expression} before accessing either uncached data (e.g. \`fetch()\`) or Request data (e.g. \`cookies()\`, \`headers()\`, \`connection()\`, and \`searchParams\`). Accessing the current time in a Server Component requires reading one of these data sources first. Alternatively, consider moving this expression into a Client Component or Cache Component. See more info here: https://nextjs.org/docs/messages/next-prerender-current-time` |
| | break |
| | case 'random': |
| | message = `Route "${workStore.route}" used ${expression} before accessing either uncached data (e.g. \`fetch()\`) or Request data (e.g. \`cookies()\`, \`headers()\`, \`connection()\`, and \`searchParams\`). Accessing random values synchronously in a Server Component requires reading one of these data sources first. Alternatively, consider moving this expression into a Client Component or Cache Component. See more info here: https://nextjs.org/docs/messages/next-prerender-random` |
| | break |
| | case 'crypto': |
| | message = `Route "${workStore.route}" used ${expression} before accessing either uncached data (e.g. \`fetch()\`) or Request data (e.g. \`cookies()\`, \`headers()\`, \`connection()\`, and \`searchParams\`). Accessing random cryptographic values synchronously in a Server Component requires reading one of these data sources first. Alternatively, consider moving this expression into a Client Component or Cache Component. See more info here: https://nextjs.org/docs/messages/next-prerender-crypto` |
| | break |
| | default: |
| | throw new InvariantError( |
| | 'Unknown expression type in abortOnSynchronousPlatformIOAccess.' |
| | ) |
| | } |
| | } else { |
| | |
| | |
| | |
| | |
| |
|
| | let accessStatement: string |
| | let additionalInfoLink: string |
| |
|
| | switch (type) { |
| | case 'time': |
| | accessStatement = 'the current time' |
| | additionalInfoLink = |
| | 'https://nextjs.org/docs/messages/next-prerender-runtime-current-time' |
| | break |
| | case 'random': |
| | accessStatement = 'random values synchronously' |
| | additionalInfoLink = |
| | 'https://nextjs.org/docs/messages/next-prerender-runtime-random' |
| | break |
| | case 'crypto': |
| | accessStatement = 'random cryptographic values synchronously' |
| | additionalInfoLink = |
| | 'https://nextjs.org/docs/messages/next-prerender-runtime-crypto' |
| | break |
| | default: |
| | throw new InvariantError( |
| | 'Unknown expression type in abortOnSynchronousPlatformIOAccess.' |
| | ) |
| | } |
| |
|
| | message = `Route "${workStore.route}" used ${expression} before accessing either uncached data (e.g. \`fetch()\`) or awaiting \`connection()\`. When configured for Runtime prefetching, accessing ${accessStatement} in a Server Component requires reading one of these data sources first. Alternatively, consider moving this expression into a Client Component or Cache Component. See more info here: ${additionalInfoLink}` |
| | } |
| |
|
| | const syncIOError = applyOwnerStack(new Error(message)) |
| | stageController.syncInterruptCurrentStageWithReason(syncIOError) |
| | } |
| | } |
| | break |
| | case 'prerender-ppr': |
| | case 'prerender-legacy': |
| | case 'cache': |
| | case 'private-cache': |
| | case 'unstable-cache': |
| | break |
| | default: |
| | workUnitStore satisfies never |
| | } |
| | } |
| |
|
| | function applyOwnerStack(error: Error) { |
| | |
| | |
| | |
| | |
| | if (process.env.NODE_ENV !== 'production') { |
| | const ownerStack = |
| | getClientReact()?.captureOwnerStack?.() ?? |
| | getServerReact()?.captureOwnerStack?.() |
| |
|
| | if (ownerStack) { |
| | let stack = ownerStack |
| |
|
| | if (error.stack) { |
| | const frames: string[] = [] |
| |
|
| | for (const frame of error.stack.split('\n').slice(1)) { |
| | if (frame.includes('react_stack_bottom_frame')) { |
| | break |
| | } |
| |
|
| | frames.push(frame) |
| | } |
| |
|
| | stack = '\n' + frames.join('\n') + stack |
| | } |
| |
|
| | error.stack = error.name + ': ' + error.message + stack |
| | } |
| | } |
| |
|
| | return error |
| | } |
| |
|