next
Version:
The React Framework
1,000 lines • 61 kB
JavaScript
/**
* The functions provided by this module are used to communicate certain properties
* about the currently running code so that Next.js can make decisions on how to handle
* the current execution in different rendering modes such as pre-rendering, resuming, and SSR.
*
* Today Next.js treats all code as potentially static. Certain APIs may only make sense when dynamically rendering.
* Traditionally this meant deopting the entire render to dynamic however with PPR we can now deopt parts
* of a React tree as dynamic while still keeping other parts static. There are really two different kinds of
* Dynamic indications.
*
* The first is simply an intention to be dynamic. unstable_noStore is an example of this where
* the currently executing code simply declares that the current scope is dynamic but if you use it
* inside unstable_cache it can still be cached. This type of indication can be removed if we ever
* make the default dynamic to begin with because the only way you would ever be static is inside
* a cache scope which this indication does not affect.
*
* The second is an indication that a dynamic data source was read. This is a stronger form of dynamic
* because it means that it is inappropriate to cache this at all. using a dynamic data source inside
* unstable_cache should error. If you want to use some dynamic data inside unstable_cache you should
* read that data outside the cache and pass it in as an argument to the cached function.
*/ "use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
0 && (module.exports = {
DynamicHoleKind: null,
Postpone: null,
PreludeState: null,
abortAndThrowOnSynchronousRequestDataAccess: null,
abortOnSynchronousPlatformIOAccess: null,
accessedDynamicData: null,
annotateDynamicAccess: null,
consumeDynamicAccess: null,
createDynamicTrackingState: null,
createDynamicValidationState: null,
createHangingInputAbortSignal: null,
createInstantValidationState: null,
createRenderInBrowserAbortSignal: null,
formatDynamicAPIAccesses: null,
getFirstDynamicReason: null,
getNavigationDisallowedDynamicReasons: null,
getStaticShellDisallowedDynamicReasons: null,
isDynamicPostpone: null,
isPrerenderInterruptedError: null,
logDisallowedDynamicError: null,
markCurrentScopeAsDynamic: null,
postponeWithTracking: null,
throwIfDisallowedDynamic: null,
throwIfSyncIOUsed: null,
throwToInterruptStaticGeneration: null,
trackAllowedDynamicAccess: null,
trackDynamicDataInDynamicRender: null,
trackDynamicHoleInNavigation: null,
trackDynamicHoleInRuntimeShell: null,
trackDynamicHoleInStaticShell: null,
trackThrownErrorInNavigation: null,
useDynamicRouteParams: null,
useDynamicSearchParams: null
});
function _export(target, all) {
for(var name in all)Object.defineProperty(target, name, {
enumerable: true,
get: all[name]
});
}
_export(exports, {
DynamicHoleKind: function() {
return DynamicHoleKind;
},
Postpone: function() {
return Postpone;
},
PreludeState: function() {
return PreludeState;
},
abortAndThrowOnSynchronousRequestDataAccess: function() {
return abortAndThrowOnSynchronousRequestDataAccess;
},
abortOnSynchronousPlatformIOAccess: function() {
return abortOnSynchronousPlatformIOAccess;
},
accessedDynamicData: function() {
return accessedDynamicData;
},
annotateDynamicAccess: function() {
return annotateDynamicAccess;
},
consumeDynamicAccess: function() {
return consumeDynamicAccess;
},
createDynamicTrackingState: function() {
return createDynamicTrackingState;
},
createDynamicValidationState: function() {
return createDynamicValidationState;
},
createHangingInputAbortSignal: function() {
return createHangingInputAbortSignal;
},
createInstantValidationState: function() {
return createInstantValidationState;
},
createRenderInBrowserAbortSignal: function() {
return createRenderInBrowserAbortSignal;
},
formatDynamicAPIAccesses: function() {
return formatDynamicAPIAccesses;
},
getFirstDynamicReason: function() {
return getFirstDynamicReason;
},
getNavigationDisallowedDynamicReasons: function() {
return getNavigationDisallowedDynamicReasons;
},
getStaticShellDisallowedDynamicReasons: function() {
return getStaticShellDisallowedDynamicReasons;
},
isDynamicPostpone: function() {
return isDynamicPostpone;
},
isPrerenderInterruptedError: function() {
return isPrerenderInterruptedError;
},
logDisallowedDynamicError: function() {
return logDisallowedDynamicError;
},
markCurrentScopeAsDynamic: function() {
return markCurrentScopeAsDynamic;
},
postponeWithTracking: function() {
return postponeWithTracking;
},
throwIfDisallowedDynamic: function() {
return throwIfDisallowedDynamic;
},
throwIfSyncIOUsed: function() {
return throwIfSyncIOUsed;
},
throwToInterruptStaticGeneration: function() {
return throwToInterruptStaticGeneration;
},
trackAllowedDynamicAccess: function() {
return trackAllowedDynamicAccess;
},
trackDynamicDataInDynamicRender: function() {
return trackDynamicDataInDynamicRender;
},
trackDynamicHoleInNavigation: function() {
return trackDynamicHoleInNavigation;
},
trackDynamicHoleInRuntimeShell: function() {
return trackDynamicHoleInRuntimeShell;
},
trackDynamicHoleInStaticShell: function() {
return trackDynamicHoleInStaticShell;
},
trackThrownErrorInNavigation: function() {
return trackThrownErrorInNavigation;
},
useDynamicRouteParams: function() {
return useDynamicRouteParams;
},
useDynamicSearchParams: function() {
return useDynamicSearchParams;
}
});
const _react = /*#__PURE__*/ _interop_require_default(require("react"));
const _hooksservercontext = require("../../client/components/hooks-server-context");
const _staticgenerationbailout = require("../../client/components/static-generation-bailout");
const _workunitasyncstorageexternal = require("./work-unit-async-storage.external");
const _workasyncstorageexternal = require("../app-render/work-async-storage.external");
const _dynamicrenderingutils = require("../dynamic-rendering-utils");
const _boundaryconstants = require("../../lib/framework/boundary-constants");
const _scheduler = require("../../lib/scheduler");
const _bailouttocsr = require("../../shared/lib/lazy-dynamic/bailout-to-csr");
const _blockingroutemessages = require("./blocking-route-messages");
const _invarianterror = require("../../shared/lib/invariant-error");
const _boundaryconstants1 = require("./instant-validation/boundary-constants");
const _boundarytracking = require("./instant-validation/boundary-tracking");
const _instantmessages = require("../../shared/lib/instant-messages");
function _interop_require_default(obj) {
return obj && obj.__esModule ? obj : {
default: obj
};
}
const hasPostpone = typeof _react.default.unstable_postpone === 'function';
function createDynamicTrackingState(isDebugDynamicAccesses) {
return {
isDebugDynamicAccesses,
dynamicAccesses: [],
syncDynamicErrorWithStack: null,
syncDynamicErrorWithStackPostMicrotask: false
};
}
function createDynamicValidationState() {
return {
hasSuspenseAboveBody: false,
hasDynamicMetadata: false,
dynamicMetadata: null,
hasDynamicViewport: false,
hasAllowedDynamic: false,
dynamicErrors: []
};
}
function getPendingClientSyncDynamicError(clientDynamic) {
return clientDynamic.syncDynamicErrorWithStackPostMicrotask ? null : clientDynamic.syncDynamicErrorWithStack;
}
function getFirstDynamicReason(trackingState) {
var _trackingState_dynamicAccesses_;
return (_trackingState_dynamicAccesses_ = trackingState.dynamicAccesses[0]) == null ? void 0 : _trackingState_dynamicAccesses_.expression;
}
function markCurrentScopeAsDynamic(store, workUnitStore, expression) {
if (workUnitStore) {
switch(workUnitStore.type){
case 'cache':
case 'unstable-cache':
// Inside cache scopes, marking a scope as dynamic has no effect,
// because the outer cache scope creates a cache boundary. This is
// subtly different from reading a dynamic data source, which is
// forbidden inside a cache scope.
return;
case 'private-cache':
// A private cache scope is already dynamic by definition.
return;
case 'prerender-legacy':
case 'prerender-ppr':
case 'request':
case 'generate-static-params':
break;
default:
workUnitStore;
}
}
// If we're forcing dynamic rendering or we're forcing static rendering, we
// don't need to do anything here because the entire page is already dynamic
// or it's static and it should not throw or postpone here.
if (store.forceDynamic || store.forceStatic) return;
if (store.dynamicShouldError) {
throw Object.defineProperty(new _staticgenerationbailout.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`), "__NEXT_ERROR_CODE", {
value: "E553",
enumerable: false,
configurable: true
});
}
if (workUnitStore) {
switch(workUnitStore.type){
case 'prerender-ppr':
return postponeWithTracking(store.route, expression, workUnitStore.dynamicTracking);
case 'prerender-legacy':
workUnitStore.revalidate = 0;
// We aren't prerendering, but we are generating a static page. We need
// to bail out of static generation.
const err = Object.defineProperty(new _hooksservercontext.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`), "__NEXT_ERROR_CODE", {
value: "E550",
enumerable: false,
configurable: true
});
store.dynamicUsageDescription = expression;
store.dynamicUsageStack = err.stack;
throw err;
case 'request':
if (process.env.NODE_ENV !== 'production') {
workUnitStore.usedDynamic = true;
}
break;
case 'generate-static-params':
break;
default:
workUnitStore;
}
}
}
function throwToInterruptStaticGeneration(expression, store, prerenderStore) {
// We aren't prerendering but we are generating a static page. We need to bail out of static generation
const err = Object.defineProperty(new _hooksservercontext.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`), "__NEXT_ERROR_CODE", {
value: "E558",
enumerable: false,
configurable: true
});
prerenderStore.revalidate = 0;
store.dynamicUsageDescription = expression;
store.dynamicUsageStack = err.stack;
throw err;
}
function trackDynamicDataInDynamicRender(workUnitStore) {
switch(workUnitStore.type){
case 'cache':
case 'unstable-cache':
// Inside cache scopes, marking a scope as dynamic has no effect,
// because the outer cache scope creates a cache boundary. This is
// subtly different from reading a dynamic data source, which is
// forbidden inside a cache scope.
return;
case 'private-cache':
// A private cache scope is already dynamic by definition.
return;
case 'prerender':
case 'prerender-runtime':
case 'prerender-legacy':
case 'prerender-ppr':
case 'prerender-client':
case 'validation-client':
case 'generate-static-params':
break;
case 'request':
if (process.env.NODE_ENV !== 'production') {
workUnitStore.usedDynamic = true;
}
break;
default:
workUnitStore;
}
}
function abortOnSynchronousDynamicDataAccess(route, expression, prerenderStore) {
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({
// 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
});
}
}
function abortOnSynchronousPlatformIOAccess(route, expression, errorWithStack, prerenderStore) {
const dynamicTracking = prerenderStore.dynamicTracking;
if (dynamicTracking && dynamicTracking.syncDynamicErrorWithStack === null) {
dynamicTracking.syncDynamicErrorWithStack = errorWithStack;
// React completes the task that is currently rendering before scheduled
// abort cleanup. Client tracking can attribute the sync IO only during
// that current task; server tracking keeps the error regardless.
queueMicrotask(()=>{
dynamicTracking.syncDynamicErrorWithStackPostMicrotask = true;
});
}
abortOnSynchronousDynamicDataAccess(route, expression, prerenderStore);
}
function abortAndThrowOnSynchronousRequestDataAccess(route, expression, errorWithStack, prerenderStore) {
// The synchronously accessed request data would have been available during
// a runtime prerender, which would have rendered past this point instead of
// aborting — so a runtime prefetch would produce more content than this
// render. Record that, same as when request data access creates a hanging
// promise (see makeRuntimeHangingPromise). Unlike
// `abortOnSynchronousPlatformIOAccess`, which aborts a runtime prerender
// all the same and therefore must not record anything.
(0, _dynamicrenderingutils.trackRuntimeDataAccessed)(prerenderStore);
const prerenderSignal = prerenderStore.controller.signal;
if (prerenderSignal.aborted === false) {
// TODO it would be better to move this aborted check into the callsite so we can avoid making
// the error object when it isn't relevant to the aborting of the prerender however
// since we need the throw semantics regardless of whether we abort it is easier to land
// this way. See how this was handled with `abortOnSynchronousPlatformIOAccess` for a closer
// to ideal implementation
abortOnSynchronousDynamicDataAccess(route, expression, prerenderStore);
// Preserve the exact server-side dynamic access for final validation after
// interrupting this render.
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}.`);
}
function Postpone({ reason, route }) {
const prerenderStore = _workunitasyncstorageexternal.workUnitAsyncStorage.getStore();
const dynamicTracking = prerenderStore && prerenderStore.type === 'prerender-ppr' ? prerenderStore.dynamicTracking : null;
postponeWithTracking(route, reason, dynamicTracking);
}
function postponeWithTracking(route, expression, dynamicTracking) {
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.default.unstable_postpone(createPostponeReason(route, expression));
}
function createPostponeReason(route, expression) {
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`;
}
function isDynamicPostpone(err) {
if (typeof err === 'object' && err !== null && typeof err.message === 'string') {
return isDynamicPostponeReason(err.message);
}
return false;
}
function isDynamicPostponeReason(reason) {
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 Object.defineProperty(new Error('Invariant: isDynamicPostpone misidentified a postpone reason. This is a bug in Next.js'), "__NEXT_ERROR_CODE", {
value: "E296",
enumerable: false,
configurable: true
});
}
const NEXT_PRERENDER_INTERRUPTED = 'NEXT_PRERENDER_INTERRUPTED';
function createPrerenderInterruptedError(message) {
const error = Object.defineProperty(new Error(message), "__NEXT_ERROR_CODE", {
value: "E394",
enumerable: false,
configurable: true
});
error.digest = NEXT_PRERENDER_INTERRUPTED;
return error;
}
function isPrerenderInterruptedError(error) {
return typeof error === 'object' && error !== null && error.digest === NEXT_PRERENDER_INTERRUPTED && 'name' in error && 'message' in error && error instanceof Error;
}
function accessedDynamicData(dynamicAccesses) {
return dynamicAccesses.length > 0;
}
function consumeDynamicAccess(serverDynamic, clientDynamic) {
// We mutate because we only call this once we are no longer writing
// to the dynamicTrackingState and it's more efficient than creating a new
// array.
serverDynamic.dynamicAccesses.push(...clientDynamic.dynamicAccesses);
return serverDynamic.dynamicAccesses;
}
function formatDynamicAPIAccesses(dynamicAccesses) {
return dynamicAccesses.filter((access)=>typeof access.stack === 'string' && access.stack.length > 0).map(({ expression, stack })=>{
stack = stack.split('\n')// Remove the "Error: " prefix from the first line of the stack trace as
// well as the first 4 lines of the stack trace which is the distance
// from the user code and the `new Error().stack` call.
.slice(4).filter((line)=>{
// Exclude Next.js internals from the stack trace.
if (line.includes('node_modules/next/')) {
return false;
}
// Exclude anonymous functions from the stack trace.
if (line.includes(' (<anonymous>)')) {
return false;
}
// Exclude Node.js internals from the stack trace.
if (line.includes(' (node:')) {
return false;
}
return true;
}).join('\n');
return `Dynamic API Usage Debug - ${expression}:\n${stack}`;
});
}
function assertPostpone() {
if (!hasPostpone) {
throw Object.defineProperty(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`), "__NEXT_ERROR_CODE", {
value: "E224",
enumerable: false,
configurable: true
});
}
}
function createRenderInBrowserAbortSignal() {
const controller = new AbortController();
controller.abort(Object.defineProperty(new _bailouttocsr.BailoutToCSRError('Render in Browser'), "__NEXT_ERROR_CODE", {
value: "E721",
enumerable: false,
configurable: true
}));
return controller.signal;
}
function createHangingInputAbortSignal(workUnitStore) {
switch(workUnitStore.type){
case 'prerender':
case 'prerender-runtime':
const controller = new AbortController();
if (workUnitStore.cacheSignal) {
// If we have a cacheSignal it means we're in a prospective render. If
// the input we're waiting on is coming from another cache, we do want
// to wait for it so that we can resolve this cache entry too.
workUnitStore.cacheSignal.inputReady().then(()=>{
controller.abort();
});
} else {
// Otherwise we're in the final render and we should already have all
// our caches filled.
// If the prerender uses stages, we have wait until the final stage.
// if an input didn't resolve at that point, then we can assume it never will.
//
// We might still be waiting on some microtasks so we
// wait one tick before giving up. When we give up, we still want to
// render the content of this cache as deeply as we can so that we can
// suspend as deeply as possible in the tree or not at all if we don't
// end up waiting for the input.
const stagedRendering = (0, _workunitasyncstorageexternal.getStagedRenderingController)(workUnitStore);
if (stagedRendering && stagedRendering.finalStage !== null) {
stagedRendering.waitForStage(stagedRendering.finalStage).then(()=>(0, _scheduler.scheduleOnNextTick)(()=>controller.abort()), noop);
} else {
(0, _scheduler.scheduleOnNextTick)(()=>controller.abort());
}
}
return controller.signal;
case 'prerender-client':
case 'validation-client':
case 'prerender-ppr':
case 'prerender-legacy':
case 'request':
case 'cache':
case 'private-cache':
case 'unstable-cache':
case 'generate-static-params':
return undefined;
default:
workUnitStore;
}
}
function noop() {}
function annotateDynamicAccess(expression, prerenderStore) {
const dynamicTracking = prerenderStore.dynamicTracking;
if (dynamicTracking) {
dynamicTracking.dynamicAccesses.push({
stack: dynamicTracking.isDebugDynamicAccesses ? new Error().stack : undefined,
expression
});
}
}
function useDynamicRouteParams(expression) {
const workStore = _workasyncstorageexternal.workAsyncStorage.getStore();
const workUnitStore = _workunitasyncstorageexternal.workUnitAsyncStorage.getStore();
if (workStore && workUnitStore) {
switch(workUnitStore.type){
case 'prerender-client':
{
const fallbackParams = workUnitStore.fallbackRouteParams;
if (fallbackParams && fallbackParams.size > 0) {
// We are in a prerender with cacheComponents semantics. We are going to
// hang here and never resolve. This will cause the currently
// rendering component to effectively be a dynamic hole.
_react.default.use((0, _dynamicrenderingutils.makeClientHookHangingPromise)(workUnitStore.renderSignal, new _dynamicrenderingutils.ClientHookDynamicError(workStore.route, expression)));
}
break;
}
case 'prerender':
throw Object.defineProperty(new _invarianterror.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.`), "__NEXT_ERROR_CODE", {
value: "E795",
enumerable: false,
configurable: true
});
case 'prerender-ppr':
{
const fallbackParams = workUnitStore.fallbackRouteParams;
if (fallbackParams && fallbackParams.size > 0) {
return postponeWithTracking(workStore.route, expression, workUnitStore.dynamicTracking);
}
break;
}
case 'validation-client':
{
break;
}
case 'prerender-runtime':
throw Object.defineProperty(new _invarianterror.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.`), "__NEXT_ERROR_CODE", {
value: "E771",
enumerable: false,
configurable: true
});
case 'cache':
case 'private-cache':
throw Object.defineProperty(new _invarianterror.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.`), "__NEXT_ERROR_CODE", {
value: "E745",
enumerable: false,
configurable: true
});
case 'generate-static-params':
throw Object.defineProperty(new _invarianterror.InvariantError(`\`${expression}\` was called in \`generateStaticParams\`. Next.js should be preventing ${expression} from being included in server component files statically, but did not in this case.`), "__NEXT_ERROR_CODE", {
value: "E1130",
enumerable: false,
configurable: true
});
case 'prerender-legacy':
case 'request':
case 'unstable-cache':
break;
default:
workUnitStore;
}
}
}
function useDynamicSearchParams(expression) {
const workStore = _workasyncstorageexternal.workAsyncStorage.getStore();
const workUnitStore = _workunitasyncstorageexternal.workUnitAsyncStorage.getStore();
if (!workStore) {
// We assume pages router context and just return
return;
}
if (!workUnitStore) {
(0, _workunitasyncstorageexternal.throwForMissingRequestStore)(expression);
}
switch(workUnitStore.type){
case 'validation-client':
// During instant validation we try to behave as close to client as possible,
// so this shouldn't hang during SSR.
return;
case 'prerender-client':
{
_react.default.use((0, _dynamicrenderingutils.makeClientHookHangingPromise)(workUnitStore.renderSignal, new _dynamicrenderingutils.ClientHookDynamicError(workStore.route, expression)));
break;
}
case 'prerender-legacy':
case 'prerender-ppr':
{
if (workStore.forceStatic) {
return;
}
throw Object.defineProperty(new _bailouttocsr.BailoutToCSRError(expression), "__NEXT_ERROR_CODE", {
value: "E394",
enumerable: false,
configurable: true
});
}
case 'prerender':
case 'prerender-runtime':
throw Object.defineProperty(new _invarianterror.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.`), "__NEXT_ERROR_CODE", {
value: "E795",
enumerable: false,
configurable: true
});
case 'cache':
case 'unstable-cache':
case 'private-cache':
throw Object.defineProperty(new _invarianterror.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.`), "__NEXT_ERROR_CODE", {
value: "E745",
enumerable: false,
configurable: true
});
case 'generate-static-params':
throw Object.defineProperty(new _invarianterror.InvariantError(`\`${expression}\` was called in \`generateStaticParams\`. Next.js should be preventing ${expression} from being included in server component files statically, but did not in this case.`), "__NEXT_ERROR_CODE", {
value: "E1130",
enumerable: false,
configurable: true
});
case 'request':
return;
default:
workUnitStore;
}
}
const hasSuspenseRegex = /\n\s+at Suspense \(<anonymous>\)/;
// Common implicit body tags that React will treat as body when placed directly in html
const bodyAndImplicitTags = 'body|div|main|section|article|aside|header|footer|nav|form|p|span|h1|h2|h3|h4|h5|h6';
// Detects when RootLayoutBoundary (our framework marker component) appears
// after Suspense in the component stack, indicating the root layout is wrapped
// within a Suspense boundary. Ensures no body/html/implicit-body components are in between.
//
// Example matches:
// at Suspense (<anonymous>)
// at __next_root_layout_boundary__ (<anonymous>)
//
// Or with other components in between (but not body/html/implicit-body):
// at Suspense (<anonymous>)
// at SomeComponent (<anonymous>)
// at __next_root_layout_boundary__ (<anonymous>)
const hasSuspenseBeforeRootLayoutWithoutBodyOrImplicitBodyRegex = new RegExp(`\\n\\s+at Suspense \\(<anonymous>\\)(?:(?!\\n\\s+at (?:${bodyAndImplicitTags}) \\(<anonymous>\\))[\\s\\S])*?\\n\\s+at ${_boundaryconstants.ROOT_LAYOUT_BOUNDARY_NAME} \\([^\\n]*\\)`);
const hasMetadataRegex = new RegExp(`\\n\\s+at ${_boundaryconstants.METADATA_BOUNDARY_NAME}[\\n\\s]`);
const hasViewportRegex = new RegExp(`\\n\\s+at ${_boundaryconstants.VIEWPORT_BOUNDARY_NAME}[\\n\\s]`);
const hasOutletRegex = new RegExp(`\\n\\s+at ${_boundaryconstants.OUTLET_BOUNDARY_NAME}[\\n\\s]`);
const hasInstantValidationBoundaryRegex = new RegExp(`\\n\\s+at ${_boundaryconstants1.INSTANT_VALIDATION_BOUNDARY_NAME}[\\n\\s]`);
const slotMarkerRegex = new RegExp(`\\n\\s+at ${_boundaryconstants1.INSTANT_SLOT_MARKER_PREFIX}(\\d+)${_boundaryconstants1.INSTANT_SLOT_MARKER_SUFFIX}[\\n\\s]`);
/** Look up the config factory for the slot this error belongs to.
* Checks the component stack for a slot marker (__next_instant_slot_N__)
* and returns the config at that index. Falls back to index 0 (root
* config) when no slot marker is found or the slot has no config. */ function resolveInstantStack(componentStack, dynamicValidation) {
const { slotStacks } = dynamicValidation;
if (slotStacks.length > 1) {
const match = slotMarkerRegex.exec(componentStack);
if (match) {
// Slot markers are 0-indexed in the component name but
// slotStacks is 1-indexed (index 0 is the root config).
const slotIndex = parseInt(match[1], 10) + 1;
const slotStack = slotStacks[slotIndex];
if (slotStack != null) {
return slotStack;
}
}
}
// Fall back to root config (index 0)
return slotStacks[0] ?? null;
}
/**
* Inspects the component stack of an outlet boundary to discover whether the
* user placed a Suspense boundary above the document body, and records the
* opt-in on `dynamicValidation.hasSuspenseAboveBody` if so.
*
* The outlet itself isn't a meaningful source of dynamic — it only resolves
* when metadata/viewport are dynamic, which we track via their own boundaries.
* However, the outlet renders alongside the page content, so its stack passes
* through the user's layout chain (typically reaching into `<body>` via the
* root layout). That makes the outlet stack our best opportunity to spot a
* Suspense boundary above the body, even when no real body content is dynamic.
* Without this, a route whose only dynamic source is `generateViewport()` would
* miss the Suspense-above-body opt-in, because the viewport's stack lives in
* the head and never sees the user's root layout.
*
* We deliberately only set `hasSuspenseAboveBody`, not `hasAllowedDynamic`. The
* latter tracks whether the body has dynamic content that's been wrapped in
* Suspense (i.e., the page is partially dynamic). The outlet rendering tells us
* about the structural opt-in for an empty shell, not about the body being
* partially dynamic. The distinction matters because dynamic metadata is only
* acceptable when the page is partially dynamic (via real body holes), and we
* don't want this outlet-based detection to mask that case.
*/ function trackOutletSuspenseAboveBody(componentStack, dynamicValidation) {
if (hasSuspenseBeforeRootLayoutWithoutBodyOrImplicitBodyRegex.test(componentStack)) {
dynamicValidation.hasSuspenseAboveBody = true;
}
}
function trackAllowedDynamicAccess(dynamicReason, workStore, componentStack, dynamicValidation, clientDynamic) {
const syncDynamicError = getPendingClientSyncDynamicError(clientDynamic);
if (hasOutletRegex.test(componentStack)) {
trackOutletSuspenseAboveBody(componentStack, dynamicValidation);
return;
} else if (hasMetadataRegex.test(componentStack)) {
dynamicValidation.hasDynamicMetadata = true;
return;
} else if (hasViewportRegex.test(componentStack)) {
dynamicValidation.hasDynamicViewport = true;
return;
} else if (hasSuspenseBeforeRootLayoutWithoutBodyOrImplicitBodyRegex.test(componentStack)) {
// For Suspense within body, the prelude wouldn't be empty so it wouldn't violate the empty static shells rule.
// But if you have Suspense above body, the prelude is empty but we allow that because having Suspense
// is an explicit signal from the user that they acknowledge the empty shell and want dynamic rendering.
dynamicValidation.hasAllowedDynamic = true;
dynamicValidation.hasSuspenseAboveBody = true;
return;
} else if (hasSuspenseRegex.test(componentStack)) {
// this error had a Suspense boundary above it so we don't need to report it as a source
// of disallowed
dynamicValidation.hasAllowedDynamic = true;
return;
} else if (syncDynamicError) {
dynamicValidation.dynamicErrors.push(syncDynamicError);
return;
}
if ((0, _dynamicrenderingutils.isClientHookDynamicError)(dynamicReason)) {
dynamicValidation.dynamicErrors.push(addErrorContext(dynamicReason, componentStack, null));
return;
}
const error = addErrorContext((0, _blockingroutemessages.createDynamicOrRuntimeBodyError)(workStore.route), componentStack, null);
dynamicValidation.dynamicErrors.push(error);
return;
}
var DynamicHoleKind = /*#__PURE__*/ function(DynamicHoleKind) {
/** We know that this hole is caused by link data. */ DynamicHoleKind[DynamicHoleKind["Link"] = 1] = "Link";
/** We know that this hole is caused by runtime data. */ DynamicHoleKind[DynamicHoleKind["Runtime"] = 2] = "Runtime";
/** We know that this hole is caused by dynamic data. */ DynamicHoleKind[DynamicHoleKind["Dynamic"] = 3] = "Dynamic";
return DynamicHoleKind;
}({});
function createInstantValidationState(slotStacks) {
return {
hasDynamicMetadata: false,
hasAllowedClientDynamicAboveBoundary: false,
dynamicMetadata: null,
hasDynamicViewport: false,
hasAllowedDynamic: false,
dynamicErrors: [],
validationPreventingErrors: [],
thrownErrorsOutsideBoundary: [],
slotStacks
};
}
function trackDynamicHoleInNavigation(dynamicReason, workStore, componentStack, dynamicValidation, clientDynamic, kind, boundaryState) {
const syncDynamicError = getPendingClientSyncDynamicError(clientDynamic);
if (hasOutletRegex.test(componentStack)) {
// We don't need to track that this is dynamic. It is only so when something else is also dynamic.
return;
}
// Resolve the config stack for this specific error. If the error
// is inside a slot marker, use that slot's config. Otherwise fall
// back to the default.
const effectiveCreateInstantStack = resolveInstantStack(componentStack, dynamicValidation);
if (hasMetadataRegex.test(componentStack)) {
const error = addErrorContext(kind === 1 ? (0, _blockingroutemessages.createLinkMetadataError)(workStore.route) : kind === 2 ? (0, _blockingroutemessages.createRuntimeMetadataError)(workStore.route) : (0, _blockingroutemessages.createDynamicMetadataError)(workStore.route), componentStack, effectiveCreateInstantStack);
dynamicValidation.dynamicMetadata = error;
return;
}
if (hasViewportRegex.test(componentStack)) {
const error = addErrorContext(kind === 1 ? (0, _blockingroutemessages.createLinkViewportError)(workStore.route) : kind === 2 ? (0, _blockingroutemessages.createRuntimeViewportError)(workStore.route) : (0, _blockingroutemessages.createDynamicViewportError)(workStore.route), componentStack, effectiveCreateInstantStack);
dynamicValidation.dynamicErrors.push(error);
return;
}
const boundaryLocation = hasInstantValidationBoundaryRegex.exec(componentStack);
if (!boundaryLocation) {
// We don't see the validation boundary in the component stack,
// so this hole must be coming from a shared parent.
// Shared parents are fully resolved and don't have RSC holes,
// but they can still suspend in a client component during SSR.
// If we managed to render all the validation boundaries, that means
// that the client holes aren't blocking validation and we can disregard them.
// Note that we don't even care whether they have suspense or not.
if ((0, _boundarytracking.allRequiredBoundariesRendered)(boundaryState)) {
dynamicValidation.hasAllowedClientDynamicAboveBoundary = true;
dynamicValidation.hasAllowedDynamic = true // Holes outside the boundary contribute to allowing dynamic metadata
;
return;
} else {
// TODO(instant-validation) TODO(NAR-787)
// If shared parents blocked us from validating, we should only log
// the errors from the innermost (segments), i.e. omit layouts whose
// slots managed to render (because clearly they didn't block validation)
const message = `Route "${workStore.route}": Could not validate \`instant\` because a Client Component in a parent segment prevented the page from rendering.`;
const error = addErrorContext(Object.defineProperty(new Error(message), "__NEXT_ERROR_CODE", {
value: "E1331",
enumerable: false,
configurable: true
}), componentStack, effectiveCreateInstantStack);
dynamicValidation.validationPreventingErrors.push(error);
return;
}
} else {
// The hole originates inside the validation boundary.
//
// Check if we have a Suspense above the hole, but below the validation boundary.
// If we do, then this dynamic usage wouldn't block a navigation to this subtree.
// Conversely, if the nearest suspense is above the validation boundary, then this subtree would block.
//
// Note that in the component stack, children come before parents.
//
// Valid:
// ...
// at Suspense
// ...
// at __next_prefetch_validation_boundary__
//
// Invalid:
// ...
// at __next_prefetch_validation_boundary__
// ...
// at Suspense
//
const suspenseLocation = hasSuspenseRegex.exec(componentStack);
if (suspenseLocation) {
if (suspenseLocation.index < boundaryLocation.index) {
dynamicValidation.hasAllowedDynamic = true;
return;
} else {
// invalid - fallthrough
}
}
}
if (syncDynamicError) {
if (effectiveCreateInstantStack !== null && syncDynamicError.cause === undefined) {
syncDynamicError.cause = effectiveCreateInstantStack();
}
dynamicValidation.dynamicErrors.push(syncDynamicError);
return;
}
if ((0, _dynamicrenderingutils.isClientHookDynamicError)(dynamicReason)) {
dynamicValidation.dynamicErrors.push(addErrorContext(dynamicReason, componentStack, effectiveCreateInstantStack));
return;
}
const error = addErrorContext(kind === 1 ? (0, _blockingroutemessages.createLinkBodyErrorInNavigation)(workStore.route) : kind === 2 ? (0, _blockingroutemessages.createRuntimeBodyErrorInNavigation)(workStore.route) : (0, _blockingroutemessages.createDynamicBodyErrorInNavigation)(workStore.route), componentStack, effectiveCreateInstantStack);
dynamicValidation.dynamicErrors.push(error);
return;
}
function trackThrownErrorInNavigation(workStore, dynamicValidation, thrownValue, componentStack) {
const boundaryLocation = hasInstantValidationBoundaryRegex.exec(componentStack);
if (!boundaryLocation) {
// There's no validation boundary on the component stack.
// This error may have blocked a boundary from rendering.
// Wrap the error to provide component context.
// This helps for errors from node_modules which would otherwise
// have no useful stack information due to ignore-listing,
// e.g. next/dynamic with `ssr: false`.
const error = addErrorContext(Object.defineProperty(new Error('An error occurred while attempting to validate instant UI. This error may be preventing the validation from completing.', {
cause: thrownValue
}), "__NEXT_ERROR_CODE", {
value: "E1118",
enumerable: false,
configurable: true
}), componentStack, null);
dynamicValidation.thrownErrorsOutsideBoundary.push(error);
} else {
// There's validation boundary on the component stack,
// so we know this error didn't block a validation boundary from rendering.
// However, this error might be hiding be hiding dynamic content that would
// cause validation to fail.
const suspenseLocation = hasSuspenseRegex.exec(componentStack);
if (suspenseLocation) {
if (suspenseLocation.index < boundaryLocation.index) {
// There's a Suspense below the validation boundary but above this error's location.
// This subtree can't fail instant validation because any potential
// dynamic holes would be guarded by the Suspense anyway,
// so we can allow this.
return;
} else {
// invalid - fallthrough
}
}
const message = `Route "${workStore.route}": Could not validate \`instant\` because an error prevented the target segment from rendering.`;
const error = addErrorContext(Object.defineProperty(new Error(message, {
cause: thrownValue
}), "__NEXT_ERROR_CODE", {
value: "E1338",
enumerable: false,
configurable: true
}), componentStack, null // TODO(instant-validation-build): conflicting use of cause
);
dynamicValidation.validationPreventingErrors.push(error);
}
}
function trackDynamicHoleInRuntimeShell(dynamicReason, workStore, componentStack, dynamicValidation, clientDynamic) {
const syncDynamicError = getPendingClientSyncDynamicError(clientDynamic);
if (hasOutletRegex.test(componentStack)) {
trackOutletSuspenseAboveBody(componentStack, dynamicValidation);
return;
} else if (hasMetadataRegex.test(componentStack)) {
const error = addErrorContext((0, _blockingroutemessages.createDynamicMetadataError)(workStore.route), componentStack, null);
dynamicValidation.dynamicMetadata = error;
return;
} else if (hasViewportRegex.test(componentStack)) {
const error = addErrorContext((0, _blockingroutemessages.createDynamicViewportError)(workStore.route), componentStack, null);
dynamicValidation.dynamicErrors.push(error);
return;
} else if (hasSuspenseBeforeRootLayoutWithoutBodyOrImplicitBodyRegex.test(componentStack)) {
// For Suspense within body, the prelude wouldn't be empty so it wouldn't violate the empty static shells rule.
// But if you have Suspense above body, the prelude is empty but we allow that because having Suspense
// is an explicit signal from the user that they acknowledge the empty shell and want dynamic rendering.
dynamicValidation.hasAllowedDynamic = true;
dynamicValidation.hasSuspenseAboveBody = true;
return;
} else if (hasSuspenseRegex.test(componentStack)) {
// this error had a Suspense boundary above it so we don't need to report it as a source
// of disallowed
dynamicValidation.hasAllowedDynamic = true;
return;
} else if (syncDynamicError) {
dynamicValidation.dynamicErrors.push(syncDynamicError);
return;
}
if ((0, _dynamicrenderingutils.isClientHookDynamicError)(dynamicReason)) {
dynamicValidation.dynamicErrors.push(addErrorContext(dynamicReason, componentStack, null));
return;
}
const error = addErrorContext((0, _blockingroutemessages.createDynamicBodyError)(workStore.route), componentStack, null);
dynamicValidation.dynamicErrors.push(error);
return;
}
function trackDynamicHoleInStaticShell(dynamicReason, workStore, componentStack, dynamicValidation, clientDynamic) {
const syncDynamicError = getPendingClientSyncDynamicError(clientDynamic);
if (hasOutletRegex.test(componentStack)) {
trackOutletSuspenseAboveBody(componentStack, dynamicValidation);
return;
} else if (hasMetadataRegex.test(componentStack)) {
const error = addErrorContext((0, _blockingroutemessages.createRuntimeMetadataError)(workStore.route), componentStack, null);
dynamicValidation.dynamicMetadata = error;
return;
} else if (hasViewportRegex.test(componentStack)) {
const error = addErrorContext((0, _blockingroutemessages.createRuntimeViewportError)(workStore.route), componentStack, null);
dynamicValidation.dynamicErrors.push(error);
return;
} else if (hasSuspenseBeforeRootLayoutWithoutBodyOrImplicitBodyRegex.test(componentStack)) {
// For Suspense within body, the prelude wouldn't be empty so it wouldn't violate the empty static shells rule.
// But if you have Suspense above body, the prelude is empty but we allow that because having Suspense
// is an explicit signal from the user that they acknowledge the empty shell and want dynamic rendering.
dynamicValidation.hasAllowedDynamic = true;
dynamicValidation.hasSuspenseAboveBody = true;
return;
} else if (hasSuspenseRegex.test(componentStack)) {
// this error had a Suspense boundary above it so we don't need to report it as a source
// of disallowed
dynamicValidation.hasAllowedDynamic = true;
return;
} else if (syncDynamicError) {
dynamicValidation.dynamicErrors.push(syncDynamicError);
return;
}
if ((0, _dynamicrenderingutils.isClientHookDynamicError)(dynamicReason)) {
dynamicValidation.dynamicErrors.push(addErrorContext(dynamicReason, componentStack, null));
return;
}
const error = addErrorContext((0, _blockingroutemessages.createRuntimeBodyError)(workStore.route), componentStack, null);
dynamicValidation.dynamicErrors.push(error);
return;
}
/**
* In dev mode, we prefer using the owner stack, otherwise the provided
* component stack is used.
*
* Accepts an already-created Error so the SWC error-code plugin can see the
* `new Error(...)` call at each call site and auto-assign error codes.
*/ function addErrorContext(error, componentStack, createInstantStack) {
const ownerStack = process.env.NODE_ENV !== 'production' && _react.default.captureOwnerStack ? _react.default.captureOwnerStack() : null;
if (createInstantStack !== null) {
error.cause = createInstantStack();
}
// TODO go back to owner stack here if available. This is temporarily using componentStack to get the right
//
error.stack = error.name + ': ' + error.message + (ownerStack || componentStack);
return error;
}
var PreludeState = /*#__PURE__*/ function(PreludeState) {
PreludeState[PreludeState["Full"] = 0] = "Full";
PreludeState[PreludeState["Empty"] = 1] = "Empty";
PreludeState[Pre