next
Version:
The React Framework
814 lines (813 loc) • 41.4 kB
JavaScript
// Render validation is kept separate from the tracking utilities used by server bootstrap.
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
0 && (module.exports = {
DynamicHoleKind: null,
PreludeState: null,
createDynamicValidationState: null,
createInstantValidationState: null,
getDisallowedReasonsInStaticRoute: null,
getNavigationDisallowedDynamicReasons: null,
getStaticShellDisallowedDynamicReasons: null,
logDisallowedDynamicError: null,
throwIfDisallowedDynamic: null,
throwIfDisallowedDynamicInStaticRoute: null,
throwIfSyncIOUsed: null,
trackAllowedDynamicAccess: null,
trackDynamicAccessInStaticRoute: null,
trackDynamicHoleInNavigation: null,
trackDynamicHoleInStaticShell: null,
trackThrownErrorInNavigation: 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;
},
PreludeState: function() {
return PreludeState;
},
createDynamicValidationState: function() {
return createDynamicValidationState;
},
createInstantValidationState: function() {
return createInstantValidationState;
},
getDisallowedReasonsInStaticRoute: function() {
return getDisallowedReasonsInStaticRoute;
},
getNavigationDisallowedDynamicReasons: function() {
return getNavigationDisallowedDynamicReasons;
},
getStaticShellDisallowedDynamicReasons: function() {
return getStaticShellDisallowedDynamicReasons;
},
logDisallowedDynamicError: function() {
return logDisallowedDynamicError;
},
throwIfDisallowedDynamic: function() {
return throwIfDisallowedDynamic;
},
throwIfDisallowedDynamicInStaticRoute: function() {
return throwIfDisallowedDynamicInStaticRoute;
},
throwIfSyncIOUsed: function() {
return throwIfSyncIOUsed;
},
trackAllowedDynamicAccess: function() {
return trackAllowedDynamicAccess;
},
trackDynamicAccessInStaticRoute: function() {
return trackDynamicAccessInStaticRoute;
},
trackDynamicHoleInNavigation: function() {
return trackDynamicHoleInNavigation;
},
trackDynamicHoleInStaticShell: function() {
return trackDynamicHoleInStaticShell;
},
trackThrownErrorInNavigation: function() {
return trackThrownErrorInNavigation;
}
});
const _staticgenerationbailout = require("../../client/components/static-generation-bailout");
const _dynamicrenderingutils = require("../dynamic-rendering-utils");
const _boundaryconstants = require("../../lib/framework/boundary-constants");
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");
const _reactbrowserbailout = require("../../shared/lib/lazy-dynamic/react-browser-bailout");
function createDynamicValidationState() {
return {
hasSuspenseAboveBody: false,
hasDynamicMetadata: false,
dynamicMetadata: null,
hasDynamicViewport: false,
hasAllowedDynamic: false,
dynamicErrors: []
};
}
function getPendingClientSyncDynamicError(clientDynamic) {
return clientDynamic.syncDynamicErrorWithStackPostMicrotask ? null : clientDynamic.syncDynamicErrorWithStack;
}
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 trackDynamicAccessInStaticRoute(dynamicReason, workStore, componentStack, dynamicValidation, clientDynamic, isServerPartial, kind) {
const syncDynamicError = getPendingClientSyncDynamicError(clientDynamic);
if (hasOutletRegex.test(componentStack)) {
trackOutletSuspenseAboveBody(componentStack, dynamicValidation);
return;
}
if (hasMetadataRegex.test(componentStack)) {
dynamicValidation.dynamicErrors.push(addErrorContext(createMetadataErrorInStaticRoute(kind, workStore.route), componentStack, null));
return;
}
if (hasViewportRegex.test(componentStack)) {
dynamicValidation.dynamicErrors.push(addErrorContext(createViewportErrorInStaticRoute(kind, workStore.route), componentStack, null));
return;
}
let hasSuspense = false;
if (hasSuspenseBeforeRootLayoutWithoutBodyOrImplicitBodyRegex.test(componentStack)) {
// this error had a Suspense boundary above it, and it's above the body as well.
dynamicValidation.hasSuspenseAboveBody = true;
hasSuspense = true;
} else if (hasSuspenseRegex.test(componentStack)) {
// this error had a Suspense boundary above it.
hasSuspense = true;
}
if (hasSuspense) {
if (!isServerPartial) {
// If we don't have holes caused by server data, then any holes must be caused
// by client-only code, which is allowed.
dynamicValidation.hasAllowedDynamic = true;
return;
}
// We have holes that may be caused by server data.
if (syncDynamicError || (0, _dynamicrenderingutils.isClientHookDynamicError)(dynamicReason)) {
// We know this hole wasn't caused by server data, so it's allowed
// (because it's wrapped in a Suspense)
dynamicValidation.hasAllowedDynamic = true;
return;
}
// Any remaining hole may be caused by a server hole.
// These are not allowed even with Suspense.
// (NOTE: this may misreport valid client dynamic holes as server holes)
dynamicValidation.dynamicErrors.push(addErrorContext(createBodyErrorInStaticRoute(kind, workStore.route), componentStack, null));
return;
}
if (syncDynamicError) {
dynamicValidation.dynamicErrors.push(syncDynamicError);
return;
}
if ((0, _dynamicrenderingutils.isClientHookDynamicError)(dynamicReason)) {
dynamicValidation.dynamicErrors.push(addErrorContext(dynamicReason, componentStack, null));
return;
}
const error = addErrorContext(isServerPartial ? // (NOTE: this may misreport client dynamic holes as server holes)
createBodyErrorInStaticRoute(kind, workStore.route) : // that server data is complete
createBodyError(kind, workStore.route), componentStack, null);
dynamicValidation.dynamicErrors.push(error);
}
function trackAllowedDynamicAccess(dynamicReason, workStore, componentStack, dynamicValidation, clientDynamic) {
const dynamicHoleKind = 6;
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(createBodyError(dynamicHoleKind, workStore.route), componentStack, null);
dynamicValidation.dynamicErrors.push(error);
return;
}
var DynamicHoleKind = /*#__PURE__*/ function(DynamicHoleKind) {
/** We know that this hole is caused by runtime data. */ DynamicHoleKind[DynamicHoleKind["Runtime"] = 1] = "Runtime";
/** We know that this hole is caused by link data. */ DynamicHoleKind[DynamicHoleKind["Link"] = 2] = "Link";
/** We know that this hole is caused by navigation(). */ DynamicHoleKind[DynamicHoleKind["Navigation"] = 3] = "Navigation";
/** We know that this hole is caused by prefetch(). */ DynamicHoleKind[DynamicHoleKind["Prefetch"] = 4] = "Prefetch";
/** We know that this hole is caused by dynamic data. */ DynamicHoleKind[DynamicHoleKind["Dynamic"] = 5] = "Dynamic";
/** We know that this hole is caused by runtime or dynamic data, but don't know which. */ DynamicHoleKind[DynamicHoleKind["RuntimeOrDynamic"] = 6] = "RuntimeOrDynamic";
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(createMetadataError(kind, workStore.route), componentStack, effectiveCreateInstantStack);
dynamicValidation.dynamicMetadata = error;
return;
}
if (hasViewportRegex.test(componentStack)) {
const error = addErrorContext(createViewportError(kind, 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(new Error(message), 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(createBodyErrorInNavigation(kind, workStore.route), componentStack, effectiveCreateInstantStack);
dynamicValidation.dynamicErrors.push(error);
return;
}
function createBodyError(kind, route) {
switch(kind){
case 1:
return (0, _blockingroutemessages.createRuntimeBodyError)(route);
case 5:
return (0, _blockingroutemessages.createDynamicBodyError)(route);
case 6:
return (0, _blockingroutemessages.createDynamicOrRuntimeBodyError)(route);
}
}
function createBodyErrorInNavigation(kind, route) {
switch(kind){
case 1:
return (0, _blockingroutemessages.createRuntimeBodyErrorInNavigation)(route);
case 2:
return (0, _blockingroutemessages.createLinkBodyErrorInNavigation)(route);
case 3:
return (0, _blockingroutemessages.createNavigationBodyErrorInNavigation)(route);
case 4:
return (0, _blockingroutemessages.createPrefetchBodyErrorInNavigation)(route);
case 5:
return (0, _blockingroutemessages.createDynamicBodyErrorInNavigation)(route);
}
}
function createMetadataError(kind, route) {
switch(kind){
case 1:
return (0, _blockingroutemessages.createRuntimeMetadataError)(route);
case 2:
return (0, _blockingroutemessages.createLinkMetadataError)(route);
case 3:
return (0, _blockingroutemessages.createNavigationMetadataError)(route);
case 4:
return (0, _blockingroutemessages.createPrefetchMetadataError)(route);
case 5:
return (0, _blockingroutemessages.createDynamicMetadataError)(route);
case 6:
return (0, _blockingroutemessages.createDynamicOrRuntimeMetadataError)(route);
}
}
function createViewportError(kind, route) {
switch(kind){
case 1:
return (0, _blockingroutemessages.createRuntimeViewportError)(route);
case 2:
return (0, _blockingroutemessages.createLinkViewportError)(route);
case 3:
return (0, _blockingroutemessages.createNavigationViewportError)(route);
case 4:
return (0, _blockingroutemessages.createPrefetchViewportError)(route);
case 5:
return (0, _blockingroutemessages.createDynamicViewportError)(route);
case 6:
return (0, _blockingroutemessages.createDynamicOrRuntimeViewportError)(route);
}
}
function createBodyErrorInStaticRoute(kind, route) {
switch(kind){
case 1:
return (0, _blockingroutemessages.createRuntimeBodyErrorInStaticRoute)(route);
case 5:
return (0, _blockingroutemessages.createDynamicBodyErrorInStaticRoute)(route);
case 6:
return (0, _blockingroutemessages.createNonPrerenderableBodyErrorInStaticRoute)(route);
}
}
function createMetadataErrorInStaticRoute(kind, route) {
switch(kind){
case 1:
return (0, _blockingroutemessages.createRuntimeMetadataErrorInStaticRoute)(route);
case 5:
return (0, _blockingroutemessages.createDynamicMetadataErrorInStaticRoute)(route);
case 6:
return (0, _blockingroutemessages.createNonPrerenderableMetadataErrorInStaticRoute)(route);
}
}
function createViewportErrorInStaticRoute(kind, route) {
switch(kind){
case 1:
return (0, _blockingroutemessages.createRuntimeViewportErrorInStaticRoute)(route);
case 5:
return (0, _blockingroutemessages.createDynamicViewportErrorInStaticRoute)(route);
case 6:
return (0, _blockingroutemessages.createNonPrerenderableViewportErrorInStaticRoute)(route);
}
}
function trackThrownErrorInNavigation(workStore, dynamicValidation, thrownValue, componentStack, reactBrowserBailout) {
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`.
if (reactBrowserBailout) {
// React preserves Next's branded bailout reason as the error cause.
// Replace the internal wrapper before storing the user-facing diagnostic.
const browserBailoutReason = (0, _reactbrowserbailout.getReactBrowserBailoutReason)(thrownValue);
if (browserBailoutReason !== undefined) {
const browserBailoutError = thrownValue;
browserBailoutError.cause = browserBailoutReason;
}
}
const error = addErrorContext(new Error('An error occurred while attempting to validate instant UI. This error may be preventing the validation from completing.', {
cause: thrownValue
}), 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
}
}
if (reactBrowserBailout) {
// React preserves Next's branded bailout reason as the error cause.
// Replace the internal wrapper before storing the user-facing diagnostic.
const browserBailoutReason = (0, _reactbrowserbailout.getReactBrowserBailoutReason)(thrownValue);
if (browserBailoutReason !== undefined) {
const browserBailoutError = thrownValue;
browserBailoutError.cause = browserBailoutReason;
}
}
const message = `Route "${workStore.route}": Could not validate \`instant\` because an error prevented the target segment from rendering.`;
const error = addErrorContext(new Error(message, {
cause: thrownValue
}), componentStack, null // TODO(instant-validation-build): conflicting use of cause
);
dynamicValidation.validationPreventingErrors.push(error);
}
}
function trackDynamicHoleInStaticShell(dynamicReason, workStore, componentStack, dynamicValidation, clientDynamic, kind) {
const syncDynamicError = getPendingClientSyncDynamicError(clientDynamic);
if (hasOutletRegex.test(componentStack)) {
trackOutletSuspenseAboveBody(componentStack, dynamicValidation);
return;
} else if (hasMetadataRegex.test(componentStack)) {
const error = addErrorContext(createMetadataError(kind, workStore.route), componentStack, null);
dynamicValidation.dynamicMetadata = error;
return;
} else if (hasViewportRegex.test(componentStack)) {
const error = addErrorContext(createViewportError(kind, 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(kind === 1 ? (0, _blockingroutemessages.createRuntimeBodyError)(workStore.route) : (0, _blockingroutemessages.createDynamicBodyError)(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.
*/ function addErrorContext(error, componentStack, createInstantStack) {
let ownerStack = null;
if (process.env.NODE_ENV !== 'production') {
const React = require('react');
ownerStack = React.captureOwnerStack ? React.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[PreludeState["Errored"] = 2] = "Errored";
return PreludeState;
}({});
function logDisallowedDynamicError(workStore, error) {
console.error(error);
(0, _blockingroutemessages.logBuildDebugHint)(workStore.route);
}
function throwIfSyncIOUsed(workStore, serverDynamic) {
if (serverDynamic.syncDynamicErrorWithStack) {
logDisallowedDynamicError(workStore, serverDynamic.syncDynamicErrorWithStack);
throw new _staticgenerationbailout.StaticGenBailoutError();
}
}
function throwIfDisallowedDynamic(workStore, prelude, dynamicValidation, serverDynamic, allowEmptyStaticShell) {
const dynamicHoleKind = 6;
throwIfSyncIOUsed(workStore, serverDynamic);
// The dynamic metadata error is a mistake-detection signal. It fires when the
// rest of the shell is otherwise fully static apart from metadata, suggesting
// the dynamic data access in `generateMetadata` was probably unintentional.
// That condition is independent of whether the user or build phase accepted
// an empty shell, so we surface it before any opt-in bypass.
if (prelude === 0 && dynamicValidation.hasAllowedDynamic === false && dynamicValidation.hasDynamicMetadata) {
console.error(createMetadataError(dynamicHoleKind, workStore.route).message);
throw new _staticgenerationbailout.StaticGenBailoutError();
}
// Either flag expresses "this shell is allowed to be empty/blocking":
// - `allowEmptyStaticShell` covers `instant = false` (user opt-in)
// and the build-phase fallback-shell case.
// - `hasSuspenseAboveBody` is the structural opt-in inside the user's root
// layout.
// Treat them as synonyms for the purpose of bypassing shell-failure errors.
if (allowEmptyStaticShell || dynamicValidation.hasSuspenseAboveBody) {
return;
}
if (prelude !== 0) {
// We didn't have any sync bailouts but there may be user code which
// blocked the root. We would have captured these during the prerender
// and can log them here and then terminate the build/validating render
const dynamicErrors = dynamicValidation.dynamicErrors;
if (dynamicErrors.length > 0) {
for(let i = 0; i < dynamicErrors.length; i++){
logDisallowedDynamicError(workStore, dynamicErrors[i]);
}
throw new _staticgenerationbailout.StaticGenBailoutError();
}
// If we got this far then the only other thing that could be blocking
// the root is dynamic Viewport. If this is dynamic then
// you need to opt into that by adding a Suspense boundary above the body
// to indicate your are ok with fully dynamic rendering.
if (dynamicValidation.hasDynamicViewport) {
console.error(createViewportError(dynamicHoleKind, workStore.route).message);
throw new _staticgenerationbailout.StaticGenBailoutError();
}
if (prelude === 1) {
// If we ever get this far then we messed up the tracking of invalid dynamic.
// We still adhere to the constraint that you must produce a shell but invite the
// user to report this as a bug in Next.js.
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 _staticgenerationbailout.StaticGenBailoutError();
}
}
}
function throwIfDisallowedDynamicInStaticRoute(workStore, prelude, dynamicValidation, serverDynamic, isFallbackShell, allowEmptyStaticShell, isServerPartial) {
const reasons = getDisallowedReasonsInStaticRoute(workStore, prelude, dynamicValidation, serverDynamic, isFallbackShell, allowEmptyStaticShell, isServerPartial);
if (reasons.length > 0) {
for (const reason of reasons){
logDisallowedDynamicError(workStore, reason);
}
throw new _staticgenerationbailout.StaticGenBailoutError();
}
}
function getDisallowedReasonsInStaticRoute(workStore, prelude, dynamicValidation, serverDynamic, isFallbackShell, allowEmptyStaticShell, isServerPartial) {
if (serverDynamic && serverDynamic.syncDynamicErrorWithStack) {
return [
serverDynamic.syncDynamicErrorWithStack
];
}
// We do a fallback shell prerender, but it's not meant to be validated --
// we just need it to create the relevant prerender outputs.
if (isFallbackShell) {
return [];
}
// Suspense-above-body and `instant = false` bypass reporting
// that the shell is blocked. However, if we have non-prerenderable server
// data, we still have to error, regardless of those two.
if (!isServerPartial && (allowEmptyStaticShell || dynamicValidation.hasSuspenseAboveBody)) {
return [];
}
const { dynamicErrors } = dynamicValidation;
if (dynamicErrors.length > 0) {
return dynamicErrors;
}
// Dynamic data may have been passed into a client component without calling `use()`,
// in which case we wouldn't see it during the prerender, but still need to error.
// Fall back to a generic error message.
if (isServerPartial) {
return [
new Error(`Route "${workStore.route}": Next.js encountered data that is not available during a static prerender, but is unable to provide a location.`)
];
}
if (prelude === 1 || prelude === 2) {
// We've somehow ended up with an empty prelude but no dynamic errors.
// We must've messed up the tracking somehow.
return [
new _invarianterror.InvariantError(`Route "${workStore.route}" did not produce a static shell and Next.js was unable to determine a reason.`)
];
}
return [];
}
function getStaticShellDisallowedDynamicReasons(workStore, prelude, dynamicValidation, allowEmptyStaticShell) {
// The dynamic metadata error is a mistake-detection signal. It fires when the
// rest of the shell is otherwise fully static apart from metadata, suggesting
// the dynamic data access in `generateMetadata` was probably unintentional.
// That condition is independent of whether the user or build phase accepted
// an empty shell, so we surface it before any opt-in bypass.
if (prelude === 0 && dynamicValidation.hasAllowedDynamic === false && dynamicValidation.dynamicErrors.length === 0 && dynamicValidation.dynamicMetadata) {
return [
dynamicValidation.dynamicMetadata
];
}
// Either flag expresses "this shell is allowed to be empty/blocking":
// - `allowEmptyStaticShell` covers `instant = false` (user opt-in)
// and the build-phase fallback-shell case.
// - `hasSuspenseAboveBody` is the structural opt-in inside the user's root
// layout.
// Treat them as synonyms for the purpose of bypassing shell-failure errors.
if (allowEmptyStaticShell || dynamicValidation.hasSuspenseAboveBody) {
return [];
}
if (prelude !== 0) {
// We didn't have any sync bailouts but there may be user code which
// blocked the root. We would have captured these during the prerender
// and can log them here and then terminate the build/validating render
const dynamicErrors = dynamicValidation.dynamicErrors;
if (dynamicErrors.length > 0) {
return dynamicErrors;
}
if (prelude === 1) {
// If we ever get this far then we messed up the tracking of invalid dynamic.
// We still adhere to the constraint that you must produce a shell but invite the
// user to report this as a bug in Next.js.
return [
new _invarianterror.InvariantError(`Route "${workStore.route}" did not produce a static shell and Next.js was unable to determine a reason.`)
];
}
}
// We had a non-empty prelude and there are no dynamic holes
return [];
}
function getNavigationDisallowedDynamicReasons(workStore, prelude, dynamicValidation, validationSampleTracking, boundaryState, devRenderDidError) {
// If we have errors related to missing samples, those should take precedence over everything else.
if (validationSampleTracking) {
const { missingSampleErrors } = validationSampleTracking;
if (missingSampleErrors.length > 0) {
return missingSampleErrors;
}
}
const { validationPreventingErrors } = dynamicValidation;
if (validationPreventingErrors.length > 0) {
if (process.env.__NEXT_DEV_SERVER && devRenderDidError) {
// The dev render already surfaced server errors to the user.
// The same errors likely caused validation to be inconclusive,
// so reporting them again as validation failures would be noisy.
return [];
}
return validationPreventingErrors;
}
// NOTE: We don't care about Suspense above body here,
// we're only concerned with the validation boundary
if (prelude !== 0) {
const dynamicErrors = dynamicValidation.dynamicErrors;
if (dynamicErrors.length > 0) {
return dynamicErrors;
}
if (prelude === 1 && !dynamicValidation.hasAllowedClientDynamicAboveBoundary && (0, _boundarytracking.allRequiredBoundariesRendered)(boundaryState)) {
// If we ever get this far then we messed up the tracking of invalid
// dynamic. (When boundaries are missing the deferred fallback below
// will surface a more useful error.)
return new _invarianterror.InvariantError(`Route "${workStore.route}" failed to render during instant validation and Next.js was unable to determine a reason.`);
}
} else {
const dynamicErrors = dynamicValidation.dynamicErrors;
if (dynamicErrors.length > 0) {
return dynamicErrors;
}
if (dynamicValidation.hasAllowedDynamic === false && dynamicValidation.dynamicMetadata) {
return [
dynamicValidation.dynamicMetadata
];
}
}
// Missing boundaries on their own aren't a strong signal — a parent
// layout may legitimately omit a slot. Defer this so the caller can
// try shallower validation depths first; if every depth comes up
// empty we still want to surface this so the user is made aware that
// validation didn't complete. When we add a markers API, the
// marker-based variant of this check can become strict again.
if (!(0, _boundarytracking.allRequiredBoundariesRendered)(boundaryState)) {
const { thrownErrorsOutsideBoundary } = dynamicValidation;
const rootInstantStack = dynamicValidation.slotStacks[0];
if (thrownErrorsOutsideBoundary.length === 0) {
const missingFiles = [];
for (const [id, filePaths] of boundaryState.requiredIds){
if (!boundaryState.renderedIds.has(id)) {
for (const filePath of filePaths){
let normalized = filePath.replace(/^\[project\][\\/]?/, '').replace(process.cwd() + '/', '').replace(process.cwd() + '\\', '');
missingFiles.push(normalized);
}
}
}
missingFiles.sort();
return (0, _instantmessages.createUnrenderedSegmentError)(workStore.route, missingFiles);
} else if (process.env.__NEXT_DEV_SERVER && devRenderDidError) {
// Errors outside the boundary likely blocked it from rendering,
// but they're already being reported to the user via the dev
// render. Suppress the validation failure to avoid noise.
return [];
} else if (thrownErrorsOutsideBoundary.length === 1) {
const message = `Route "${workStore.route}": Could not validate \`instant\` because the target segment was prevented from rendering, likely due to the following error.`;
const error = rootInstantStack !== null ? rootInstantStack() : new Error();
error.name = 'Error';
error.message = message;
return new AggregateError([
error,
thrownErrorsOutsideBoundary[0]
]);
} else {
const message = `Route "${workStore.route}": Could not validate \`instant\` because the target segment was prevented from rendering, likely due to one of the following errors.`;
const error = rootInstantStack !== null ? rootInstantStack() : new Error();
error.name = 'Error';
error.message = message;
return new AggregateError([
error,
...thrownErrorsOutsideBoundary
]);
}
}
// We had a non-empty prelude and there are no dynamic holes
return [];
}
//# sourceMappingURL=dynamic-rendering-validation.js.map