UNPKG

next

Version:

The React Framework

879 lines 242 kB
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