UNPKG

next

Version:

The React Framework

256 lines (255 loc) • 16.5 kB
import { workAsyncStorage } from '../app-render/work-async-storage.external'; import { throwForMissingRequestStore, workUnitAsyncStorage } from '../app-render/work-unit-async-storage.external'; import { applyOwnerStack, makePrefetchHangingPromise, makeUntrackedHangingPromise, RENDER_STAGES_BY_DATA_KIND, trackIncompatibleShellContent } from '../dynamic-rendering-utils'; import { isRequestApiAllowedInCurrentPhase } from './utils'; import { InvariantError } from '../../shared/lib/invariant-error'; import { RenderStage } from '../app-render/staged-rendering'; /** * When `partialPrefetching` is enabled, this function allows you to indicate * that the subsequent code should be excluded from the shell. It will be deferred until * a prefetch (i.e. when using `<Link prefetch={true}>`) or a navigation. * * It has no effect during static prerendering — static output is computed * once and shared across many clients, so there's no per-request cost to * save — and no effect on the initial load of a page. * * Unlike `connection()`, it does not mark the subtree as request-dependent — * content below `await prefetch()` remains fully cacheable. * * Read more: [Next.js Docs: `prefetch`](https://nextjs.org/docs/app/api-reference/functions/prefetch) */ export function prefetch() { const workStore = workAsyncStorage.getStore(); const workUnitStore = workUnitAsyncStorage.getStore(); if (!workStore || !workUnitStore) { const callingExpression = 'prefetch'; throwForMissingRequestStore(callingExpression); } if (!process.env.__NEXT_CACHE_COMPONENTS) { throw new Error(`Route ${workStore.route} used \`prefetch()\`, which requires Cache Components to be enabled. Learn more: https://nextjs.org/docs/app/api-reference/config/next-config-js/cacheComponents`); } if (!isRequestApiAllowedInCurrentPhase(workUnitStore)) { throw new Error(`Route ${workStore.route} used \`prefetch()\` inside \`after()\` while rendering. The \`prefetch()\` function is used to indicate the subsequent code must not run in the app shell, but \`after()\` executes after the request, so this function is not allowed in this scope. See more info here: https://nextjs.org/docs/app/api-reference/functions/after`); } const stageVariants = RENDER_STAGES_BY_DATA_KIND.staticUrlData.prefetchApi; switch(workUnitStore.type){ case 'prerender': { // Content below `prefetch()` is excluded from the shell, but it's // deliberately included in the static output (and thus in static // prefetches), so we only delay it until the static prefetch stage. // It resolves in `PrefetchStatic_prefetchApi`, one stage ahead of URL data, // so validation can attribute a blocking hole to `prefetch()`. const { stagedRendering } = workUnitStore; if (!stagedRendering) { // Prospective prerender // `prefetch()` will resolve in the final prerender, so resolve it here as well. return Promise.resolve(undefined); } else { // Final prerender return stagedRendering.delayUntilStage(stageVariants.static, 'prefetch', undefined); } } case 'prerender-runtime': { // In a shell render, prefetch() doesn't resolve, because it doesn't reach // `PrefetchRuntime`. It'll resolve in a runtime prefetch, and in a runtime // prerender produced during a navigation. // Note that this does not mark the subtree as dynamic -- content guarded by // prefetch() is still considered cacheable. const { stagedRendering } = workUnitStore; const prefetchStage = stageVariants.runtime; if (!stagedRendering) { // Prospective prerender // Make sure we don't unblock content that won't be reached in the final prerender. if (workUnitStore.finalStage < prefetchStage) { return makePrefetchHangingPromise(workUnitStore.renderSignal, workStore.route, '`prefetch()`'); } else { return Promise.resolve(undefined); } } else { // Final prerender return stagedRendering.delayUntilStage(prefetchStage, 'prefetch', undefined); } } case 'request': { const { stagedRendering } = workUnitStore; if (stagedRendering) { // We can either recover a static shell or a runtime shell, but not both. trackIncompatibleShellContent(workUnitStore, '`prefetch()`'); const stage = stageVariants[workUnitStore.needsRuntimeShell ? 'runtime' // Match the timing of 'prerender-runtime'. : 'static' // Match the timing of 'prerender'. ]; return stagedRendering.delayUntilStage(stage, 'prefetch', undefined); } return Promise.resolve(undefined); } case 'cache': { const error = new Error(`Route ${workStore.route} used \`prefetch()\` inside "use cache". This is not currently supported. Instead, move the "use cache" directive to a function that's called below \`await prefetch()\`, so that the cached content is deferred to the prefetch without caching the stage boundary itself. See more info here: https://nextjs.org/docs/messages/next-request-in-use-cache`); Error.captureStackTrace(error, prefetch); applyOwnerStack(error); workStore.invalidDynamicUsageError ??= error; throw error; } case 'private-cache': { const error = new Error(`Route ${workStore.route} used \`prefetch()\` inside "use cache: private". This is not currently supported. Instead, move the "use cache" directive to a function that's called below \`await prefetch()\`, so that the cached content is deferred to the prefetch without caching the stage boundary itself. See more info here: https://nextjs.org/docs/messages/next-request-in-use-cache`); Error.captureStackTrace(error, prefetch); applyOwnerStack(error); workStore.invalidDynamicUsageError ??= error; throw error; } case 'unstable-cache': { throw new Error(`Route ${workStore.route} used \`prefetch()\` inside a function cached with \`unstable_cache()\`. The \`prefetch()\` function is used to indicate the subsequent code must not run in the app shell, but \`unstable_cache()\` caches must be able to be produced before a prefetch, so this function is not allowed in this scope. See more info here: https://nextjs.org/docs/app/api-reference/functions/unstable_cache`); } case 'build-time-generator': { throw new Error(`Route ${workStore.route} used \`prefetch()\` inside \`${workUnitStore.functionName}\`. This is not supported because \`${workUnitStore.functionName}\` runs at build time without a prefetch. Read more: https://nextjs.org/docs/messages/next-dynamic-api-wrong-context`); } case 'prerender-client': case 'validation-client': { const exportName = '`prefetch`'; 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-legacy': { // NOTE: Should not be reachable, because we don't use this mode in cacheComponents, // which we require at the top throw new Error(`Route ${workStore.route} used \`prefetch()\`, which requires Cache Components to be enabled. Learn more: https://nextjs.org/docs/app/api-reference/config/next-config-js/cacheComponents`); } default: { workUnitStore; return Promise.resolve(undefined); } } } /** * This function allows you to indicate that the subsequent code should be * deferred to the actual navigation instead of rendering during a runtime * prefetch. Runtime prefetches are rendered per-user, per-link, so deferring * content below `await navigation()` saves that per-request * rendering cost. * * It has no effect during static prerendering — static output is computed * once and shared across many clients, so there's no per-request cost to * save — and no effect on the initial load of a page. * * Unlike `connection()`, it does not mark the subtree as request-dependent — * content below `await navigation()` remains fully cacheable. * * Read more: [Next.js Docs: `navigation`](https://nextjs.org/docs/app/api-reference/functions/navigation) */ export function navigation() { const workStore = workAsyncStorage.getStore(); const workUnitStore = workUnitAsyncStorage.getStore(); if (!workStore || !workUnitStore) { const callingExpression = 'navigation'; throwForMissingRequestStore(callingExpression); } if (!process.env.__NEXT_CACHE_COMPONENTS) { throw new Error(`Route ${workStore.route} used \`navigation()\`, which requires Cache Components to be enabled. Learn more: https://nextjs.org/docs/app/api-reference/config/next-config-js/cacheComponents`); } if (!isRequestApiAllowedInCurrentPhase(workUnitStore)) { throw new Error(`Route ${workStore.route} used \`navigation()\` inside \`after()\` while rendering. The \`navigation()\` function is used to indicate the subsequent code must only run during an actual navigation, but \`after()\` executes after the request, so this function is not allowed in this scope. See more info here: https://nextjs.org/docs/app/api-reference/functions/after`); } switch(workUnitStore.type){ case 'prerender': { // Static prerenders are computed once and shared across many // clients, so there's no per-request prefetch cost to save by // deferring the content — it's deliberately included in the static // output (and thus in static prefetches). // However, it's excluded from the shell, and has to be separated from // prefetch(), so we have to delay it. const { stagedRendering } = workUnitStore; if (!stagedRendering) { // Prospective prerender // `navigation()` will resolve in the final prerender, so resolve it here as well. return Promise.resolve(undefined); } else { // Final prerender return stagedRendering.delayUntilStage(RenderStage.NavigationStatic, 'navigation', undefined); } } case 'prerender-runtime': { // In a shell or runtime prefetch, navigation() doesn't resolve, // because they don't reach `NavigationRuntime`. // It'll only resolve in a runtime prerender produced during a navigation. // Note that this does not mark the subtree as dynamic -- content guarded by // navigation() is still considered cacheable. const { stagedRendering } = workUnitStore; const navigationStage = RenderStage.NavigationRuntime; if (!stagedRendering) { // Prospective prerender // Make sure we don't unblock content that won't be reached in the final prerender. if (workUnitStore.finalStage < navigationStage) { return makeUntrackedHangingPromise(workUnitStore.renderSignal, workStore.route, '`navigation()`'); } else { return Promise.resolve(undefined); } } else { // Final prerender return stagedRendering.delayUntilStage(navigationStage, 'navigation', undefined); } } case 'request': { const { stagedRendering } = workUnitStore; if (stagedRendering) { // We can either recover a static shell or a runtime shell, but not both. trackIncompatibleShellContent(workUnitStore, '`navigation()`'); const stage = workUnitStore.needsRuntimeShell ? RenderStage.NavigationRuntime // Match the timing of 'prerender-runtime'. : RenderStage.NavigationStatic // Match the timing of 'prerender'. ; return stagedRendering.delayUntilStage(stage, 'navigation', undefined); } return Promise.resolve(undefined); } case 'cache': { const error = new Error(`Route ${workStore.route} used \`navigation()\` inside "use cache". This is not currently supported. Instead, move the "use cache" directive to a function that's called below \`await navigation()\`, so that the cached content is deferred to the navigation without caching the stage boundary itself. See more info here: https://nextjs.org/docs/messages/next-request-in-use-cache`); Error.captureStackTrace(error, navigation); applyOwnerStack(error); workStore.invalidDynamicUsageError ??= error; throw error; } case 'private-cache': { const error = new Error(`Route ${workStore.route} used \`navigation()\` inside "use cache: private". This is not currently supported. Instead, move the "use cache" directive to a function that's called below \`await navigation()\`, so that the cached content is deferred to the navigation without caching the stage boundary itself. See more info here: https://nextjs.org/docs/messages/next-request-in-use-cache`); Error.captureStackTrace(error, navigation); applyOwnerStack(error); workStore.invalidDynamicUsageError ??= error; throw error; } case 'unstable-cache': { throw new Error(`Route ${workStore.route} used \`navigation()\` inside a function cached with \`unstable_cache()\`. The \`navigation()\` function is used to indicate the subsequent code must only run during an actual navigation, but \`unstable_cache()\` caches must be able to be produced before a navigation, so this function is not allowed in this scope. See more info here: https://nextjs.org/docs/app/api-reference/functions/unstable_cache`); } case 'build-time-generator': { throw new Error(`Route ${workStore.route} used \`navigation()\` inside \`${workUnitStore.functionName}\`. This is not supported because \`${workUnitStore.functionName}\` runs at build time without a navigation. Read more: https://nextjs.org/docs/messages/next-dynamic-api-wrong-context`); } case 'prerender-client': case 'validation-client': { const exportName = '`navigation`'; 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-legacy': { // NOTE: Should not be reachable, because we don't use this mode in cacheComponents, // which we require at the top throw new Error(`Route ${workStore.route} used \`navigation()\`, which requires Cache Components to be enabled. Learn more: https://nextjs.org/docs/app/api-reference/config/next-config-js/cacheComponents`); } default: { workUnitStore; return Promise.resolve(undefined); } } } //# sourceMappingURL=cache-stages.js.map