next
Version:
The React Framework
879 lines • 242 kB
JavaScript
import { jsx as _jsx } from "react/jsx-runtime";
import { workAsyncStorage } from '../app-render/work-async-storage.external';
import * as ReactClient from 'react';
import RenderResult from '../render-result';
import { chainStreams, continueFizzStream, continueDynamicPrerender, continueStaticPrerender, continueDynamicHTMLResume, continueStaticFallbackPrerender, streamToBuffer, streamToString, createInlinedDataStream, createPendingStream, createOnHeadersCallback, resumeAndAbort, renderToFlightStream, renderToFizzStream, resumeToFizzStream, getServerPrerender, getClientPrerender, processPrelude as processPreludeOp, createDocumentClosingStream } from './stream-ops';
import { stripInternalQueries } from '../internal-utils';
import { NEXT_HMR_REFRESH_HEADER, NEXT_ROUTER_PREFETCH_HEADER, NEXT_ROUTER_STATE_TREE_HEADER, NEXT_ROUTER_STALE_TIME_HEADER, NEXT_URL, RSC_HEADER, NEXT_ROUTER_SEGMENT_PREFETCH_HEADER, NEXT_REQUEST_ID_HEADER, NEXT_HTML_REQUEST_ID_HEADER } from '../../client/components/app-router-headers';
import { createMetadataContext } from '../../lib/metadata/metadata-context';
import { createRequestStoreForRender } from '../async-storage/request-store';
import { createWorkStore } from '../async-storage/work-store';
import { getAccessFallbackErrorTypeByStatus, getAccessFallbackHTTPStatus, isHTTPAccessFallbackError } from '../../client/components/http-access-fallback/http-access-fallback';
import { getURLFromRedirectError, getRedirectStatusCodeFromError } from '../../client/components/redirect';
import { isRedirectError } from '../../client/components/redirect-error';
import { getImplicitTags } from '../lib/implicit-tags';
import { AppRenderSpan, NextNodeServerSpan } from '../lib/trace/constants';
import { getTracer, SpanStatusCode } from '../lib/trace/tracer';
import { FlightRenderResult } from './flight-render-result';
import { createReactServerErrorHandler, createHTMLErrorHandler, isUserLandError, getDigestForWellKnownError } from './create-error-handler';
import { dynamicParamTypes } from './get-short-dynamic-param-type';
import { getSegmentParam } from '../../shared/lib/router/utils/get-segment-param';
import { getScriptNonceFromHeader } from './get-script-nonce-from-header';
import { parseAndValidateFlightRouterState } from './parse-and-validate-flight-router-state';
import { createFlightRouterStateFromLoaderTree } from './create-flight-router-state-from-loader-tree';
import { handleAction } from './action-handler';
import { isBailoutToCSRError } from '../../shared/lib/lazy-dynamic/bailout-to-csr';
import { warn, error } from '../../build/output/log';
import { appendMutableCookies } from '../web/spec-extension/adapters/request-cookies';
import { createServerInsertedHTML } from './server-inserted-html';
import { getRequiredScripts } from './required-scripts';
import { addPathPrefix } from '../../shared/lib/router/utils/add-path-prefix';
import { makeGetServerInsertedHTML } from './make-get-server-inserted-html';
import { walkTreeWithFlightRouterState, createFullTreeFlightDataForNavigation } from './walk-tree-with-flight-router-state';
import { createComponentTree, getRootParams } from './create-component-tree';
import { getAssetQueryString } from './get-asset-query-string';
import { getClientReferenceManifest, getServerModuleMap } from './manifests-singleton';
import { DynamicState, DynamicHTMLPreludeState, parsePostponedState } from './postponed-state';
import { getDynamicDataPostponedState, getDynamicHTMLPostponedState, getPostponedFromState } from './postponed-state';
import { isDynamicServerError } from '../../client/components/hooks-server-context';
import { getFlightStream } from './use-flight-response';
import { StaticGenBailoutError, isStaticGenBailoutError } from '../../client/components/static-generation-bailout';
import { getStackWithoutErrorMessage } from '../../lib/format-server-error';
import { extractNextErrorCode } from '../../lib/error-telemetry-utils';
import { accessedDynamicData, createRenderInBrowserAbortSignal, formatDynamicAPIAccesses, isPrerenderInterruptedError, createDynamicTrackingState, createDynamicValidationState, trackAllowedDynamicAccess, throwIfDisallowedDynamic, PreludeState, consumeDynamicAccess, logDisallowedDynamicError, trackDynamicHoleInRuntimeShell, trackDynamicHoleInStaticShell, getStaticShellDisallowedDynamicReasons, getNavigationDisallowedDynamicReasons, trackDynamicHoleInNavigation, DynamicHoleKind, trackThrownErrorInNavigation, createInstantValidationState } from './dynamic-rendering';
import { getClientComponentLoaderMetrics, wrapClientComponentLoader } from '../client-component-renderer-logger';
import { isNodeNextRequest } from '../base-http/helpers';
import { parseRelativeUrl } from '../../shared/lib/router/utils/parse-relative-url';
import AppRouter from '../../client/components/app-router';
import { getIsPossibleServerAction } from '../lib/server-action-request-meta';
import { createInitialRouterState } from '../../client/components/router-reducer/create-initial-router-state';
import { createMutableActionQueue } from '../../client/components/app-router-instance';
import { getRevalidateReason } from '../instrumentation/utils';
import { PAGE_SEGMENT_KEY } from '../../shared/lib/segment';
import { getFallbackRouteParams } from '../request/fallback-params';
import { createReactServerPrerenderResult, ReactServerResult, createReactServerPrerenderResultFromRender } from './app-render-prerender-utils';
import { Phase, printDebugThrownValueForProspectiveRender } from './prospective-render-utils';
import { runInSequentialTasks } from './app-render-render-utils';
import { waitAtLeastOneReactRenderTask } from '../../lib/scheduler';
import { getHmrRefreshHash, workUnitAsyncStorage } from './work-unit-async-storage.external';
import { consoleAsyncStorage } from './console-async-storage.external';
import { CacheSignal } from './cache-signal';
import { createResponseVaryParamsAccumulator, finishAccumulatingVaryParams, getMetadataVaryParamsThenable } from './vary-params';
import { getTracedMetadata } from '../lib/trace/utils';
import { InvariantError } from '../../shared/lib/invariant-error';
import { StaleTimeIterable, createSelectStaleTime, trackStaleTime, finishStaleTimeTracking } from './stale-time';
import { HTML_CONTENT_TYPE_HEADER, INFINITE_CACHE } from '../../lib/constants';
import { createComponentStylesAndScripts } from './create-component-styles-and-scripts';
import { parseLoaderTree } from '../../shared/lib/router/utils/parse-loader-tree';
import { createPrerenderResumeDataCache, createRenderResumeDataCache } from '../resume-data-cache/resume-data-cache';
import isError from '../../lib/is-error';
import { createServerInsertedMetadata } from './metadata-insertion/create-server-inserted-metadata';
import { getPreviouslyRevalidatedTags } from '../server-utils';
import { executeRevalidates } from '../revalidation-utils';
import { trackPendingChunkLoad, trackPendingImport, trackPendingModules } from './module-loading/track-module-loading.external';
import { isReactLargeShellError } from './react-large-shell-error';
import { normalizeConventionFilePath } from './segment-explorer-path';
import { getRequestMeta } from '../request-meta';
import { getDynamicParam, interpolateParallelRouteParams } from '../../shared/lib/router/utils/get-dynamic-param';
import { ImageConfigContext } from '../../shared/lib/image-config-context.shared-runtime';
import { imageConfigDefault } from '../../shared/lib/image-config';
import { RenderStage, StagedRenderingController } from './staged-rendering';
import { anySegmentHasRuntimePrefetchEnabled, isPageAllowedToBlock, anySegmentNeedsInstantValidationInDev, anySegmentNeedsInstantValidationInBuild, resolveInstantConfigSamplesForPage } from './instant-validation/instant-config';
import { warnOnce } from '../../shared/lib/utils/warn-once';
import { createDebugChannel } from './debug-channel-server';
import { createNodeStreamWithLateRelease } from './instant-validation/stream-utils';
import { createValidationBoundaryTracking } from './instant-validation/boundary-tracking';
import { ResponseCookies } from '../web/spec-extension/cookies';
import { isInstantValidationError } from './instant-validation/instant-validation-error';
function maybeAppendBuildIdToRSCPayload(ctx, payload) {
if (!ctx.sharedContext.deploymentId) {
// When using the build id, we need to initialize the id on initial page load, so a build id
// header wouldn't be enough.
payload.b = ctx.sharedContext.buildId;
}
return payload;
}
const flightDataPathHeadKey = 'h';
const getFlightViewportKey = (requestId)=>requestId + 'v';
const getFlightMetadataKey = (requestId)=>requestId + 'm';
const filterStackFrame = process.env.NODE_ENV !== 'production' ? require('../lib/source-maps').filterStackFrameDEV : undefined;
function parseRequestHeaders(headers, options) {
// runtime prefetch requests are *not* treated as prefetch requests
// (TODO: this is confusing, we should refactor this to express this better)
const isPrefetchRequest = headers[NEXT_ROUTER_PREFETCH_HEADER] === '1';
const isRuntimePrefetchRequest = headers[NEXT_ROUTER_PREFETCH_HEADER] === '2';
const isHmrRefresh = headers[NEXT_HMR_REFRESH_HEADER] !== undefined;
const isRSCRequest = headers[RSC_HEADER] !== undefined;
const shouldProvideFlightRouterState = isRSCRequest && (!isPrefetchRequest || !options.isRoutePPREnabled);
const flightRouterState = shouldProvideFlightRouterState ? parseAndValidateFlightRouterState(headers[NEXT_ROUTER_STATE_TREE_HEADER]) : undefined;
// Checks if this is a prefetch of the Route Tree by the Segment Cache
const isRouteTreePrefetchRequest = headers[NEXT_ROUTER_SEGMENT_PREFETCH_HEADER] === '/_tree';
const csp = headers['content-security-policy'] || headers['content-security-policy-report-only'];
const nonce = typeof csp === 'string' ? getScriptNonceFromHeader(csp) : undefined;
const previouslyRevalidatedTags = getPreviouslyRevalidatedTags(headers, options.previewModeId);
let requestId;
let htmlRequestId;
if (process.env.__NEXT_DEV_SERVER) {
// The request IDs are only used for the dev server to send debug
// information to the matching client (identified by the HTML request ID
// that was sent to the client with the HTML document) for the current
// request (identified by the request ID, as defined by the client).
requestId = typeof headers[NEXT_REQUEST_ID_HEADER] === 'string' ? headers[NEXT_REQUEST_ID_HEADER] : undefined;
htmlRequestId = typeof headers[NEXT_HTML_REQUEST_ID_HEADER] === 'string' ? headers[NEXT_HTML_REQUEST_ID_HEADER] : undefined;
}
return {
flightRouterState,
isPrefetchRequest,
isRuntimePrefetchRequest,
isRouteTreePrefetchRequest,
isHmrRefresh,
isRSCRequest,
nonce,
previouslyRevalidatedTags,
requestId,
htmlRequestId
};
}
/**
* Walks the loader tree to find the minimum `unstable_dynamicStaleTime` exported by
* any page module. Returns null if no page exports the config.
*
* This only reads static exports from page modules — it does not render any
* server components, so it's cheap to call.
*
* TODO: Move this to the prefetch hints file so we don't have to walk the
* tree on every render.
*/ async function getDynamicStaleTime(tree) {
const { page, parallelRoutes } = parseLoaderTree(tree);
let result = null;
// Only pages (not layouts) can export unstable_dynamicStaleTime.
if (typeof page !== 'undefined') {
const pageMod = await page[0]();
if (pageMod && typeof pageMod.unstable_dynamicStaleTime === 'number') {
const value = pageMod.unstable_dynamicStaleTime;
result = result !== null ? Math.min(result, value) : value;
}
}
const childPromises = [];
for(const parallelRouteKey in parallelRoutes){
childPromises.push(getDynamicStaleTime(parallelRoutes[parallelRouteKey]));
}
const childResults = await Promise.all(childPromises);
for (const childResult of childResults){
if (childResult !== null) {
result = result !== null ? Math.min(result, childResult) : childResult;
}
}
return result;
}
function createNotFoundLoaderTree(loaderTree) {
const components = loaderTree[2];
const hasGlobalNotFound = !!components['global-not-found'];
const notFoundTreeComponents = hasGlobalNotFound ? {
layout: components['global-not-found'],
page: [
()=>null,
'next/dist/client/components/builtin/empty-stub'
]
} : {
page: components['not-found']
};
return [
'',
{
children: [
PAGE_SEGMENT_KEY,
{},
notFoundTreeComponents,
null
]
},
// Always include global-error so that getGlobalErrorStyles can access it.
// When global-not-found is present, use full components.
// Otherwise, only include global-error module.
hasGlobalNotFound ? components : {
'global-error': components['global-error']
},
null
];
}
/**
* Returns a function that parses the dynamic segment and return the associated value.
*/ function makeGetDynamicParamFromSegment(interpolatedParams, fallbackRouteParams, optimisticRouting) {
return function getDynamicParamFromSegment(loaderTree) {
const [segment, , , staticSiblings] = loaderTree;
const segmentParam = getSegmentParam(segment);
if (!segmentParam) {
return null;
}
const segmentKey = segmentParam.paramName;
const dynamicParamType = dynamicParamTypes[segmentParam.paramType];
// Static siblings are only included when optimistic routing is enabled
const siblings = optimisticRouting ? staticSiblings : null;
return getDynamicParam(interpolatedParams, segmentKey, dynamicParamType, fallbackRouteParams, siblings);
};
}
function NonIndex({ createElement, pagePath, statusCode, isPossibleServerAction }) {
const is404Page = pagePath === '/404';
const isInvalidStatusCode = typeof statusCode === 'number' && statusCode > 400;
// Only render noindex for page request, skip for server actions
// TODO: is this correct if `isPossibleServerAction` is a false positive?
if (!isPossibleServerAction && (is404Page || isInvalidStatusCode)) {
return createElement('meta', {
name: 'robots',
content: 'noindex'
});
}
return null;
}
/**
* This is used by server actions & client-side navigations to generate RSC data from a client-side request.
* This function is only called on "dynamic" requests (ie, there wasn't already a static response).
* It uses request headers (namely `next-router-state-tree`) to determine where to start rendering.
*/ async function generateDynamicRSCPayload(ctx, options) {
// Flight data that is going to be passed to the browser.
// Currently a single item array but in the future multiple patches might be combined in a single request.
// We initialize `flightData` to an empty string because the client router knows how to tolerate
// it (treating it as an MPA navigation). The only time this function wouldn't generate flight data
// is for server actions, if the server action handler instructs this function to skip it. When the server
// action reducer sees a falsy value, it'll simply resolve the action with no data.
let flightData = '';
const { componentMod: { routeModule: { userland: { loaderTree } }, createElement, createMetadataComponents, Fragment }, query, requestId, flightRouterState, workStore, url } = ctx;
const serveStreamingMetadata = !!ctx.renderOpts.serveStreamingMetadata;
if (!(options == null ? void 0 : options.skipPageRendering)) {
var _ctx_renderOpts_prefetchHints;
const preloadCallbacks = [];
// If we're performing instant validation, we need to render the whole tree,
// without skipping shared layouts.
const needsFullTree = process.env.__NEXT_DEV_SERVER && ctx.renderOpts.cacheComponents && !(options == null ? void 0 : options.actionResult) && // Only for navigations
await anySegmentNeedsInstantValidationInDev(loaderTree);
const metadataIsRuntimePrefetchable = await anySegmentHasRuntimePrefetchEnabled(loaderTree);
const { Viewport, Metadata, MetadataOutlet } = createMetadataComponents({
tree: loaderTree,
parsedQuery: query,
pathname: url.pathname,
metadataContext: createMetadataContext(ctx.renderOpts),
interpolatedParams: ctx.interpolatedParams,
serveStreamingMetadata,
isRuntimePrefetchable: metadataIsRuntimePrefetchable
});
const rscHead = createElement(Fragment, {
key: flightDataPathHeadKey
}, createElement(NonIndex, {
createElement,
pagePath: ctx.pagePath,
statusCode: ctx.res.statusCode,
isPossibleServerAction: ctx.isPossibleServerAction
}), createElement(Viewport, {
key: getFlightViewportKey(requestId)
}), createElement(Metadata, {
key: getFlightMetadataKey(requestId)
}));
flightData = (needsFullTree ? await createFullTreeFlightDataForNavigation({
ctx,
loaderTree,
rscHead,
injectedCSS: new Set(),
injectedJS: new Set(),
injectedFontPreloadTags: new Set(),
preloadCallbacks,
MetadataOutlet
}) : await walkTreeWithFlightRouterState({
ctx,
loaderTreeToFilter: loaderTree,
parentParams: {},
flightRouterState,
rscHead,
injectedCSS: new Set(),
injectedJS: new Set(),
injectedFontPreloadTags: new Set(),
rootLayoutIncluded: false,
preloadCallbacks,
MetadataOutlet,
hintTree: ((_ctx_renderOpts_prefetchHints = ctx.renderOpts.prefetchHints) == null ? void 0 : _ctx_renderOpts_prefetchHints[ctx.pagePath]) ?? null
})).map((path)=>path.slice(1)) // remove the '' (root) segment
;
}
// In dev, the Vary header may not reliably reflect whether a route can
// be intercepted, because interception routes are compiled on demand.
// Default to true so the client doesn't cache a stale Fallback entry.
const varyHeader = ctx.res.getHeader('vary');
const couldBeIntercepted = !!process.env.__NEXT_DEV_SERVER || typeof varyHeader === 'string' && varyHeader.includes(NEXT_URL);
// If we have an action result, then this is a server action response.
// We can rely on this because `ActionResult` will always be a promise, even if
// the result is falsey.
if (options == null ? void 0 : options.actionResult) {
return maybeAppendBuildIdToRSCPayload(ctx, {
a: options.actionResult,
f: flightData,
q: getRenderedSearch(query),
i: !!couldBeIntercepted
});
}
// Otherwise, it's a regular RSC response.
const baseResponse = maybeAppendBuildIdToRSCPayload(ctx, {
f: flightData,
q: getRenderedSearch(query),
i: !!couldBeIntercepted,
S: workStore.isStaticGeneration,
h: getMetadataVaryParamsThenable()
});
if ((options == null ? void 0 : options.staleTimeIterable) !== undefined) {
baseResponse.s = options.staleTimeIterable;
}
if ((options == null ? void 0 : options.staticStageByteLengthPromise) !== undefined) {
baseResponse.l = options.staticStageByteLengthPromise;
}
if ((options == null ? void 0 : options.runtimePrefetchStream) !== undefined) {
baseResponse.p = options.runtimePrefetchStream;
}
// Include the per-page dynamic stale time from unstable_dynamicStaleTime, but only
// for dynamic renders (not prerenders/static generation). The client treats
// its presence as authoritative.
// TODO: Move this to the prefetch hints file so we don't have to walk the
// tree on every render.
if (!workStore.isStaticGeneration) {
const dynamicStaleTime = await getDynamicStaleTime(ctx.componentMod.routeModule.userland.loaderTree);
if (dynamicStaleTime !== null) {
baseResponse.d = dynamicStaleTime;
}
}
return baseResponse;
}
function createErrorContext(ctx, renderSource) {
return {
routerKind: 'App Router',
routePath: ctx.pagePath,
// TODO: is this correct if `isPossibleServerAction` is a false positive?
routeType: ctx.isPossibleServerAction ? 'action' : 'render',
renderSource,
revalidateReason: getRevalidateReason(ctx.workStore)
};
}
/**
* Produces a RenderResult containing the Flight data for the given request. See
* `generateDynamicRSCPayload` for information on the contents of the render result.
*/ async function generateDynamicFlightRenderResult(req, ctx, requestStore, options) {
const { htmlRequestId, renderOpts, requestId, workStore } = ctx;
const { onInstrumentationRequestError, setReactDebugChannel, isBuildTimePrerendering = false } = renderOpts;
function onFlightDataRenderError(err, silenceLog) {
return onInstrumentationRequestError == null ? void 0 : onInstrumentationRequestError(err, req, createErrorContext(ctx, 'react-server-components-payload'), silenceLog);
}
const onError = createReactServerErrorHandler(process.env.NODE_ENV === 'development', isBuildTimePrerendering, workStore.reactServerErrorsByDigest, onFlightDataRenderError);
const debugChannel = setReactDebugChannel && createDebugChannel();
if (debugChannel) {
setReactDebugChannel(debugChannel.clientSide, htmlRequestId, requestId);
}
const { clientModules } = getClientReferenceManifest();
// For app dir, use the bundled version of Flight server renderer (renderToReadableStream)
// which contains the subset React.
const rscPayload = await workUnitAsyncStorage.run(requestStore, generateDynamicRSCPayload, ctx, options);
const flightStream = workUnitAsyncStorage.run(requestStore, renderToFlightStream, ctx.componentMod, rscPayload, clientModules, {
onError,
temporaryReferences: options == null ? void 0 : options.temporaryReferences,
filterStackFrame,
debugChannel: debugChannel == null ? void 0 : debugChannel.serverSide
});
return new FlightRenderResult(flightStream, {
fetchMetrics: workStore.fetchMetrics
}, options == null ? void 0 : options.waitUntil);
}
/**
* Production-only staged dynamic flight render for cache components. Uses
* staged rendering to separate static (RDC-backed) from runtime/dynamic
* content.
*/ async function generateStagedDynamicFlightRenderResult(req, ctx, requestStore) {
const { componentMod, workStore, renderOpts } = ctx;
const { renderToReadableStream, routeModule } = componentMod;
const { loaderTree } = routeModule.userland;
const { onInstrumentationRequestError, experimental } = renderOpts;
function onFlightDataRenderError(err, silenceLog) {
return onInstrumentationRequestError == null ? void 0 : onInstrumentationRequestError(err, req, createErrorContext(ctx, 'react-server-components-payload'), silenceLog);
}
const onError = createReactServerErrorHandler(false, false, workStore.reactServerErrorsByDigest, onFlightDataRenderError);
const selectStaleTime = createSelectStaleTime(experimental);
const staleTimeIterable = new StaleTimeIterable();
// TODO(cached-navs): this assumes that we checked during build that there's no sync IO.
// but it can happen e.g. after a revalidation or conditionally for a param that wasn't prerendered.
// we should change this to track sync IO, log an error and advance to dynamic.
const shouldTrackSyncIO = false;
const stageController = new StagedRenderingController(null, null, shouldTrackSyncIO);
// Initialize stale time tracking on the request store.
requestStore.stale = INFINITE_CACHE;
requestStore.stagedRendering = stageController;
requestStore.asyncApiPromises = createAsyncApiPromises(stageController, requestStore.cookies, requestStore.mutableCookies, requestStore.headers);
trackStaleTime(requestStore, staleTimeIterable, selectStaleTime);
// Deferred promise for the static stage byte length. Flight serializes the
// resolved value into the stream so the client knows where the static
// prefix ends.
let resolveStaticStageByteLength;
const staticStageByteLengthPromise = new Promise((resolve)=>{
resolveStaticStageByteLength = resolve;
});
// Check if this route has opted into runtime prefetching via
// unstable_instant. If so, we piggyback on the dynamic render to fill caches
// and then spawn a final runtime prerender whose result stream is embedded in
// the RSC payload. This is gated on the explicit opt-in because it adds extra
// server processing, increases the response payload size, and the runtime
// prefetch output should have been validated first.
const hasRuntimePrefetch = await anySegmentHasRuntimePrefetchEnabled(loaderTree);
let runtimePrefetchStream;
if (hasRuntimePrefetch) {
// Create a mutable cache that gets filled during the dynamic render.
const prerenderResumeDataCache = createPrerenderResumeDataCache();
requestStore.prerenderResumeDataCache = prerenderResumeDataCache;
const cacheSignal = new CacheSignal();
trackPendingModules(cacheSignal);
requestStore.cacheSignal = cacheSignal;
// Create a deferred stream for the runtime prefetch result. Its readable
// side goes into the RSC payload (Flight serializes it lazily). The
// writable side receives the runtime prerender result once the dynamic
// render has filled all caches.
const runtimePrefetchTransform = new TransformStream();
runtimePrefetchStream = runtimePrefetchTransform.readable;
// Wait for the dynamic render to fill caches, then run the final runtime
// prerender (fire-and-forget — does not block the response).
void cacheSignal.cacheReady().then(()=>spawnRuntimePrefetchWithFilledCaches(runtimePrefetchTransform.writable, ctx, prerenderResumeDataCache, requestStore, onError));
}
const rscPayload = await workUnitAsyncStorage.run(requestStore, generateDynamicRSCPayload, ctx, {
staleTimeIterable,
staticStageByteLengthPromise,
runtimePrefetchStream
});
const { clientModules } = getClientReferenceManifest();
const flightReadableStream = await runInSequentialTasks(()=>{
stageController.advanceStage(RenderStage.Static);
const stream = workUnitAsyncStorage.run(requestStore, renderToReadableStream, rscPayload, clientModules, {
onError,
filterStackFrame
});
const [dynamicStream, staticStream] = stream.tee();
countStaticStageBytes(staticStream, stageController).then(resolveStaticStageByteLength);
return dynamicStream;
}, ()=>{
// This is a separate task that doesn't advance a stage. It forces
// draining the microtask queue so that the stale time iterable is closed
// before we advance to the dynamic stage.
void finishStaleTimeTracking(staleTimeIterable);
}, ()=>{
stageController.advanceStage(RenderStage.Dynamic);
});
return new FlightRenderResult(flightReadableStream, {
fetchMetrics: workStore.fetchMetrics
});
}
/**
* Runs a final runtime prerender using the provided (already filled) cache and
* pipes its output into the provided writable stream. The caller is responsible
* for waiting until caches are warm before calling this function.
*/ async function spawnRuntimePrefetchWithFilledCaches(writable, ctx, prerenderResumeDataCache, requestStore, onError) {
try {
const { componentMod, getDynamicParamFromSegment } = ctx;
const { loaderTree } = componentMod.routeModule.userland;
const rootParams = getRootParams(loaderTree, getDynamicParamFromSegment);
const staleTimeIterable = new StaleTimeIterable();
const { result } = await finalRuntimeServerPrerender(ctx, generateDynamicRSCPayload.bind(null, ctx, {
staleTimeIterable
}), prerenderResumeDataCache, null, rootParams, requestStore.headers, requestStore.cookies, requestStore.draftMode, onError, staleTimeIterable);
await result.prelude.pipeTo(writable);
} catch {
// Runtime prerender failed. Close the stream gracefully — the navigation
// still works, we just won't get cached runtime data.
try {
await writable.close();
} catch {
// Writable may already be closed/errored.
}
}
}
async function stagedRenderToReadableStreamWithoutCachesInDev(ctx, requestStore, getPayload, options) {
// We're rendering while bypassing caches,
// so we have no hope of showing a useful runtime stage.
// But we still want things like `params` to show up in devtools correctly,
// which relies on mechanisms we've set up for staged rendering,
// so we do a 2-task version (Static -> Dynamic) instead.
// We aren't filling caches so we don't need to abort this render, it'll
// stream in a single pass
const stageController = new StagedRenderingController(null, null, false // do not track sync IO (we don't have reliable stages)
);
const environmentName = ()=>{
const currentStage = stageController.currentStage;
switch(currentStage){
case RenderStage.Before:
case RenderStage.EarlyStatic:
case RenderStage.Static:
return 'Prerender';
case RenderStage.EarlyRuntime:
case RenderStage.Runtime:
case RenderStage.Dynamic:
case RenderStage.Abandoned:
return 'Server';
default:
currentStage;
throw Object.defineProperty(new InvariantError(`Invalid render stage: ${currentStage}`), "__NEXT_ERROR_CODE", {
value: "E881",
enumerable: false,
configurable: true
});
}
};
requestStore.stagedRendering = stageController;
requestStore.asyncApiPromises = createAsyncApiPromises(stageController, requestStore.cookies, requestStore.mutableCookies, requestStore.headers);
const { clientModules } = getClientReferenceManifest();
const rscPayload = await getPayload(requestStore);
return await runInSequentialTasks(()=>{
stageController.advanceStage(RenderStage.Static);
return workUnitAsyncStorage.run(requestStore, renderToFlightStream, ctx.componentMod, rscPayload, clientModules, {
...options,
environmentName
});
}, ()=>{
stageController.advanceStage(RenderStage.Dynamic);
});
}
/**
* Fork of `generateDynamicFlightRenderResult` that renders using `renderWithRestartOnCacheMissInDev`
* to ensure correct separation of environments Prerender/Server (for use in Cache Components)
*/ async function generateDynamicFlightRenderResultWithStagesInDev(req, ctx, initialRequestStore, createRequestStore, fallbackParams) {
const { htmlRequestId, renderOpts, requestId, workStore, componentMod: { createElement, routeModule: { userland: { loaderTree } } }, url } = ctx;
const { onInstrumentationRequestError, setReactDebugChannel, setCacheStatus, isBuildTimePrerendering = false } = renderOpts;
function onFlightDataRenderError(err, silenceLog) {
return onInstrumentationRequestError == null ? void 0 : onInstrumentationRequestError(err, req, createErrorContext(ctx, 'react-server-components-payload'), silenceLog);
}
const onError = createReactServerErrorHandler(process.env.NODE_ENV === 'development', isBuildTimePrerendering, workStore.reactServerErrorsByDigest, onFlightDataRenderError);
// We validate RSC requests for HMR refreshes and client navigations when
// instant configs exist, since we render all the layouts necessary to perform
// the validation in those cases.
const shouldValidate = !isBypassingCachesInDev(initialRequestStore) && (initialRequestStore.isHmrRefresh === true || await anySegmentNeedsInstantValidationInDev(loaderTree));
const getPayload = async (requestStore)=>{
const payload = await workUnitAsyncStorage.run(requestStore, generateDynamicRSCPayload, ctx, undefined);
if (isBypassingCachesInDev(requestStore)) {
// Mark the RSC payload to indicate that caches were bypassed in dev.
// This lets the client know not to cache anything based on this render.
payload._bypassCachesInDev = createElement(WarnForBypassCachesInDev, {
route: workStore.route
});
} else if (shouldValidate) {
// If this payload will be used for validation, it needs to contain the
// canonical URL. Without it we'd get an error.
payload.c = prepareInitialCanonicalUrl(url);
}
return payload;
};
let debugChannel;
let stream;
if (// We only do this flow if we can safely recreate the store from scratch
// (which is not the case for renders after an action)
createRequestStore && // We only do this flow if we're not bypassing caches in dev using
// "disable cache" in devtools or a hard refresh (cache-control: "no-store")
!isBypassingCachesInDev(initialRequestStore)) {
// Before we kick off the render, we set the cache status back to it's initial state
// in case a previous render bypassed the cache.
if (setCacheStatus) {
setCacheStatus('ready', htmlRequestId);
}
const { stream: serverStream, accumulatedChunksPromise, syncInterruptReason, startTime, staticStageEndTime, runtimeStageEndTime, debugChannel: returnedDebugChannel, requestStore: finalRequestStore } = await renderWithRestartOnCacheMissInDev(ctx, initialRequestStore, createRequestStore, getPayload, onError);
if (shouldValidate) {
let validationDebugChannelClient = undefined;
if (returnedDebugChannel) {
const [t1, t2] = returnedDebugChannel.clientSide.readable.tee();
returnedDebugChannel.clientSide.readable = t1;
validationDebugChannelClient = nodeStreamFromReadableStream(t2);
}
consoleAsyncStorage.run({
dim: true
}, spawnStaticShellValidationInDev, accumulatedChunksPromise, syncInterruptReason, startTime, staticStageEndTime, runtimeStageEndTime, ctx, finalRequestStore, fallbackParams, validationDebugChannelClient);
} else {
logValidationSkipped(ctx);
}
debugChannel = returnedDebugChannel;
stream = serverStream;
} else {
// We're either bypassing caches or we can't restart the render.
// Do a dynamic render, but with (basic) environment labels.
// Set cache status to bypass when specifically bypassing caches in dev
if (setCacheStatus) {
setCacheStatus('bypass', htmlRequestId);
}
debugChannel = setReactDebugChannel && createDebugChannel();
stream = await stagedRenderToReadableStreamWithoutCachesInDev(ctx, initialRequestStore, getPayload, {
onError: onError,
filterStackFrame,
debugChannel: debugChannel == null ? void 0 : debugChannel.serverSide
});
}
if (debugChannel && setReactDebugChannel) {
setReactDebugChannel(debugChannel.clientSide, htmlRequestId, requestId);
}
return new FlightRenderResult(stream, {
fetchMetrics: workStore.fetchMetrics
});
}
async function generateRuntimePrefetchResult(req, ctx, requestStore) {
const { workStore, renderOpts } = ctx;
const { isBuildTimePrerendering = false, onInstrumentationRequestError } = renderOpts;
function onFlightDataRenderError(err, silenceLog) {
return onInstrumentationRequestError == null ? void 0 : onInstrumentationRequestError(err, req, // TODO(runtime-ppr): should we use a different value?
createErrorContext(ctx, 'react-server-components-payload'), silenceLog);
}
const onError = createReactServerErrorHandler(false, isBuildTimePrerendering, workStore.reactServerErrorsByDigest, onFlightDataRenderError);
const metadata = {};
const staleTimeIterable = new StaleTimeIterable();
const { componentMod: { routeModule: { userland: { loaderTree } } }, getDynamicParamFromSegment } = ctx;
const rootParams = getRootParams(loaderTree, getDynamicParamFromSegment);
// We need to share caches between the prospective prerender and the final prerender,
// but we're not going to persist this anywhere.
const prerenderResumeDataCache = createPrerenderResumeDataCache();
// We're not resuming an existing render.
const renderResumeDataCache = null;
await prospectiveRuntimeServerPrerender(ctx, generateDynamicRSCPayload.bind(null, ctx), prerenderResumeDataCache, renderResumeDataCache, rootParams, requestStore.headers, requestStore.cookies, requestStore.draftMode);
const response = await finalRuntimeServerPrerender(ctx, generateDynamicRSCPayload.bind(null, ctx, {
staleTimeIterable
}), prerenderResumeDataCache, renderResumeDataCache, rootParams, requestStore.headers, requestStore.cookies, requestStore.draftMode, onError, staleTimeIterable);
applyMetadataFromPrerenderResult(response, metadata, workStore);
metadata.fetchMetrics = ctx.workStore.fetchMetrics;
return new FlightRenderResult(response.result.prelude, metadata);
}
async function prospectiveRuntimeServerPrerender(ctx, getPayload, prerenderResumeDataCache, renderResumeDataCache, rootParams, headers, cookies, draftMode) {
const { implicitTags, renderOpts, workStore } = ctx;
const { ComponentMod } = renderOpts;
// Prerender controller represents the lifetime of the prerender.
// It will be aborted when a Task is complete or a synchronously aborting
// API is called. Notably during cache-filling renders this does not actually
// terminate the render itself which will continue until all caches are filled
const initialServerPrerenderController = new AbortController();
// This controller represents the lifetime of the React render call. Notably
// during the cache-filling render it is different from the prerender controller
// because we don't want to end the react render until all caches are filled.
const initialServerRenderController = new AbortController();
// The cacheSignal helps us track whether caches are still filling or we are ready
// to cut the render off.
const cacheSignal = new CacheSignal();
const initialServerPrerenderStore = {
type: 'prerender-runtime',
phase: 'render',
rootParams,
implicitTags,
renderSignal: initialServerRenderController.signal,
controller: initialServerPrerenderController,
// During the initial prerender we need to track all cache reads to ensure
// we render long enough to fill every cache it is possible to visit during
// the final prerender.
cacheSignal,
// We only need to track dynamic accesses during the final prerender.
dynamicTracking: null,
// Runtime prefetches are never cached server-side, only client-side,
// so we set `expire` and `revalidate` to their minimum values just in case.
revalidate: 1,
expire: 0,
stale: INFINITE_CACHE,
tags: [
...implicitTags.tags
],
renderResumeDataCache,
prerenderResumeDataCache,
hmrRefreshHash: undefined,
// We don't track vary params during initial prerender, only the final one
varyParamsAccumulator: null,
// No stage sequencing needed for prospective renders.
stagedRendering: null,
// These are not present in regular prerenders, but allowed in a runtime prerender.
headers,
cookies,
draftMode
};
const { clientModules } = getClientReferenceManifest();
// We're not going to use the result of this render because the only time it could be used
// is if it completes in a microtask and that's likely very rare for any non-trivial app
const initialServerPayload = await workUnitAsyncStorage.run(initialServerPrerenderStore, getPayload);
const prerenderOptions = {
filterStackFrame,
onError: (err)=>{
const digest = getDigestForWellKnownError(err);
if (digest) {
return digest;
}
if (initialServerPrerenderController.signal.aborted) {
// The render aborted before this error was handled which indicates
// the error is caused by unfinished components within the render
return;
} else if (process.env.NEXT_DEBUG_BUILD || process.env.__NEXT_VERBOSE_LOGGING) {
printDebugThrownValueForProspectiveRender(err, workStore.route, Phase.ProspectiveRender);
}
},
// We don't want to stop rendering until the cacheSignal is complete so we pass
// a different signal to this render call than is used by dynamic APIs to signify
// transitioning out of the prerender environment
signal: initialServerRenderController.signal
};
const pendingInitialServerResult = workUnitAsyncStorage.run(initialServerPrerenderStore, getServerPrerender(ComponentMod), initialServerPayload, clientModules, prerenderOptions);
// Wait for all caches to be finished filling and for async imports to resolve
trackPendingModules(cacheSignal);
await cacheSignal.cacheReady();
initialServerRenderController.abort();
initialServerPrerenderController.abort();
// We don't need to continue the prerender process if we already
// detected invalid dynamic usage in the initial prerender phase.
if (workStore.invalidDynamicUsageError) {
throw workStore.invalidDynamicUsageError;
}
try {
return await createReactServerPrerenderResult(pendingInitialServerResult);
} catch (err) {
if (initialServerRenderController.signal.aborted || initialServerPrerenderController.signal.aborted) {
// These are expected errors that might error the prerender. we ignore them.
} else if (process.env.NEXT_DEBUG_BUILD || process.env.__NEXT_VERBOSE_LOGGING) {
// We don't normally log these errors because we are going to retry anyway but
// it can be useful for debugging Next.js itself to get visibility here when needed
printDebugThrownValueForProspectiveRender(err, workStore.route, Phase.ProspectiveRender);
}
return null;
}
}
/**
* Prepends a single ASCII byte to the stream indicating whether the response
* is partial (contains dynamic holes): '~' (0x7e) for partial, '#' (0x23)
* for complete.
*/ function prependIsPartialByte(stream, isPartial) {
const byte = new Uint8Array([
isPartial ? 0x7e : 0x23
]);
return stream.pipeThrough(new TransformStream({
start (controller) {
controller.enqueue(byte);
}
}));
}
async function finalRuntimeServerPrerender(ctx, getPayload, prerenderResumeDataCache, renderResumeDataCache, rootParams, headers, cookies, draftMode, onError, staleTimeIterable) {
const { implicitTags, renderOpts } = ctx;
const { ComponentMod, experimental, isDebugDynamicAccesses } = renderOpts;
const selectStaleTime = createSelectStaleTime(experimental);
let serverIsDynamic = false;
const finalServerController = new AbortController();
const serverDynamicTracking = createDynamicTrackingState(isDebugDynamicAccesses);
const finalStageController = new StagedRenderingController(finalServerController.signal, null, true // track sync IO
);
const varyParamsAccumulator = createResponseVaryParamsAccumulator();
const finalServerPrerenderStore = {
type: 'prerender-runtime',
phase: 'render',
rootParams,
implicitTags,
renderSignal: finalServerController.signal,
controller: finalServerController,
// All caches we could read must already be filled so no tracking is necessary
cacheSignal: null,
dynamicTracking: serverDynamicTracking,
// Runtime prefetches are never cached server-side, only client-side,
// so we set `expire` and `revalidate` to their minimum values just in case.
revalidate: 1,
expire: 0,
stale: INFINITE_CACHE,
tags: [
...implicitTags.tags
],
prerenderResumeDataCache,
renderResumeDataCache,
hmrRefreshHash: undefined,
varyParamsAccumulator,
// Used to separate the stages in the 5-task pipeline.
stagedRendering: finalStageController,
// These are not present in regular prerenders, but allowed in a runtime prerender.
headers,
cookies,
draftMode
};
trackStaleTime(finalServerPrerenderStore, staleTimeIterable, selectStaleTime);
const { clientModules } = getClientReferenceManifest();
const finalRSCPayload = await workUnitAsyncStorage.run(finalServerPrerenderStore, getPayload);
let prerenderIsPending = true;
const result = await runInSequentialTasks(async ()=>{
// EarlyStatic stage: render begins.
// Runtime-prefetchable segments render immediately.
// Non-prefetchable segments are gated until the Static stage.
finalStageController.advanceStage(RenderStage.EarlyStatic);
const prerenderResult = await workUnitAsyncStorage.run(finalServerPrerenderStore, getServerPrerender(ComponentMod), finalRSCPayload, clientModules, {
filterStackFrame,
onError,
signal: finalServerController.signal
});
prerenderIsPending = false;
return prerenderResult;
}, ()=>{
// Advance to Static stage: resolve promise holding back
// non-prefetchable segments so they can begin rendering.
finalStageController.advanceStage(RenderStage.Static);
}, ()=>{
// Advance to EarlyRuntime stage: resolve cookies/headers for
// runtime-prefetchable segments. Sync IO is checked here.
finalStageController.advanceStage(RenderStage.EarlyRuntime);
}, ()=>{
// Advance to Runtime stage: resolve cookies/headers for
// non-prefetchable segments. Sync IO is allowed here.
finalStageController.advanceStage(RenderStage.Runtime);
}, ()=>{
Promise.all([
finishStaleTimeTracking(staleTimeIterable),
finishAccumulatingVaryParams(varyParamsAccumulator)
]).then(()=>{
// Abort. This runs as a microtask after Flight has flushed the
// staleTime and varyParams closing chunks, but before the next
// macrotask resolves the overall result.
if (finalServerController.signal.aborted) {
// If the server controller is already aborted we must have called
// something that required aborting the prerender synchronously such
// as with new Date()
serverIsDynamic = true;
return;
}
if (prerenderIsPending) {
// If prerenderIsPending then we have blocked for longer than a Task
// and we assume there is something unfinished.
serverIsDynamic = true;
}
finalServerController.abort();
});
});
result.prelude = prependIsPartialByte(result.prelude, serverIsDynamic);
return {
result,
// TODO(runtime-ppr): do we need to produce a digest map here?
// digestErrorsMap: ...,
dynamicAccess: serverDynamicTracking,
isPartial: serverIsDynamic,
collectedRevalidate: finalServerPrerenderStore.revalidate,
collectedExpire: finalServerPrerenderStore.expire,
collectedStale: staleTimeIterable.currentValue,
collectedTags: finalServerPrerenderStore.tags
};
}
/**
* Crawlers will inadvertently think the canonicalUrl in the RSC payload should be crawled
* when our intention is to just seed the router state with the current URL.
* This function splits up the pathname so that we can later join it on
* when we're ready to consume the path.
*/ function prepareInitialCanonicalUrl(url) {
return (url.pathname + url.search).split('/');
}
function getRenderedSearch(query) {
// Inlined implementation of querystring.encode, which is not available in
// the Edge runtime.
const pairs = [];
for(const key in query){
const value = query[key];
if (value == null) continue;
if (Array.isArray(value)) {
for (const v of value){
pairs.push(`${encodeURIComponent(key)}=${encodeURIComponent(String(v))}`);
}
} else {
pairs.push(`${encodeURIComponent(key)}=${encodeURIComponent(String(value))}`);
}
}
// The result should match the format of a web URL's `search` property, since
// this is the format that's stored in the App Router state.
// TODO: We're a bit inconsistent about this. The x-nextjs-rewritten-query
// header omits the leading question mark. Should refactor to always do
// that instead.
if (pairs.length === 0) {
// If the search string is empty, retu