| | import type { ComponentType, ErrorInfo, JSX, ReactNode } from 'react' |
| | import type { RenderOpts, PreloadCallbacks } from './types' |
| | import type { |
| | ActionResult, |
| | DynamicParamTypesShort, |
| | FlightRouterState, |
| | Segment, |
| | CacheNodeSeedData, |
| | RSCPayload, |
| | FlightData, |
| | InitialRSCPayload, |
| | FlightDataPath, |
| | } from '../../shared/lib/app-router-types' |
| | import type { Readable } from 'node:stream' |
| | import { |
| | workAsyncStorage, |
| | type WorkStore, |
| | } from '../app-render/work-async-storage.external' |
| | import type { |
| | PrerenderStoreModernRuntime, |
| | RequestStore, |
| | } from '../app-render/work-unit-async-storage.external' |
| | import type { NextParsedUrlQuery } from '../request-meta' |
| | import type { LoaderTree } from '../lib/app-dir-module' |
| | import type { AppPageModule } from '../route-modules/app-page/module' |
| | import type { BaseNextRequest, BaseNextResponse } from '../base-http' |
| | import type { IncomingHttpHeaders } from 'http' |
| | import * as ReactClient from 'react' |
| |
|
| | import RenderResult, { |
| | type AppPageRenderResultMetadata, |
| | type RenderResultOptions, |
| | } from '../render-result' |
| | import { |
| | chainStreams, |
| | renderToInitialFizzStream, |
| | createDocumentClosingStream, |
| | continueFizzStream, |
| | continueDynamicPrerender, |
| | continueStaticPrerender, |
| | continueDynamicHTMLResume, |
| | streamToBuffer, |
| | streamToString, |
| | continueStaticFallbackPrerender, |
| | } from '../stream-utils/node-web-streams-helper' |
| | 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, type ImplicitTags } 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, |
| | type DigestedError, |
| | 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 } 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, |
| | type PostponedState, |
| | DynamicHTMLPreludeState, |
| | parsePostponedState, |
| | } from './postponed-state' |
| | import { |
| | getDynamicDataPostponedState, |
| | getDynamicHTMLPostponedState, |
| | getPostponedFromState, |
| | } from './postponed-state' |
| | import { isDynamicServerError } from '../../client/components/hooks-server-context' |
| | import { |
| | getFlightStream, |
| | createInlinedDataReadableStream, |
| | } from './use-flight-response' |
| | import { |
| | StaticGenBailoutError, |
| | isStaticGenBailoutError, |
| | } from '../../client/components/static-generation-bailout' |
| | import { getStackWithoutErrorMessage } from '../../lib/format-server-error' |
| | import { |
| | accessedDynamicData, |
| | createRenderInBrowserAbortSignal, |
| | formatDynamicAPIAccesses, |
| | isPrerenderInterruptedError, |
| | createDynamicTrackingState, |
| | createDynamicValidationState, |
| | trackAllowedDynamicAccess, |
| | throwIfDisallowedDynamic, |
| | PreludeState, |
| | consumeDynamicAccess, |
| | type DynamicAccess, |
| | logDisallowedDynamicError, |
| | trackDynamicHoleInRuntimeShell, |
| | trackDynamicHoleInStaticShell, |
| | getStaticShellDisallowedDynamicReasons, |
| | } 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 type { ServerComponentsHmrCache } from '../response-cache' |
| | import type { RequestErrorContext } from '../instrumentation/types' |
| | 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 type { OpaqueFallbackRouteParams } from '../request/fallback-params' |
| | import { |
| | prerenderAndAbortInSequentialTasksWithStages, |
| | processPrelude, |
| | } from './app-render-prerender-utils' |
| | import { |
| | type ReactServerPrerenderResult, |
| | ReactServerResult, |
| | createReactServerPrerenderResult, |
| | createReactServerPrerenderResultFromRender, |
| | prerenderAndAbortInSequentialTasks, |
| | } from './app-render-prerender-utils' |
| | import { |
| | Phase, |
| | printDebugThrownValueForProspectiveRender, |
| | } from './prospective-render-utils' |
| | import { |
| | pipelineInSequentialTasks, |
| | scheduleInSequentialTasks, |
| | } from './app-render-render-utils' |
| | import { waitAtLeastOneReactRenderTask } from '../../lib/scheduler' |
| | import { |
| | getHmrRefreshHash, |
| | workUnitAsyncStorage, |
| | type PrerenderStore, |
| | } from './work-unit-async-storage.external' |
| | import { consoleAsyncStorage } from './console-async-storage.external' |
| | import { CacheSignal } from './cache-signal' |
| | import { getTracedMetadata } from '../lib/trace/utils' |
| | import { InvariantError } from '../../shared/lib/invariant-error' |
| |
|
| | 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, |
| | type PrerenderResumeDataCache, |
| | type RenderResumeDataCache, |
| | } from '../resume-data-cache/resume-data-cache' |
| | import type { MetadataErrorType } from '../../lib/metadata/resolve-metadata' |
| | 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 type { GlobalErrorComponent } from '../../client/components/builtin/global-error' |
| | import { normalizeConventionFilePath } from './segment-explorer-path' |
| | import { getRequestMeta } from '../request-meta' |
| | import { |
| | getDynamicParam, |
| | interpolateParallelRouteParams, |
| | } from '../../shared/lib/router/utils/get-dynamic-param' |
| | import type { ExperimentalConfig } from '../config-shared' |
| | import type { Params } from '../request/params' |
| | import { createPromiseWithResolvers } from '../../shared/lib/promise-with-resolvers' |
| | 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 } from './staged-validation' |
| | import { warnOnce } from '../../shared/lib/utils/warn-once' |
| |
|
| | export type GetDynamicParamFromSegment = ( |
| | |
| | segment: string |
| | ) => DynamicParam | null |
| |
|
| | export type DynamicParam = { |
| | param: string |
| | value: string | string[] | null |
| | treeSegment: Segment |
| | type: DynamicParamTypesShort |
| | } |
| |
|
| | export type GenerateFlight = typeof generateDynamicFlightRenderResult |
| |
|
| | export type AppSharedContext = { |
| | buildId: string |
| | } |
| |
|
| | export type AppRenderContext = { |
| | sharedContext: AppSharedContext |
| | workStore: WorkStore |
| | url: ReturnType<typeof parseRelativeUrl> |
| | componentMod: AppPageModule |
| | renderOpts: RenderOpts |
| | parsedRequestHeaders: ParsedRequestHeaders |
| | getDynamicParamFromSegment: GetDynamicParamFromSegment |
| | query: NextParsedUrlQuery |
| | isPrefetch: boolean |
| | isPossibleServerAction: boolean |
| | requestTimestamp: number |
| | appUsingSizeAdjustment: boolean |
| | flightRouterState?: FlightRouterState |
| | requestId: string |
| | htmlRequestId: string |
| | pagePath: string |
| | assetPrefix: string |
| | isNotFoundPath: boolean |
| | nonce: string | undefined |
| | res: BaseNextResponse |
| | |
| | |
| | |
| | |
| | |
| | implicitTags: ImplicitTags |
| | } |
| |
|
| | interface ParseRequestHeadersOptions { |
| | readonly isRoutePPREnabled: boolean |
| | readonly previewModeId: string | undefined |
| | } |
| |
|
| | const flightDataPathHeadKey = 'h' |
| | const getFlightViewportKey = (requestId: string) => requestId + 'v' |
| | const getFlightMetadataKey = (requestId: string) => requestId + 'm' |
| |
|
| | const filterStackFrame = |
| | process.env.NODE_ENV !== 'production' |
| | ? (require('../lib/source-maps') as typeof import('../lib/source-maps')) |
| | .filterStackFrameDEV |
| | : undefined |
| |
|
| | interface ParsedRequestHeaders { |
| | |
| | |
| | |
| | |
| | |
| | |
| | readonly flightRouterState: FlightRouterState | undefined |
| | readonly isPrefetchRequest: boolean |
| | readonly isRuntimePrefetchRequest: boolean |
| | readonly isRouteTreePrefetchRequest: boolean |
| | readonly isHmrRefresh: boolean |
| | readonly isRSCRequest: boolean |
| | readonly nonce: string | undefined |
| | readonly previouslyRevalidatedTags: string[] |
| | readonly requestId: string | undefined |
| | readonly htmlRequestId: string | undefined |
| | } |
| |
|
| | function parseRequestHeaders( |
| | headers: IncomingHttpHeaders, |
| | options: ParseRequestHeadersOptions |
| | ): ParsedRequestHeaders { |
| | |
| | |
| | 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 |
| |
|
| | |
| | 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: string | undefined |
| | let htmlRequestId: string | undefined |
| |
|
| | if (process.env.NODE_ENV !== 'production') { |
| | |
| | |
| | |
| | |
| |
|
| | 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, |
| | } |
| | } |
| |
|
| | function createNotFoundLoaderTree(loaderTree: LoaderTree): LoaderTree { |
| | const components = loaderTree[2] |
| | const hasGlobalNotFound = !!components['global-not-found'] |
| | const notFoundTreeComponents: LoaderTree[2] = 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], |
| | }, |
| | |
| | hasGlobalNotFound ? components : {}, |
| | ] |
| | } |
| |
|
| | |
| | |
| | |
| | function makeGetDynamicParamFromSegment( |
| | interpolatedParams: Params, |
| | fallbackRouteParams: OpaqueFallbackRouteParams | null |
| | ): GetDynamicParamFromSegment { |
| | return function getDynamicParamFromSegment( |
| | |
| | segment: string |
| | ) { |
| | const segmentParam = getSegmentParam(segment) |
| | if (!segmentParam) { |
| | return null |
| | } |
| | const segmentKey = segmentParam.paramName |
| | const dynamicParamType = dynamicParamTypes[segmentParam.paramType] |
| | return getDynamicParam( |
| | interpolatedParams, |
| | segmentKey, |
| | dynamicParamType, |
| | fallbackRouteParams |
| | ) |
| | } |
| | } |
| |
|
| | function NonIndex({ |
| | createElement, |
| | pagePath, |
| | statusCode, |
| | isPossibleServerAction, |
| | }: { |
| | createElement: typeof ReactClient.createElement |
| | pagePath: string |
| | statusCode: number | undefined |
| | isPossibleServerAction: boolean |
| | }) { |
| | const is404Page = pagePath === '/404' |
| | const isInvalidStatusCode = typeof statusCode === 'number' && statusCode > 400 |
| |
|
| | |
| | |
| | if (!isPossibleServerAction && (is404Page || isInvalidStatusCode)) { |
| | return createElement('meta', { |
| | name: 'robots', |
| | content: 'noindex', |
| | }) |
| | } |
| | return null |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | |
| | async function generateDynamicRSCPayload( |
| | ctx: AppRenderContext, |
| | options?: { |
| | actionResult?: ActionResult |
| | skipPageRendering?: boolean |
| | runtimePrefetchSentinel?: number |
| | } |
| | ): Promise<RSCPayload> { |
| | |
| | |
| |
|
| | |
| | |
| | |
| | |
| | let flightData: FlightData = '' |
| |
|
| | const { |
| | componentMod: { |
| | routeModule: { |
| | userland: { loaderTree }, |
| | }, |
| | createElement, |
| | createMetadataComponents, |
| | Fragment, |
| | }, |
| | getDynamicParamFromSegment, |
| | query, |
| | requestId, |
| | flightRouterState, |
| | workStore, |
| | url, |
| | } = ctx |
| |
|
| | const serveStreamingMetadata = !!ctx.renderOpts.serveStreamingMetadata |
| |
|
| | if (!options?.skipPageRendering) { |
| | const preloadCallbacks: PreloadCallbacks = [] |
| |
|
| | const { Viewport, Metadata, MetadataOutlet } = createMetadataComponents({ |
| | tree: loaderTree, |
| | parsedQuery: query, |
| | pathname: url.pathname, |
| | metadataContext: createMetadataContext(ctx.renderOpts), |
| | getDynamicParamFromSegment, |
| | workStore, |
| | serveStreamingMetadata, |
| | }) |
| |
|
| | flightData = ( |
| | await walkTreeWithFlightRouterState({ |
| | ctx, |
| | loaderTreeToFilter: loaderTree, |
| | parentParams: {}, |
| | flightRouterState, |
| | |
| | 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), |
| | }) |
| | ), |
| | injectedCSS: new Set(), |
| | injectedJS: new Set(), |
| | injectedFontPreloadTags: new Set(), |
| | rootLayoutIncluded: false, |
| | preloadCallbacks, |
| | MetadataOutlet, |
| | }) |
| | ).map((path) => path.slice(1)) |
| | } |
| |
|
| | const varyHeader = ctx.res.getHeader('vary') |
| | const couldBeIntercepted = |
| | typeof varyHeader === 'string' && varyHeader.includes(NEXT_URL) |
| |
|
| | |
| | |
| | |
| | if (options?.actionResult) { |
| | return { |
| | a: options.actionResult, |
| | f: flightData, |
| | b: ctx.sharedContext.buildId, |
| | q: getRenderedSearch(query), |
| | i: !!couldBeIntercepted, |
| | } |
| | } |
| |
|
| | |
| | const baseResponse = { |
| | b: ctx.sharedContext.buildId, |
| | f: flightData, |
| | q: getRenderedSearch(query), |
| | i: !!couldBeIntercepted, |
| | S: workStore.isStaticGeneration, |
| | } |
| |
|
| | |
| | |
| | |
| | if (options?.runtimePrefetchSentinel !== undefined) { |
| | return { |
| | ...baseResponse, |
| | rp: [options.runtimePrefetchSentinel] as any, |
| | } |
| | } |
| |
|
| | return baseResponse |
| | } |
| |
|
| | function createErrorContext( |
| | ctx: AppRenderContext, |
| | renderSource: RequestErrorContext['renderSource'] |
| | ): RequestErrorContext { |
| | return { |
| | routerKind: 'App Router', |
| | routePath: ctx.pagePath, |
| | |
| | routeType: ctx.isPossibleServerAction ? 'action' : 'render', |
| | renderSource, |
| | revalidateReason: getRevalidateReason(ctx.workStore), |
| | } |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | async function generateDynamicFlightRenderResult( |
| | req: BaseNextRequest, |
| | ctx: AppRenderContext, |
| | requestStore: RequestStore, |
| | options?: { |
| | actionResult: ActionResult |
| | skipPageRendering: boolean |
| | componentTree?: CacheNodeSeedData |
| | preloadCallbacks?: PreloadCallbacks |
| | temporaryReferences?: WeakMap<any, string> |
| | waitUntil?: Promise<unknown> |
| | } |
| | ): Promise<RenderResult> { |
| | const { |
| | componentMod: { renderToReadableStream }, |
| | htmlRequestId, |
| | renderOpts, |
| | requestId, |
| | workStore, |
| | } = ctx |
| |
|
| | const { |
| | dev = false, |
| | onInstrumentationRequestError, |
| | setReactDebugChannel, |
| | nextExport = false, |
| | } = renderOpts |
| |
|
| | function onFlightDataRenderError(err: DigestedError, silenceLog: boolean) { |
| | return onInstrumentationRequestError?.( |
| | err, |
| | req, |
| | createErrorContext(ctx, 'react-server-components-payload'), |
| | silenceLog |
| | ) |
| | } |
| |
|
| | const onError = createReactServerErrorHandler( |
| | dev, |
| | nextExport, |
| | workStore.reactServerErrorsByDigest, |
| | onFlightDataRenderError |
| | ) |
| |
|
| | const debugChannel = setReactDebugChannel && createDebugChannel() |
| |
|
| | if (debugChannel) { |
| | setReactDebugChannel(debugChannel.clientSide, htmlRequestId, requestId) |
| | } |
| |
|
| | const { clientModules } = getClientReferenceManifest() |
| |
|
| | |
| | |
| | const rscPayload = await workUnitAsyncStorage.run( |
| | requestStore, |
| | generateDynamicRSCPayload, |
| | ctx, |
| | options |
| | ) |
| |
|
| | const flightReadableStream = workUnitAsyncStorage.run( |
| | requestStore, |
| | renderToReadableStream, |
| | rscPayload, |
| | clientModules, |
| | { |
| | onError, |
| | temporaryReferences: options?.temporaryReferences, |
| | filterStackFrame, |
| | debugChannel: debugChannel?.serverSide, |
| | } |
| | ) |
| |
|
| | return new FlightRenderResult( |
| | flightReadableStream, |
| | { fetchMetrics: workStore.fetchMetrics }, |
| | options?.waitUntil |
| | ) |
| | } |
| |
|
| | type RenderToReadableStreamServerOptions = NonNullable< |
| | Parameters< |
| | (typeof import('react-server-dom-webpack/server.node'))['renderToReadableStream'] |
| | >[2] |
| | > |
| |
|
| | async function stagedRenderToReadableStreamWithoutCachesInDev( |
| | ctx: AppRenderContext, |
| | requestStore: RequestStore, |
| | getPayload: (requestStore: RequestStore) => Promise<RSCPayload>, |
| | options: Omit<RenderToReadableStreamServerOptions, 'environmentName'> |
| | ) { |
| | const { |
| | componentMod: { renderToReadableStream }, |
| | } = ctx |
| | |
| | |
| | |
| | |
| | |
| |
|
| | |
| | |
| | const hasRuntimePrefetch = false |
| |
|
| | |
| | |
| | const abortSignal = null |
| |
|
| | const stageController = new StagedRenderingController( |
| | abortSignal, |
| | hasRuntimePrefetch |
| | ) |
| | const environmentName = () => { |
| | const currentStage = stageController.currentStage |
| | switch (currentStage) { |
| | case RenderStage.Before: |
| | case RenderStage.Static: |
| | return 'Prerender' |
| | case RenderStage.Runtime: |
| | case RenderStage.Dynamic: |
| | case RenderStage.Abandoned: |
| | return 'Server' |
| | default: |
| | currentStage satisfies never |
| | throw new InvariantError(`Invalid render stage: ${currentStage}`) |
| | } |
| | } |
| |
|
| | requestStore.stagedRendering = stageController |
| | requestStore.asyncApiPromises = createAsyncApiPromisesInDev( |
| | stageController, |
| | requestStore.cookies, |
| | requestStore.mutableCookies, |
| | requestStore.headers |
| | ) |
| |
|
| | const { clientModules } = getClientReferenceManifest() |
| | const rscPayload = await getPayload(requestStore) |
| |
|
| | return await workUnitAsyncStorage.run( |
| | requestStore, |
| | scheduleInSequentialTasks, |
| | () => { |
| | stageController.advanceStage(RenderStage.Static) |
| | return renderToReadableStream(rscPayload, clientModules, { |
| | ...options, |
| | environmentName, |
| | }) |
| | }, |
| | () => { |
| | stageController.advanceStage(RenderStage.Dynamic) |
| | } |
| | ) |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | async function generateDynamicFlightRenderResultWithStagesInDev( |
| | req: BaseNextRequest, |
| | ctx: AppRenderContext, |
| | initialRequestStore: RequestStore, |
| | createRequestStore: (() => RequestStore) | undefined, |
| | devFallbackParams: OpaqueFallbackRouteParams | null |
| | ): Promise<RenderResult> { |
| | const { |
| | htmlRequestId, |
| | renderOpts, |
| | requestId, |
| | workStore, |
| | componentMod: { createElement }, |
| | url, |
| | } = ctx |
| |
|
| | const { |
| | dev = false, |
| | onInstrumentationRequestError, |
| | setReactDebugChannel, |
| | setCacheStatus, |
| | nextExport = false, |
| | } = renderOpts |
| |
|
| | function onFlightDataRenderError(err: DigestedError, silenceLog: boolean) { |
| | return onInstrumentationRequestError?.( |
| | err, |
| | req, |
| | createErrorContext(ctx, 'react-server-components-payload'), |
| | silenceLog |
| | ) |
| | } |
| |
|
| | const onError = createReactServerErrorHandler( |
| | dev, |
| | nextExport, |
| | workStore.reactServerErrorsByDigest, |
| | onFlightDataRenderError |
| | ) |
| |
|
| | |
| | |
| | const shouldValidate = |
| | !isBypassingCachesInDev(renderOpts, initialRequestStore) && |
| | initialRequestStore.isHmrRefresh === true |
| |
|
| | const getPayload = async (requestStore: RequestStore) => { |
| | const payload: RSCPayload & |
| | RSCPayloadDevProperties & |
| | RSCInitialPayloadPartialDev = await workUnitAsyncStorage.run( |
| | requestStore, |
| | generateDynamicRSCPayload, |
| | ctx, |
| | undefined |
| | ) |
| |
|
| | if (isBypassingCachesInDev(renderOpts, requestStore)) { |
| | |
| | |
| | payload._bypassCachesInDev = createElement(WarnForBypassCachesInDev, { |
| | route: workStore.route, |
| | }) |
| | } else if (shouldValidate) { |
| | |
| | |
| | payload.c = prepareInitialCanonicalUrl(url) |
| | } |
| |
|
| | return payload |
| | } |
| |
|
| | let debugChannel: DebugChannelPair | undefined |
| | let stream: ReadableStream<Uint8Array> |
| |
|
| | if ( |
| | |
| | |
| | createRequestStore && |
| | |
| | |
| | !isBypassingCachesInDev(renderOpts, initialRequestStore) |
| | ) { |
| | |
| | |
| | if (setCacheStatus) { |
| | setCacheStatus('ready', htmlRequestId) |
| | } |
| |
|
| | const { |
| | stream: serverStream, |
| | accumulatedChunksPromise, |
| | staticInterruptReason, |
| | runtimeInterruptReason, |
| | staticStageEndTime, |
| | runtimeStageEndTime, |
| | debugChannel: returnedDebugChannel, |
| | requestStore: finalRequestStore, |
| | } = await renderWithRestartOnCacheMissInDev( |
| | ctx, |
| | initialRequestStore, |
| | createRequestStore, |
| | getPayload, |
| | onError |
| | ) |
| |
|
| | if (shouldValidate) { |
| | let validationDebugChannelClient: Readable | undefined = undefined |
| | if (returnedDebugChannel) { |
| | const [t1, t2] = returnedDebugChannel.clientSide.readable.tee() |
| | returnedDebugChannel.clientSide.readable = t1 |
| | validationDebugChannelClient = nodeStreamFromReadableStream(t2) |
| | } |
| | consoleAsyncStorage.run( |
| | { dim: true }, |
| | spawnStaticShellValidationInDev, |
| | accumulatedChunksPromise, |
| | staticInterruptReason, |
| | runtimeInterruptReason, |
| | staticStageEndTime, |
| | runtimeStageEndTime, |
| | ctx, |
| | finalRequestStore, |
| | devFallbackParams, |
| | validationDebugChannelClient |
| | ) |
| | } |
| |
|
| | debugChannel = returnedDebugChannel |
| | stream = serverStream |
| | } else { |
| | |
| | |
| |
|
| | |
| | if (setCacheStatus) { |
| | setCacheStatus('bypass', htmlRequestId) |
| | } |
| |
|
| | debugChannel = setReactDebugChannel && createDebugChannel() |
| |
|
| | stream = await stagedRenderToReadableStreamWithoutCachesInDev( |
| | ctx, |
| | initialRequestStore, |
| | getPayload, |
| | { |
| | onError: onError, |
| | filterStackFrame, |
| | debugChannel: debugChannel?.serverSide, |
| | } |
| | ) |
| | } |
| |
|
| | if (debugChannel && setReactDebugChannel) { |
| | setReactDebugChannel(debugChannel.clientSide, htmlRequestId, requestId) |
| | } |
| |
|
| | return new FlightRenderResult(stream, { |
| | fetchMetrics: workStore.fetchMetrics, |
| | }) |
| | } |
| |
|
| | async function generateRuntimePrefetchResult( |
| | req: BaseNextRequest, |
| | ctx: AppRenderContext, |
| | requestStore: RequestStore |
| | ): Promise<RenderResult> { |
| | const { workStore, renderOpts } = ctx |
| | const { nextExport = false, onInstrumentationRequestError } = renderOpts |
| |
|
| | function onFlightDataRenderError(err: DigestedError, silenceLog: boolean) { |
| | return onInstrumentationRequestError?.( |
| | err, |
| | req, |
| | |
| | createErrorContext(ctx, 'react-server-components-payload'), |
| | silenceLog |
| | ) |
| | } |
| |
|
| | const onError = createReactServerErrorHandler( |
| | false, |
| | nextExport, |
| | workStore.reactServerErrorsByDigest, |
| | onFlightDataRenderError |
| | ) |
| |
|
| | const metadata: AppPageRenderResultMetadata = {} |
| |
|
| | |
| | |
| | const runtimePrefetchSentinel = Math.floor( |
| | Math.random() * Number.MAX_SAFE_INTEGER |
| | ) |
| |
|
| | const generatePayload = () => |
| | generateDynamicRSCPayload(ctx, { runtimePrefetchSentinel }) |
| |
|
| | const { |
| | componentMod: { |
| | routeModule: { |
| | userland: { loaderTree }, |
| | }, |
| | }, |
| | getDynamicParamFromSegment, |
| | } = ctx |
| | const rootParams = getRootParams(loaderTree, getDynamicParamFromSegment) |
| |
|
| | |
| | |
| | const prerenderResumeDataCache = createPrerenderResumeDataCache() |
| | |
| | const renderResumeDataCache = null |
| |
|
| | await prospectiveRuntimeServerPrerender( |
| | ctx, |
| | generatePayload, |
| | prerenderResumeDataCache, |
| | renderResumeDataCache, |
| | rootParams, |
| | requestStore.headers, |
| | requestStore.cookies, |
| | requestStore.draftMode |
| | ) |
| |
|
| | const response = await finalRuntimeServerPrerender( |
| | ctx, |
| | generatePayload, |
| | prerenderResumeDataCache, |
| | renderResumeDataCache, |
| | rootParams, |
| | requestStore.headers, |
| | requestStore.cookies, |
| | requestStore.draftMode, |
| | onError, |
| | runtimePrefetchSentinel |
| | ) |
| |
|
| | applyMetadataFromPrerenderResult(response, metadata, workStore) |
| | metadata.fetchMetrics = ctx.workStore.fetchMetrics |
| |
|
| | return new FlightRenderResult(response.result.prelude, metadata) |
| | } |
| |
|
| | async function prospectiveRuntimeServerPrerender( |
| | ctx: AppRenderContext, |
| | getPayload: () => any, |
| | prerenderResumeDataCache: PrerenderResumeDataCache | null, |
| | renderResumeDataCache: RenderResumeDataCache | null, |
| | rootParams: Params, |
| | headers: PrerenderStoreModernRuntime['headers'], |
| | cookies: PrerenderStoreModernRuntime['cookies'], |
| | draftMode: PrerenderStoreModernRuntime['draftMode'] |
| | ) { |
| | const { implicitTags, renderOpts, workStore } = ctx |
| | const { ComponentMod } = renderOpts |
| |
|
| | |
| | |
| | |
| | |
| | const initialServerPrerenderController = new AbortController() |
| |
|
| | |
| | |
| | |
| | const initialServerRenderController = new AbortController() |
| |
|
| | |
| | |
| | const cacheSignal = new CacheSignal() |
| |
|
| | const initialServerPrerenderStore: PrerenderStoreModernRuntime = { |
| | type: 'prerender-runtime', |
| | phase: 'render', |
| | rootParams, |
| | implicitTags, |
| | renderSignal: initialServerRenderController.signal, |
| | controller: initialServerPrerenderController, |
| | |
| | |
| | |
| | cacheSignal, |
| | |
| | dynamicTracking: null, |
| | |
| | |
| | revalidate: 1, |
| | expire: 0, |
| | stale: INFINITE_CACHE, |
| | tags: [...implicitTags.tags], |
| | renderResumeDataCache, |
| | prerenderResumeDataCache, |
| | hmrRefreshHash: undefined, |
| | |
| | runtimeStagePromise: null, |
| | |
| | headers, |
| | cookies, |
| | draftMode, |
| | } |
| |
|
| | const { clientModules } = getClientReferenceManifest() |
| |
|
| | |
| | |
| | const initialServerPayload = await workUnitAsyncStorage.run( |
| | initialServerPrerenderStore, |
| | getPayload |
| | ) |
| |
|
| | const pendingInitialServerResult = workUnitAsyncStorage.run( |
| | initialServerPrerenderStore, |
| | ComponentMod.prerender, |
| | initialServerPayload, |
| | clientModules, |
| | { |
| | filterStackFrame, |
| | onError: (err) => { |
| | const digest = getDigestForWellKnownError(err) |
| |
|
| | if (digest) { |
| | return digest |
| | } |
| |
|
| | if (initialServerPrerenderController.signal.aborted) { |
| | |
| | |
| | return |
| | } else if ( |
| | process.env.NEXT_DEBUG_BUILD || |
| | process.env.__NEXT_VERBOSE_LOGGING |
| | ) { |
| | printDebugThrownValueForProspectiveRender( |
| | err, |
| | workStore.route, |
| | Phase.ProspectiveRender |
| | ) |
| | } |
| | }, |
| | |
| | |
| | |
| | signal: initialServerRenderController.signal, |
| | } |
| | ) |
| |
|
| | |
| | trackPendingModules(cacheSignal) |
| | await cacheSignal.cacheReady() |
| |
|
| | initialServerRenderController.abort() |
| | initialServerPrerenderController.abort() |
| |
|
| | |
| | |
| | if (workStore.invalidDynamicUsageError) { |
| | throw workStore.invalidDynamicUsageError |
| | } |
| |
|
| | try { |
| | return await createReactServerPrerenderResult(pendingInitialServerResult) |
| | } catch (err) { |
| | if ( |
| | initialServerRenderController.signal.aborted || |
| | initialServerPrerenderController.signal.aborted |
| | ) { |
| | |
| | } else if ( |
| | process.env.NEXT_DEBUG_BUILD || |
| | process.env.__NEXT_VERBOSE_LOGGING |
| | ) { |
| | |
| | |
| | printDebugThrownValueForProspectiveRender( |
| | err, |
| | workStore.route, |
| | Phase.ProspectiveRender |
| | ) |
| | } |
| | return null |
| | } |
| | } |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | function createRuntimePrefetchTransformStream( |
| | sentinel: number, |
| | isPartial: boolean, |
| | staleTime: number |
| | ): TransformStream<Uint8Array, Uint8Array> { |
| | const encoder = new TextEncoder() |
| |
|
| | |
| | |
| | const search = encoder.encode(`[${sentinel}]`) |
| | const first = search[0] |
| | const replace = encoder.encode(`[${isPartial},${staleTime}]`) |
| | const searchLen = search.length |
| |
|
| | let currentChunk: Uint8Array | null = null |
| | let found = false |
| |
|
| | function processChunk( |
| | controller: TransformStreamDefaultController<Uint8Array>, |
| | nextChunk: null | Uint8Array |
| | ) { |
| | if (found) { |
| | if (nextChunk) { |
| | controller.enqueue(nextChunk) |
| | } |
| | return |
| | } |
| |
|
| | if (currentChunk) { |
| | |
| | let exclusiveUpperBound = currentChunk.length - (searchLen - 1) |
| | if (nextChunk) { |
| | |
| | exclusiveUpperBound += Math.min(nextChunk.length, searchLen - 1) |
| | } |
| | if (exclusiveUpperBound < 1) { |
| | |
| | controller.enqueue(currentChunk) |
| | currentChunk = nextChunk |
| | return |
| | } |
| |
|
| | let currentIndex = currentChunk.indexOf(first) |
| |
|
| | |
| | candidateLoop: while ( |
| | -1 < currentIndex && |
| | currentIndex < exclusiveUpperBound |
| | ) { |
| | |
| | let matchIndex = 1 |
| | while (matchIndex < searchLen) { |
| | const candidateIndex = currentIndex + matchIndex |
| | const candidateValue = |
| | candidateIndex < currentChunk.length |
| | ? currentChunk[candidateIndex] |
| | : |
| | nextChunk![candidateIndex - currentChunk.length] |
| | if (candidateValue !== search[matchIndex]) { |
| | |
| | currentIndex = currentChunk.indexOf(first, currentIndex + 1) |
| | continue candidateLoop |
| | } |
| | matchIndex++ |
| | } |
| | |
| | found = true |
| | |
| | controller.enqueue(currentChunk.subarray(0, currentIndex)) |
| | |
| | controller.enqueue(replace) |
| | |
| | if (currentIndex + searchLen < currentChunk.length) { |
| | controller.enqueue(currentChunk.slice(currentIndex + searchLen)) |
| | } |
| | |
| | if (nextChunk) { |
| | |
| | const overflowBytes = currentIndex + searchLen - currentChunk.length |
| | const truncatedChunk = |
| | overflowBytes > 0 ? nextChunk!.subarray(overflowBytes) : nextChunk |
| | controller.enqueue(truncatedChunk) |
| | } |
| | |
| | currentChunk = null |
| | return |
| | } |
| | |
| | controller.enqueue(currentChunk) |
| | } |
| |
|
| | |
| | currentChunk = nextChunk |
| | } |
| |
|
| | return new TransformStream<Uint8Array, Uint8Array>({ |
| | transform(chunk, controller) { |
| | processChunk(controller, chunk) |
| | }, |
| | flush(controller) { |
| | processChunk(controller, null) |
| | }, |
| | }) |
| | } |
| |
|
| | async function finalRuntimeServerPrerender( |
| | ctx: AppRenderContext, |
| | getPayload: () => any, |
| | prerenderResumeDataCache: PrerenderResumeDataCache | null, |
| | renderResumeDataCache: RenderResumeDataCache | null, |
| | rootParams: Params, |
| | headers: PrerenderStoreModernRuntime['headers'], |
| | cookies: PrerenderStoreModernRuntime['cookies'], |
| | draftMode: PrerenderStoreModernRuntime['draftMode'], |
| | onError: (err: unknown) => string | undefined, |
| | runtimePrefetchSentinel: number |
| | ) { |
| | 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 { promise: runtimeStagePromise, resolve: resolveBlockedRuntimeAPIs } = |
| | createPromiseWithResolvers<void>() |
| |
|
| | const finalServerPrerenderStore: PrerenderStoreModernRuntime = { |
| | type: 'prerender-runtime', |
| | phase: 'render', |
| | rootParams, |
| | implicitTags, |
| | renderSignal: finalServerController.signal, |
| | controller: finalServerController, |
| | |
| | cacheSignal: null, |
| | dynamicTracking: serverDynamicTracking, |
| | |
| | |
| | revalidate: 1, |
| | expire: 0, |
| | stale: INFINITE_CACHE, |
| | tags: [...implicitTags.tags], |
| | prerenderResumeDataCache, |
| | renderResumeDataCache, |
| | hmrRefreshHash: undefined, |
| | |
| | runtimeStagePromise, |
| | |
| | headers, |
| | cookies, |
| | draftMode, |
| | } |
| |
|
| | const { clientModules } = getClientReferenceManifest() |
| |
|
| | const finalRSCPayload = await workUnitAsyncStorage.run( |
| | finalServerPrerenderStore, |
| | getPayload |
| | ) |
| |
|
| | let prerenderIsPending = true |
| | const result = await prerenderAndAbortInSequentialTasksWithStages( |
| | async () => { |
| | |
| | const prerenderResult = await workUnitAsyncStorage.run( |
| | finalServerPrerenderStore, |
| | ComponentMod.prerender, |
| | finalRSCPayload, |
| | clientModules, |
| | { |
| | filterStackFrame, |
| | onError, |
| | signal: finalServerController.signal, |
| | } |
| | ) |
| | prerenderIsPending = false |
| | return prerenderResult |
| | }, |
| | () => { |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | resolveBlockedRuntimeAPIs() |
| | }, |
| | () => { |
| | |
| | if (finalServerController.signal.aborted) { |
| | |
| | |
| | serverIsDynamic = true |
| | return |
| | } |
| |
|
| | if (prerenderIsPending) { |
| | |
| | |
| | serverIsDynamic = true |
| | } |
| | finalServerController.abort() |
| | } |
| | ) |
| |
|
| | |
| | |
| | const collectedStale = selectStaleTime(finalServerPrerenderStore.stale) |
| | result.prelude = result.prelude.pipeThrough( |
| | createRuntimePrefetchTransformStream( |
| | runtimePrefetchSentinel, |
| | serverIsDynamic, |
| | collectedStale |
| | ) |
| | ) |
| |
|
| | return { |
| | result, |
| | |
| | |
| | dynamicAccess: serverDynamicTracking, |
| | isPartial: serverIsDynamic, |
| | collectedRevalidate: finalServerPrerenderStore.revalidate, |
| | collectedExpire: finalServerPrerenderStore.expire, |
| | collectedStale, |
| | collectedTags: finalServerPrerenderStore.tags, |
| | } |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | function prepareInitialCanonicalUrl(url: RequestStore['url']) { |
| | return (url.pathname + url.search).split('/') |
| | } |
| |
|
| | function getRenderedSearch(query: NextParsedUrlQuery): string { |
| | |
| | |
| | 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))}` |
| | ) |
| | } |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | |
| | if (pairs.length === 0) { |
| | |
| | return '' |
| | } |
| | |
| | return '?' + pairs.join('&') |
| | } |
| |
|
| | |
| | async function getRSCPayload( |
| | tree: LoaderTree, |
| | ctx: AppRenderContext, |
| | is404: boolean |
| | ): Promise<InitialRSCPayload & { P: ReactNode }> { |
| | const injectedCSS = new Set<string>() |
| | const injectedJS = new Set<string>() |
| | const injectedFontPreloadTags = new Set<string>() |
| | let missingSlots: Set<string> | undefined |
| |
|
| | |
| | if (process.env.NODE_ENV === 'development') { |
| | missingSlots = new Set<string>() |
| | } |
| |
|
| | const { |
| | getDynamicParamFromSegment, |
| | query, |
| | appUsingSizeAdjustment, |
| | componentMod: { createMetadataComponents, createElement, Fragment }, |
| | url, |
| | workStore, |
| | } = ctx |
| |
|
| | const initialTree = createFlightRouterStateFromLoaderTree( |
| | tree, |
| | getDynamicParamFromSegment, |
| | query |
| | ) |
| | const serveStreamingMetadata = !!ctx.renderOpts.serveStreamingMetadata |
| | const hasGlobalNotFound = !!tree[2]['global-not-found'] |
| |
|
| | const { Viewport, Metadata, MetadataOutlet } = createMetadataComponents({ |
| | tree, |
| | |
| | |
| | |
| | |
| | |
| | errorType: is404 && !hasGlobalNotFound ? 'not-found' : undefined, |
| | parsedQuery: query, |
| | pathname: url.pathname, |
| | metadataContext: createMetadataContext(ctx.renderOpts), |
| | getDynamicParamFromSegment, |
| | workStore, |
| | serveStreamingMetadata, |
| | }) |
| |
|
| | const preloadCallbacks: PreloadCallbacks = [] |
| |
|
| | const seedData = await createComponentTree({ |
| | ctx, |
| | loaderTree: tree, |
| | parentParams: {}, |
| | injectedCSS, |
| | injectedJS, |
| | injectedFontPreloadTags, |
| | rootLayoutIncluded: false, |
| | missingSlots, |
| | preloadCallbacks, |
| | authInterrupts: ctx.renderOpts.experimental.authInterrupts, |
| | MetadataOutlet, |
| | }) |
| |
|
| | |
| | |
| | |
| | const varyHeader = ctx.res.getHeader('vary') |
| | const couldBeIntercepted = |
| | typeof varyHeader === 'string' && varyHeader.includes(NEXT_URL) |
| |
|
| | const initialHead = createElement( |
| | Fragment, |
| | { |
| | key: flightDataPathHeadKey, |
| | }, |
| | createElement(NonIndex, { |
| | createElement, |
| | pagePath: ctx.pagePath, |
| | statusCode: ctx.res.statusCode, |
| | isPossibleServerAction: ctx.isPossibleServerAction, |
| | }), |
| | createElement(Viewport, null), |
| | createElement(Metadata, null), |
| | appUsingSizeAdjustment |
| | ? createElement('meta', { |
| | name: 'next-size-adjust', |
| | content: '', |
| | }) |
| | : null |
| | ) |
| |
|
| | const { GlobalError, styles: globalErrorStyles } = await getGlobalErrorStyles( |
| | tree, |
| | ctx |
| | ) |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | const isPossiblyPartialHead = |
| | workStore.isStaticGeneration && |
| | ctx.renderOpts.experimental.isRoutePPREnabled === true |
| |
|
| | return { |
| | |
| | P: createElement(Preloads, { |
| | preloadCallbacks: preloadCallbacks, |
| | }), |
| | b: ctx.sharedContext.buildId, |
| | c: prepareInitialCanonicalUrl(url), |
| | q: getRenderedSearch(query), |
| | i: !!couldBeIntercepted, |
| | f: [ |
| | [ |
| | initialTree, |
| | seedData, |
| | initialHead, |
| | isPossiblyPartialHead, |
| | ] as FlightDataPath, |
| | ], |
| | m: missingSlots, |
| | G: [GlobalError, globalErrorStyles], |
| | S: workStore.isStaticGeneration, |
| | } |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | function Preloads({ preloadCallbacks }: { preloadCallbacks: Function[] }) { |
| | preloadCallbacks.forEach((preloadFn) => preloadFn()) |
| | return null |
| | } |
| |
|
| | |
| | async function getErrorRSCPayload( |
| | tree: LoaderTree, |
| | ctx: AppRenderContext, |
| | ssrError: unknown, |
| | errorType: MetadataErrorType | 'redirect' | undefined |
| | ) { |
| | const { |
| | getDynamicParamFromSegment, |
| | query, |
| | componentMod: { createMetadataComponents, createElement, Fragment }, |
| | url, |
| | workStore, |
| | } = ctx |
| |
|
| | const serveStreamingMetadata = !!ctx.renderOpts.serveStreamingMetadata |
| | const { Viewport, Metadata } = createMetadataComponents({ |
| | tree, |
| | parsedQuery: query, |
| | pathname: url.pathname, |
| | metadataContext: createMetadataContext(ctx.renderOpts), |
| | errorType, |
| | getDynamicParamFromSegment, |
| | workStore, |
| | serveStreamingMetadata: serveStreamingMetadata, |
| | }) |
| |
|
| | const initialHead = createElement( |
| | Fragment, |
| | { |
| | key: flightDataPathHeadKey, |
| | }, |
| | createElement(NonIndex, { |
| | createElement, |
| | pagePath: ctx.pagePath, |
| | statusCode: ctx.res.statusCode, |
| | isPossibleServerAction: ctx.isPossibleServerAction, |
| | }), |
| | createElement(Viewport, null), |
| | process.env.NODE_ENV === 'development' && |
| | createElement('meta', { |
| | name: 'next-error', |
| | content: 'not-found', |
| | }), |
| | createElement(Metadata, null) |
| | ) |
| |
|
| | const initialTree = createFlightRouterStateFromLoaderTree( |
| | tree, |
| | getDynamicParamFromSegment, |
| | query |
| | ) |
| |
|
| | let err: Error | undefined = undefined |
| | if (ssrError) { |
| | err = isError(ssrError) ? ssrError : new Error(ssrError + '') |
| | } |
| |
|
| | |
| | |
| | const seedData: CacheNodeSeedData = [ |
| | createElement( |
| | 'html', |
| | { |
| | id: '__next_error__', |
| | }, |
| | createElement('head', null), |
| | createElement( |
| | 'body', |
| | null, |
| | process.env.NODE_ENV !== 'production' && err |
| | ? createElement('template', { |
| | 'data-next-error-message': err.message, |
| | 'data-next-error-digest': 'digest' in err ? err.digest : '', |
| | 'data-next-error-stack': err.stack, |
| | }) |
| | : null |
| | ) |
| | ), |
| | {}, |
| | null, |
| | false, |
| | false, |
| | ] |
| |
|
| | const { GlobalError, styles: globalErrorStyles } = await getGlobalErrorStyles( |
| | tree, |
| | ctx |
| | ) |
| |
|
| | const isPossiblyPartialHead = |
| | workStore.isStaticGeneration && |
| | ctx.renderOpts.experimental.isRoutePPREnabled === true |
| |
|
| | return { |
| | b: ctx.sharedContext.buildId, |
| | c: prepareInitialCanonicalUrl(url), |
| | q: getRenderedSearch(query), |
| | m: undefined, |
| | i: false, |
| | f: [ |
| | [ |
| | initialTree, |
| | seedData, |
| | initialHead, |
| | isPossiblyPartialHead, |
| | ] as FlightDataPath, |
| | ], |
| | G: [GlobalError, globalErrorStyles], |
| | S: workStore.isStaticGeneration, |
| | } satisfies InitialRSCPayload |
| | } |
| |
|
| | |
| | function App<T>({ |
| | reactServerStream, |
| | reactDebugStream, |
| | debugEndTime, |
| | preinitScripts, |
| | ServerInsertedHTMLProvider, |
| | nonce, |
| | images, |
| | }: { |
| | |
| | reactServerStream: Readable | BinaryStreamOf<T> |
| | reactDebugStream: Readable | ReadableStream<Uint8Array> | undefined |
| | debugEndTime: number | undefined |
| | preinitScripts: () => void |
| | ServerInsertedHTMLProvider: ComponentType<{ |
| | children: JSX.Element |
| | }> |
| | images: RenderOpts['images'] |
| | nonce?: string |
| | }): JSX.Element { |
| | preinitScripts() |
| | const response = ReactClient.use( |
| | getFlightStream<InitialRSCPayload>( |
| | reactServerStream, |
| | reactDebugStream, |
| | debugEndTime, |
| | nonce |
| | ) |
| | ) |
| |
|
| | const initialState = createInitialRouterState({ |
| | |
| | |
| | navigatedAt: -1, |
| | initialFlightData: response.f, |
| | initialCanonicalUrlParts: response.c, |
| | initialRenderedSearch: response.q, |
| | |
| | |
| | location: null, |
| | }) |
| |
|
| | const actionQueue = createMutableActionQueue(initialState, null) |
| |
|
| | const { HeadManagerContext } = |
| | require('../../shared/lib/head-manager-context.shared-runtime') as typeof import('../../shared/lib/head-manager-context.shared-runtime') |
| |
|
| | return ( |
| | <HeadManagerContext.Provider |
| | value={{ |
| | appDir: true, |
| | nonce, |
| | }} |
| | > |
| | <ImageConfigContext.Provider value={images ?? imageConfigDefault}> |
| | <ServerInsertedHTMLProvider> |
| | <AppRouter actionQueue={actionQueue} globalErrorState={response.G} /> |
| | </ServerInsertedHTMLProvider> |
| | </ImageConfigContext.Provider> |
| | </HeadManagerContext.Provider> |
| | ) |
| | |
| | } |
| |
|
| | |
| | |
| | |
| | function ErrorApp<T>({ |
| | reactServerStream, |
| | preinitScripts, |
| | ServerInsertedHTMLProvider, |
| | nonce, |
| | images, |
| | }: { |
| | reactServerStream: BinaryStreamOf<T> |
| | preinitScripts: () => void |
| | ServerInsertedHTMLProvider: ComponentType<{ |
| | children: JSX.Element |
| | }> |
| | nonce?: string |
| | images: RenderOpts['images'] |
| | }): JSX.Element { |
| | |
| | preinitScripts() |
| | const response = ReactClient.use( |
| | getFlightStream<InitialRSCPayload>( |
| | reactServerStream, |
| | undefined, |
| | undefined, |
| | nonce |
| | ) |
| | ) |
| |
|
| | const initialState = createInitialRouterState({ |
| | |
| | |
| | navigatedAt: -1, |
| | initialFlightData: response.f, |
| | initialCanonicalUrlParts: response.c, |
| | initialRenderedSearch: response.q, |
| | |
| | |
| | location: null, |
| | }) |
| |
|
| | const actionQueue = createMutableActionQueue(initialState, null) |
| |
|
| | return ( |
| | <ImageConfigContext.Provider value={images ?? imageConfigDefault}> |
| | <ServerInsertedHTMLProvider> |
| | <AppRouter actionQueue={actionQueue} globalErrorState={response.G} /> |
| | </ServerInsertedHTMLProvider> |
| | </ImageConfigContext.Provider> |
| | ) |
| | |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | |
| | export type BinaryStreamOf<T> = ReadableStream<Uint8Array> |
| |
|
| | async function renderToHTMLOrFlightImpl( |
| | req: BaseNextRequest, |
| | res: BaseNextResponse, |
| | url: ReturnType<typeof parseRelativeUrl>, |
| | pagePath: string, |
| | query: NextParsedUrlQuery, |
| | renderOpts: RenderOpts, |
| | workStore: WorkStore, |
| | parsedRequestHeaders: ParsedRequestHeaders, |
| | postponedState: PostponedState | null, |
| | serverComponentsHmrCache: ServerComponentsHmrCache | undefined, |
| | sharedContext: AppSharedContext, |
| | interpolatedParams: Params, |
| | fallbackRouteParams: OpaqueFallbackRouteParams | null |
| | ) { |
| | const isNotFoundPath = pagePath === '/404' |
| | if (isNotFoundPath) { |
| | res.statusCode = 404 |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | const requestTimestamp = Date.now() |
| |
|
| | const { |
| | ComponentMod, |
| | nextFontManifest, |
| | serverActions, |
| | assetPrefix = '', |
| | enableTainting, |
| | cacheComponents, |
| | } = renderOpts |
| |
|
| | |
| | |
| | if (ComponentMod.__next_app__) { |
| | const instrumented = wrapClientComponentLoader(ComponentMod) |
| |
|
| | |
| | |
| | |
| | |
| |
|
| | const shouldTrackModuleLoading = () => { |
| | if (!cacheComponents) { |
| | return false |
| | } |
| | if (renderOpts.dev) { |
| | return true |
| | } |
| | const workUnitStore = workUnitAsyncStorage.getStore() |
| |
|
| | if (!workUnitStore) { |
| | return false |
| | } |
| |
|
| | switch (workUnitStore.type) { |
| | case 'prerender': |
| | case 'prerender-client': |
| | case 'prerender-runtime': |
| | case 'cache': |
| | case 'private-cache': |
| | return true |
| | case 'prerender-ppr': |
| | case 'prerender-legacy': |
| | case 'request': |
| | case 'unstable-cache': |
| | return false |
| | default: |
| | workUnitStore satisfies never |
| | } |
| | } |
| |
|
| | const __next_require__: typeof instrumented.require = (...args) => { |
| | const exportsOrPromise = instrumented.require(...args) |
| | if (shouldTrackModuleLoading()) { |
| | |
| | trackPendingImport(exportsOrPromise) |
| | } |
| | return exportsOrPromise |
| | } |
| | |
| | globalThis.__next_require__ = __next_require__ |
| |
|
| | const __next_chunk_load__: typeof instrumented.loadChunk = (...args) => { |
| | const loadingChunk = instrumented.loadChunk(...args) |
| | if (shouldTrackModuleLoading()) { |
| | trackPendingChunkLoad(loadingChunk) |
| | } |
| | return loadingChunk |
| | } |
| | |
| | globalThis.__next_chunk_load__ = __next_chunk_load__ |
| | } |
| |
|
| | if ( |
| | process.env.NODE_ENV === 'development' && |
| | renderOpts.setIsrStatus && |
| | !cacheComponents |
| | ) { |
| | |
| | const { pathname } = new URL(req.url || '/', 'http://n') |
| | renderOpts.setIsrStatus( |
| | pathname, |
| | |
| | process.env.NEXT_RUNTIME === 'edge' ? false : undefined |
| | ) |
| | } |
| |
|
| | if ( |
| | |
| | |
| | process.env.NEXT_RUNTIME !== 'edge' && |
| | isNodeNextRequest(req) |
| | ) { |
| | res.onClose(() => { |
| | |
| | |
| | workStore.shouldTrackFetchMetrics = false |
| | }) |
| |
|
| | req.originalRequest.on('end', () => { |
| | if ('performance' in globalThis) { |
| | const metrics = getClientComponentLoaderMetrics({ reset: true }) |
| | if (metrics) { |
| | getTracer() |
| | .startSpan(NextNodeServerSpan.clientComponentLoading, { |
| | startTime: metrics.clientComponentLoadStart, |
| | attributes: { |
| | 'next.clientComponentLoadCount': |
| | metrics.clientComponentLoadCount, |
| | 'next.span_type': NextNodeServerSpan.clientComponentLoading, |
| | }, |
| | }) |
| | .end( |
| | metrics.clientComponentLoadStart + |
| | metrics.clientComponentLoadTimes |
| | ) |
| | } |
| | } |
| | }) |
| | } |
| |
|
| | const metadata: AppPageRenderResultMetadata = { |
| | statusCode: isNotFoundPath ? 404 : undefined, |
| | } |
| |
|
| | const appUsingSizeAdjustment = !!nextFontManifest?.appUsingSizeAdjust |
| |
|
| | ComponentMod.patchFetch() |
| |
|
| | |
| | const { |
| | routeModule: { |
| | userland: { loaderTree }, |
| | }, |
| | taintObjectReference, |
| | } = ComponentMod |
| | if (enableTainting) { |
| | taintObjectReference( |
| | 'Do not pass process.env to Client Components since it will leak sensitive data', |
| | process.env |
| | ) |
| | } |
| |
|
| | workStore.fetchMetrics = [] |
| | metadata.fetchMetrics = workStore.fetchMetrics |
| |
|
| | |
| | query = { ...query } |
| | stripInternalQueries(query) |
| |
|
| | const { isStaticGeneration } = workStore |
| |
|
| | let requestId: string |
| | let htmlRequestId: string |
| |
|
| | const { |
| | flightRouterState, |
| | isPrefetchRequest, |
| | isRuntimePrefetchRequest, |
| | isRSCRequest, |
| | isHmrRefresh, |
| | nonce, |
| | } = parsedRequestHeaders |
| |
|
| | if (parsedRequestHeaders.requestId) { |
| | |
| | requestId = parsedRequestHeaders.requestId |
| | } else { |
| | |
| | if (isStaticGeneration) { |
| | requestId = Buffer.from( |
| | await crypto.subtle.digest('SHA-1', Buffer.from(req.url)) |
| | ).toString('hex') |
| | } else if (process.env.NEXT_RUNTIME === 'edge') { |
| | requestId = crypto.randomUUID() |
| | } else { |
| | requestId = ( |
| | require('next/dist/compiled/nanoid') as typeof import('next/dist/compiled/nanoid') |
| | ).nanoid() |
| | } |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | |
| | htmlRequestId = parsedRequestHeaders.htmlRequestId || requestId |
| |
|
| | const getDynamicParamFromSegment = makeGetDynamicParamFromSegment( |
| | interpolatedParams, |
| | fallbackRouteParams |
| | ) |
| |
|
| | const isPossibleActionRequest = getIsPossibleServerAction(req) |
| |
|
| | const implicitTags = await getImplicitTags( |
| | workStore.page, |
| | url, |
| | fallbackRouteParams |
| | ) |
| |
|
| | const ctx: AppRenderContext = { |
| | componentMod: ComponentMod, |
| | url, |
| | renderOpts, |
| | workStore, |
| | parsedRequestHeaders, |
| | getDynamicParamFromSegment, |
| | query, |
| | isPrefetch: isPrefetchRequest, |
| | isPossibleServerAction: isPossibleActionRequest, |
| | requestTimestamp, |
| | appUsingSizeAdjustment, |
| | flightRouterState, |
| | requestId, |
| | htmlRequestId, |
| | pagePath, |
| | assetPrefix, |
| | isNotFoundPath, |
| | nonce, |
| | res, |
| | sharedContext, |
| | implicitTags, |
| | } |
| |
|
| | getTracer().setRootSpanAttribute('next.route', pagePath) |
| |
|
| | if (isStaticGeneration) { |
| | |
| | |
| | const prerenderToStreamWithTracing = getTracer().wrap( |
| | AppRenderSpan.getBodyResult, |
| | { |
| | spanName: `prerender route (app) ${pagePath}`, |
| | attributes: { |
| | 'next.route': pagePath, |
| | }, |
| | }, |
| | prerenderToStream |
| | ) |
| |
|
| | const response = await prerenderToStreamWithTracing( |
| | req, |
| | res, |
| | ctx, |
| | metadata, |
| | loaderTree, |
| | fallbackRouteParams |
| | ) |
| |
|
| | |
| | |
| | |
| | if ( |
| | response.dynamicAccess && |
| | accessedDynamicData(response.dynamicAccess) && |
| | renderOpts.isDebugDynamicAccesses |
| | ) { |
| | warn('The following dynamic usage was detected:') |
| | for (const access of formatDynamicAPIAccesses(response.dynamicAccess)) { |
| | warn(access) |
| | } |
| | } |
| |
|
| | |
| | |
| | if (workStore.invalidDynamicUsageError) { |
| | logDisallowedDynamicError(workStore, workStore.invalidDynamicUsageError) |
| | throw new StaticGenBailoutError() |
| | } |
| | if (response.digestErrorsMap.size) { |
| | const buildFailingError = response.digestErrorsMap.values().next().value |
| | if (buildFailingError) throw buildFailingError |
| | } |
| | |
| | if (response.ssrErrors.length) { |
| | const buildFailingError = response.ssrErrors.find((err) => |
| | isUserLandError(err) |
| | ) |
| | if (buildFailingError) throw buildFailingError |
| | } |
| |
|
| | const options: RenderResultOptions = { |
| | metadata, |
| | contentType: HTML_CONTENT_TYPE_HEADER, |
| | } |
| |
|
| | |
| | const maybeRevalidatesPromise = executeRevalidates(workStore) |
| | if (maybeRevalidatesPromise !== false) { |
| | const revalidatesPromise = maybeRevalidatesPromise.finally(() => { |
| | if (process.env.NEXT_PRIVATE_DEBUG_CACHE) { |
| | console.log('pending revalidates promise finished for:', url.href) |
| | } |
| | }) |
| | if (renderOpts.waitUntil) { |
| | renderOpts.waitUntil(revalidatesPromise) |
| | } else { |
| | options.waitUntil = revalidatesPromise |
| | } |
| | } |
| |
|
| | applyMetadataFromPrerenderResult(response, metadata, workStore) |
| |
|
| | if (response.renderResumeDataCache) { |
| | metadata.renderResumeDataCache = response.renderResumeDataCache |
| | } |
| |
|
| | return new RenderResult(await streamToString(response.stream), options) |
| | } else { |
| | |
| | const renderResumeDataCache = |
| | renderOpts.renderResumeDataCache ?? |
| | postponedState?.renderResumeDataCache ?? |
| | null |
| |
|
| | const rootParams = getRootParams(loaderTree, ctx.getDynamicParamFromSegment) |
| | const devFallbackParams = getRequestMeta(req, 'devFallbackParams') || null |
| |
|
| | const createRequestStore = createRequestStoreForRender.bind( |
| | null, |
| | req, |
| | res, |
| | url, |
| | rootParams, |
| | implicitTags, |
| | renderOpts.onUpdateCookies, |
| | renderOpts.previewProps, |
| | isHmrRefresh, |
| | serverComponentsHmrCache, |
| | renderResumeDataCache, |
| | devFallbackParams |
| | ) |
| | const requestStore = createRequestStore() |
| |
|
| | if ( |
| | process.env.NODE_ENV === 'development' && |
| | renderOpts.setIsrStatus && |
| | !cacheComponents && |
| | |
| | |
| | |
| | |
| | process.env.NEXT_RUNTIME !== 'edge' && |
| | isNodeNextRequest(req) |
| | ) { |
| | const setIsrStatus = renderOpts.setIsrStatus |
| | req.originalRequest.on('end', () => { |
| | const { pathname } = new URL(req.url || '/', 'http://n') |
| | const isStatic = !requestStore.usedDynamic && !workStore.forceDynamic |
| | setIsrStatus(pathname, isStatic) |
| | }) |
| | } |
| |
|
| | if (isRSCRequest) { |
| | if (isRuntimePrefetchRequest) { |
| | return generateRuntimePrefetchResult(req, ctx, requestStore) |
| | } else { |
| | if ( |
| | process.env.NODE_ENV === 'development' && |
| | process.env.NEXT_RUNTIME !== 'edge' && |
| | cacheComponents |
| | ) { |
| | return generateDynamicFlightRenderResultWithStagesInDev( |
| | req, |
| | ctx, |
| | requestStore, |
| | createRequestStore, |
| | devFallbackParams |
| | ) |
| | } else { |
| | return generateDynamicFlightRenderResult(req, ctx, requestStore) |
| | } |
| | } |
| | } |
| |
|
| | let didExecuteServerAction = false |
| | let formState: null | any = null |
| | if (isPossibleActionRequest) { |
| | |
| | requestStore.renderResumeDataCache = null |
| |
|
| | |
| | const actionRequestResult = await handleAction({ |
| | req, |
| | res, |
| | ComponentMod, |
| | generateFlight: generateDynamicFlightRenderResult, |
| | workStore, |
| | requestStore, |
| | serverActions, |
| | ctx, |
| | metadata, |
| | }) |
| |
|
| | if (actionRequestResult) { |
| | if (actionRequestResult.type === 'not-found') { |
| | const notFoundLoaderTree = createNotFoundLoaderTree(loaderTree) |
| | res.statusCode = 404 |
| | metadata.statusCode = 404 |
| | const stream = await renderToStream( |
| | requestStore, |
| | req, |
| | res, |
| | ctx, |
| | notFoundLoaderTree, |
| | formState, |
| | postponedState, |
| | metadata, |
| | undefined, |
| | devFallbackParams |
| | ) |
| |
|
| | return new RenderResult(stream, { |
| | metadata, |
| | contentType: HTML_CONTENT_TYPE_HEADER, |
| | }) |
| | } else if (actionRequestResult.type === 'done') { |
| | if (actionRequestResult.result) { |
| | actionRequestResult.result.assignMetadata(metadata) |
| | return actionRequestResult.result |
| | } else if (actionRequestResult.formState) { |
| | formState = actionRequestResult.formState |
| | } |
| | } |
| | } |
| |
|
| | didExecuteServerAction = true |
| | |
| | requestStore.renderResumeDataCache = renderResumeDataCache |
| | } |
| |
|
| | const options: RenderResultOptions = { |
| | metadata, |
| | contentType: HTML_CONTENT_TYPE_HEADER, |
| | } |
| |
|
| | const stream = await renderToStream( |
| | |
| | |
| | requestStore, |
| | req, |
| | res, |
| | ctx, |
| | loaderTree, |
| | formState, |
| | postponedState, |
| | metadata, |
| | |
| | |
| | |
| | |
| | |
| | didExecuteServerAction ? undefined : createRequestStore, |
| | devFallbackParams |
| | ) |
| |
|
| | |
| | |
| | |
| | if (workStore.invalidDynamicUsageError && workStore.dev) { |
| | throw workStore.invalidDynamicUsageError |
| | } |
| |
|
| | |
| | const maybeRevalidatesPromise = executeRevalidates(workStore) |
| | if (maybeRevalidatesPromise !== false) { |
| | const revalidatesPromise = maybeRevalidatesPromise.finally(() => { |
| | if (process.env.NEXT_PRIVATE_DEBUG_CACHE) { |
| | console.log('pending revalidates promise finished for:', url.href) |
| | } |
| | }) |
| | if (renderOpts.waitUntil) { |
| | renderOpts.waitUntil(revalidatesPromise) |
| | } else { |
| | options.waitUntil = revalidatesPromise |
| | } |
| | } |
| |
|
| | |
| | return new RenderResult(stream, options) |
| | } |
| | } |
| |
|
| | export type AppPageRender = ( |
| | req: BaseNextRequest, |
| | res: BaseNextResponse, |
| | pagePath: string, |
| | query: NextParsedUrlQuery, |
| | fallbackRouteParams: OpaqueFallbackRouteParams | null, |
| | renderOpts: RenderOpts, |
| | serverComponentsHmrCache: ServerComponentsHmrCache | undefined, |
| | sharedContext: AppSharedContext |
| | ) => Promise<RenderResult<AppPageRenderResultMetadata>> |
| |
|
| | export const renderToHTMLOrFlight: AppPageRender = ( |
| | req, |
| | res, |
| | pagePath, |
| | query, |
| | fallbackRouteParams, |
| | renderOpts, |
| | serverComponentsHmrCache, |
| | sharedContext |
| | ) => { |
| | if (!req.url) { |
| | throw new Error('Invalid URL') |
| | } |
| |
|
| | const url = parseRelativeUrl(req.url, undefined, false) |
| |
|
| | |
| | |
| | const parsedRequestHeaders = parseRequestHeaders(req.headers, { |
| | isRoutePPREnabled: renderOpts.experimental.isRoutePPREnabled === true, |
| | previewModeId: renderOpts.previewProps?.previewModeId, |
| | }) |
| |
|
| | const { isPrefetchRequest, previouslyRevalidatedTags, nonce } = |
| | parsedRequestHeaders |
| |
|
| | let interpolatedParams: Params |
| | let postponedState: PostponedState | null = null |
| |
|
| | |
| | |
| | if (typeof renderOpts.postponed === 'string') { |
| | if (fallbackRouteParams) { |
| | throw new InvariantError( |
| | 'postponed state should not be provided when fallback params are provided' |
| | ) |
| | } |
| |
|
| | interpolatedParams = interpolateParallelRouteParams( |
| | renderOpts.ComponentMod.routeModule.userland.loaderTree, |
| | renderOpts.params ?? {}, |
| | pagePath, |
| | fallbackRouteParams |
| | ) |
| |
|
| | postponedState = parsePostponedState( |
| | renderOpts.postponed, |
| | interpolatedParams, |
| | renderOpts.experimental.maxPostponedStateSizeBytes |
| | ) |
| | } else { |
| | interpolatedParams = interpolateParallelRouteParams( |
| | renderOpts.ComponentMod.routeModule.userland.loaderTree, |
| | renderOpts.params ?? {}, |
| | pagePath, |
| | fallbackRouteParams |
| | ) |
| | } |
| |
|
| | if ( |
| | postponedState?.renderResumeDataCache && |
| | renderOpts.renderResumeDataCache |
| | ) { |
| | throw new InvariantError( |
| | 'postponed state and dev warmup immutable resume data cache should not be provided together' |
| | ) |
| | } |
| |
|
| | const workStore = createWorkStore({ |
| | page: renderOpts.routeModule.definition.page, |
| | renderOpts, |
| | |
| | isPrefetchRequest, |
| | buildId: sharedContext.buildId, |
| | previouslyRevalidatedTags, |
| | nonce, |
| | }) |
| |
|
| | return workAsyncStorage.run( |
| | workStore, |
| | |
| | renderToHTMLOrFlightImpl, |
| | |
| | req, |
| | res, |
| | url, |
| | pagePath, |
| | query, |
| | renderOpts, |
| | workStore, |
| | parsedRequestHeaders, |
| | postponedState, |
| | serverComponentsHmrCache, |
| | sharedContext, |
| | interpolatedParams, |
| | fallbackRouteParams |
| | ) |
| | } |
| |
|
| | function applyMetadataFromPrerenderResult( |
| | response: Pick< |
| | PrerenderToStreamResult, |
| | | 'collectedExpire' |
| | | 'collectedRevalidate' |
| | | 'collectedStale' |
| | | 'collectedTags' |
| | >, |
| | metadata: AppPageRenderResultMetadata, |
| | workStore: WorkStore |
| | ) { |
| | if (response.collectedTags) { |
| | metadata.fetchTags = response.collectedTags.join(',') |
| | } |
| |
|
| | |
| | const staleHeader = String(response.collectedStale) |
| | metadata.headers ??= {} |
| | metadata.headers[NEXT_ROUTER_STALE_TIME_HEADER] = staleHeader |
| |
|
| | |
| | |
| | if (workStore.forceStatic === false || response.collectedRevalidate === 0) { |
| | metadata.cacheControl = { revalidate: 0, expire: undefined } |
| | } else { |
| | |
| | metadata.cacheControl = { |
| | revalidate: |
| | response.collectedRevalidate >= INFINITE_CACHE |
| | ? false |
| | : response.collectedRevalidate, |
| | expire: |
| | response.collectedExpire >= INFINITE_CACHE |
| | ? undefined |
| | : response.collectedExpire, |
| | } |
| | } |
| |
|
| | |
| | if (metadata.cacheControl.revalidate === 0) { |
| | metadata.staticBailoutInfo = { |
| | description: workStore.dynamicUsageDescription, |
| | stack: workStore.dynamicUsageStack, |
| | } |
| | } |
| | } |
| |
|
| | type RSCPayloadDevProperties = { |
| | |
| | _validation?: Promise<ReactNode> |
| | _bypassCachesInDev?: ReactNode |
| | } |
| |
|
| | type RSCInitialPayloadPartialDev = { |
| | c?: InitialRSCPayload['c'] |
| | } |
| |
|
| | async function renderToStream( |
| | requestStore: RequestStore, |
| | req: BaseNextRequest, |
| | res: BaseNextResponse, |
| | ctx: AppRenderContext, |
| | tree: LoaderTree, |
| | formState: any, |
| | postponedState: PostponedState | null, |
| | metadata: AppPageRenderResultMetadata, |
| | createRequestStore: (() => RequestStore) | undefined, |
| | devFallbackParams: OpaqueFallbackRouteParams | null |
| | ): Promise<ReadableStream<Uint8Array>> { |
| | |
| | const { |
| | assetPrefix, |
| | htmlRequestId, |
| | nonce, |
| | pagePath, |
| | renderOpts, |
| | requestId, |
| | workStore, |
| | } = ctx |
| |
|
| | const { |
| | basePath, |
| | buildManifest, |
| | ComponentMod: { |
| | createElement, |
| | renderToReadableStream: serverRenderToReadableStream, |
| | }, |
| | crossOrigin, |
| | dev = false, |
| | experimental, |
| | nextExport = false, |
| | onInstrumentationRequestError, |
| | page, |
| | reactMaxHeadersLength, |
| | setReactDebugChannel, |
| | shouldWaitOnAllReady, |
| | subresourceIntegrityManifest, |
| | supportsDynamicResponse, |
| | cacheComponents, |
| | } = renderOpts |
| |
|
| | const { ServerInsertedHTMLProvider, renderServerInsertedHTML } = |
| | createServerInsertedHTML() |
| | const getServerInsertedMetadata = createServerInsertedMetadata(nonce) |
| |
|
| | const tracingMetadata = getTracedMetadata( |
| | getTracer().getTracePropagationData(), |
| | experimental.clientTraceMetadata |
| | ) |
| |
|
| | const polyfills: JSX.IntrinsicElements['script'][] = |
| | buildManifest.polyfillFiles |
| | .filter( |
| | (polyfill) => |
| | polyfill.endsWith('.js') && !polyfill.endsWith('.module.js') |
| | ) |
| | .map((polyfill) => ({ |
| | src: `${assetPrefix}/_next/${polyfill}${getAssetQueryString( |
| | ctx, |
| | false |
| | )}`, |
| | integrity: subresourceIntegrityManifest?.[polyfill], |
| | crossOrigin, |
| | noModule: true, |
| | nonce, |
| | })) |
| |
|
| | const [preinitScripts, bootstrapScript] = getRequiredScripts( |
| | buildManifest, |
| | |
| | |
| | assetPrefix, |
| | crossOrigin, |
| | subresourceIntegrityManifest, |
| | getAssetQueryString(ctx, true), |
| | nonce, |
| | page |
| | ) |
| |
|
| | |
| | |
| | const bootstrapScriptContent = |
| | process.env.NODE_ENV !== 'production' |
| | ? `self.__next_r=${JSON.stringify(requestId)}` |
| | : undefined |
| |
|
| | |
| | |
| | |
| | |
| | const renderSpan = getTracer().startSpan( |
| | `render route (app) ${pagePath}` as any, |
| | { |
| | attributes: { |
| | 'next.span_name': `render route (app) ${pagePath}`, |
| | 'next.span_type': AppRenderSpan.getBodyResult, |
| | 'next.route': pagePath, |
| | }, |
| | } |
| | ) |
| |
|
| | |
| | const endSpanWithError = (err: unknown) => { |
| | if (!renderSpan.isRecording()) return |
| | if (err instanceof Error) { |
| | renderSpan.recordException(err) |
| | renderSpan.setAttribute('error.type', err.name) |
| | } |
| | renderSpan.setStatus({ |
| | code: SpanStatusCode.ERROR, |
| | message: err instanceof Error ? err.message : undefined, |
| | }) |
| | renderSpan.end() |
| | } |
| |
|
| | |
| | |
| | return getTracer().withSpan(renderSpan, async () => { |
| | const { reactServerErrorsByDigest } = workStore |
| | function onHTMLRenderRSCError(err: DigestedError, silenceLog: boolean) { |
| | return onInstrumentationRequestError?.( |
| | err, |
| | req, |
| | createErrorContext(ctx, 'react-server-components'), |
| | silenceLog |
| | ) |
| | } |
| | const serverComponentsErrorHandler = createReactServerErrorHandler( |
| | dev, |
| | nextExport, |
| | reactServerErrorsByDigest, |
| | onHTMLRenderRSCError, |
| | renderSpan |
| | ) |
| |
|
| | function onHTMLRenderSSRError(err: DigestedError) { |
| | |
| | |
| | const silenceLog = false |
| | return onInstrumentationRequestError?.( |
| | err, |
| | req, |
| | createErrorContext(ctx, 'server-rendering'), |
| | silenceLog |
| | ) |
| | } |
| |
|
| | const allCapturedErrors: Array<unknown> = [] |
| | const htmlRendererErrorHandler = createHTMLErrorHandler( |
| | dev, |
| | nextExport, |
| | reactServerErrorsByDigest, |
| | allCapturedErrors, |
| | onHTMLRenderSSRError, |
| | renderSpan |
| | ) |
| |
|
| | let reactServerResult: null | ReactServerResult = null |
| | let reactDebugStream: ReadableStream<Uint8Array> | undefined |
| |
|
| | const setHeader = res.setHeader.bind(res) |
| | const appendHeader = res.appendHeader.bind(res) |
| | const { clientModules } = getClientReferenceManifest() |
| |
|
| | try { |
| | if ( |
| | |
| | process.env.NODE_ENV === 'development' && |
| | |
| | dev && |
| | |
| | process.env.NEXT_RUNTIME !== 'edge' && |
| | |
| | cacheComponents |
| | ) { |
| | let debugChannel: DebugChannelPair | undefined |
| |
|
| | const getPayload = async ( |
| | |
| | requestStore: RequestStore |
| | ) => { |
| | const payload: InitialRSCPayload & RSCPayloadDevProperties = |
| | await workUnitAsyncStorage.run( |
| | requestStore, |
| | getRSCPayload, |
| | tree, |
| | ctx, |
| | res.statusCode === 404 |
| | ) |
| |
|
| | if (isBypassingCachesInDev(renderOpts, requestStore)) { |
| | |
| | |
| | if (renderOpts.setCacheStatus) { |
| | |
| | renderOpts.setCacheStatus('bypass', htmlRequestId) |
| | } |
| | payload._bypassCachesInDev = createElement( |
| | WarnForBypassCachesInDev, |
| | { |
| | route: workStore.route, |
| | } |
| | ) |
| | } |
| |
|
| | return payload |
| | } |
| |
|
| | if ( |
| | |
| | |
| | createRequestStore && |
| | |
| | |
| | !isBypassingCachesInDev(renderOpts, requestStore) |
| | ) { |
| | const { |
| | stream: serverStream, |
| | accumulatedChunksPromise, |
| | staticInterruptReason, |
| | runtimeInterruptReason, |
| | staticStageEndTime, |
| | runtimeStageEndTime, |
| | debugChannel: returnedDebugChannel, |
| | requestStore: finalRequestStore, |
| | } = await renderWithRestartOnCacheMissInDev( |
| | ctx, |
| | requestStore, |
| | createRequestStore, |
| | getPayload, |
| | serverComponentsErrorHandler |
| | ) |
| |
|
| | let validationDebugChannelClient: Readable | undefined = undefined |
| | if (returnedDebugChannel) { |
| | const [t1, t2] = returnedDebugChannel.clientSide.readable.tee() |
| | returnedDebugChannel.clientSide.readable = t1 |
| | validationDebugChannelClient = nodeStreamFromReadableStream(t2) |
| | } |
| |
|
| | consoleAsyncStorage.run( |
| | { dim: true }, |
| | spawnStaticShellValidationInDev, |
| | accumulatedChunksPromise, |
| | staticInterruptReason, |
| | runtimeInterruptReason, |
| | staticStageEndTime, |
| | runtimeStageEndTime, |
| | ctx, |
| | finalRequestStore, |
| | devFallbackParams, |
| | validationDebugChannelClient |
| | ) |
| |
|
| | reactServerResult = new ReactServerResult(serverStream) |
| | requestStore = finalRequestStore |
| | debugChannel = returnedDebugChannel |
| | } else { |
| | |
| | |
| |
|
| | debugChannel = setReactDebugChannel && createDebugChannel() |
| |
|
| | const serverStream = |
| | await stagedRenderToReadableStreamWithoutCachesInDev( |
| | ctx, |
| | requestStore, |
| | getPayload, |
| | { |
| | onError: serverComponentsErrorHandler, |
| | filterStackFrame, |
| | debugChannel: debugChannel?.serverSide, |
| | } |
| | ) |
| | reactServerResult = new ReactServerResult(serverStream) |
| | } |
| |
|
| | if (debugChannel && setReactDebugChannel) { |
| | const [readableSsr, readableBrowser] = |
| | debugChannel.clientSide.readable.tee() |
| |
|
| | reactDebugStream = readableSsr |
| |
|
| | setReactDebugChannel( |
| | { readable: readableBrowser }, |
| | htmlRequestId, |
| | requestId |
| | ) |
| | } |
| | } else { |
| | |
| | const RSCPayload: RSCPayload & RSCPayloadDevProperties = |
| | await workUnitAsyncStorage.run( |
| | requestStore, |
| | getRSCPayload, |
| | tree, |
| | ctx, |
| | res.statusCode === 404 |
| | ) |
| |
|
| | const debugChannel = setReactDebugChannel && createDebugChannel() |
| |
|
| | if (debugChannel) { |
| | const [readableSsr, readableBrowser] = |
| | debugChannel.clientSide.readable.tee() |
| |
|
| | reactDebugStream = readableSsr |
| |
|
| | setReactDebugChannel( |
| | { readable: readableBrowser }, |
| | htmlRequestId, |
| | requestId |
| | ) |
| | } |
| |
|
| | reactServerResult = new ReactServerResult( |
| | workUnitAsyncStorage.run( |
| | requestStore, |
| | serverRenderToReadableStream, |
| | RSCPayload, |
| | clientModules, |
| | { |
| | filterStackFrame, |
| | onError: serverComponentsErrorHandler, |
| | debugChannel: debugChannel?.serverSide, |
| | } |
| | ) |
| | ) |
| | } |
| |
|
| | |
| | |
| | |
| | await waitAtLeastOneReactRenderTask() |
| |
|
| | |
| | |
| | if (typeof renderOpts.postponed === 'string') { |
| | if (postponedState?.type === DynamicState.DATA) { |
| | |
| | |
| | |
| | const inlinedReactServerDataStream = createInlinedDataReadableStream( |
| | reactServerResult.tee(), |
| | nonce, |
| | formState |
| | ) |
| |
|
| | |
| | if (renderSpan.isRecording()) renderSpan.end() |
| | return chainStreams( |
| | inlinedReactServerDataStream, |
| | createDocumentClosingStream() |
| | ) |
| | } else if (postponedState) { |
| | |
| | const { postponed, preludeState } = |
| | getPostponedFromState(postponedState) |
| | const resume = ( |
| | require('react-dom/server') as typeof import('react-dom/server') |
| | ).resume |
| |
|
| | const htmlStream = await workUnitAsyncStorage.run( |
| | requestStore, |
| | resume, |
| | <App |
| | reactServerStream={reactServerResult.tee()} |
| | reactDebugStream={reactDebugStream} |
| | debugEndTime={undefined} |
| | preinitScripts={preinitScripts} |
| | ServerInsertedHTMLProvider={ServerInsertedHTMLProvider} |
| | nonce={nonce} |
| | images={ctx.renderOpts.images} |
| | />, |
| | postponed, |
| | { onError: htmlRendererErrorHandler, nonce } |
| | ) |
| |
|
| | |
| | htmlStream.allReady.finally(() => { |
| | if (renderSpan.isRecording()) renderSpan.end() |
| | }) |
| |
|
| | const getServerInsertedHTML = makeGetServerInsertedHTML({ |
| | polyfills, |
| | renderServerInsertedHTML, |
| | serverCapturedErrors: allCapturedErrors, |
| | basePath, |
| | tracingMetadata: tracingMetadata, |
| | }) |
| | return await continueDynamicHTMLResume(htmlStream, { |
| | |
| | |
| | |
| | |
| | delayDataUntilFirstHtmlChunk: |
| | preludeState === DynamicHTMLPreludeState.Empty, |
| | inlinedDataStream: createInlinedDataReadableStream( |
| | reactServerResult.consume(), |
| | nonce, |
| | formState |
| | ), |
| | getServerInsertedHTML, |
| | getServerInsertedMetadata, |
| | }) |
| | } |
| | } |
| |
|
| | |
| | const renderToReadableStream = ( |
| | require('react-dom/server') as typeof import('react-dom/server') |
| | ).renderToReadableStream |
| |
|
| | const htmlStream = await workUnitAsyncStorage.run( |
| | requestStore, |
| | renderToReadableStream, |
| | <App |
| | reactServerStream={reactServerResult.tee()} |
| | reactDebugStream={reactDebugStream} |
| | debugEndTime={undefined} |
| | preinitScripts={preinitScripts} |
| | ServerInsertedHTMLProvider={ServerInsertedHTMLProvider} |
| | nonce={nonce} |
| | images={ctx.renderOpts.images} |
| | />, |
| | { |
| | onError: htmlRendererErrorHandler, |
| | nonce, |
| | onHeaders: (headers: Headers) => { |
| | headers.forEach((value, key) => { |
| | appendHeader(key, value) |
| | }) |
| | }, |
| | maxHeadersLength: reactMaxHeadersLength, |
| | bootstrapScriptContent, |
| | bootstrapScripts: [bootstrapScript], |
| | formState, |
| | } |
| | ) |
| |
|
| | |
| | htmlStream.allReady.finally(() => { |
| | if (renderSpan.isRecording()) renderSpan.end() |
| | }) |
| |
|
| | const getServerInsertedHTML = makeGetServerInsertedHTML({ |
| | polyfills, |
| | renderServerInsertedHTML, |
| | serverCapturedErrors: allCapturedErrors, |
| | basePath, |
| | tracingMetadata: tracingMetadata, |
| | }) |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | const generateStaticHTML = |
| | supportsDynamicResponse !== true || !!shouldWaitOnAllReady |
| |
|
| | return await continueFizzStream(htmlStream, { |
| | inlinedDataStream: createInlinedDataReadableStream( |
| | reactServerResult.consume(), |
| | nonce, |
| | formState |
| | ), |
| | isStaticGeneration: generateStaticHTML, |
| | isBuildTimePrerendering: ctx.workStore.isBuildTimePrerendering === true, |
| | buildId: ctx.workStore.buildId, |
| | getServerInsertedHTML, |
| | getServerInsertedMetadata, |
| | validateRootLayout: dev, |
| | }) |
| | } catch (err) { |
| | if ( |
| | isStaticGenBailoutError(err) || |
| | (typeof err === 'object' && |
| | err !== null && |
| | 'message' in err && |
| | typeof err.message === 'string' && |
| | err.message.includes( |
| | 'https://nextjs.org/docs/advanced-features/static-html-export' |
| | )) |
| | ) { |
| | |
| | endSpanWithError(err) |
| | throw err |
| | } |
| |
|
| | |
| | |
| | const shouldBailoutToCSR = isBailoutToCSRError(err) |
| | if (shouldBailoutToCSR) { |
| | const stack = getStackWithoutErrorMessage(err) |
| | error( |
| | `${err.reason} should be wrapped in a suspense boundary at page "${pagePath}". Read more: https://nextjs.org/docs/messages/missing-suspense-with-csr-bailout\n${stack}` |
| | ) |
| |
|
| | endSpanWithError(err) |
| | throw err |
| | } |
| |
|
| | let errorType: MetadataErrorType | 'redirect' | undefined |
| |
|
| | if (isHTTPAccessFallbackError(err)) { |
| | res.statusCode = getAccessFallbackHTTPStatus(err) |
| | metadata.statusCode = res.statusCode |
| | errorType = getAccessFallbackErrorTypeByStatus(res.statusCode) |
| | } else if (isRedirectError(err)) { |
| | errorType = 'redirect' |
| | res.statusCode = getRedirectStatusCodeFromError(err) |
| | metadata.statusCode = res.statusCode |
| |
|
| | const redirectUrl = addPathPrefix( |
| | getURLFromRedirectError(err), |
| | basePath |
| | ) |
| |
|
| | |
| | |
| | const headers = new Headers() |
| | if (appendMutableCookies(headers, requestStore.mutableCookies)) { |
| | setHeader('set-cookie', Array.from(headers.values())) |
| | } |
| |
|
| | setHeader('location', redirectUrl) |
| | } else if (!shouldBailoutToCSR) { |
| | res.statusCode = 500 |
| | metadata.statusCode = res.statusCode |
| | } |
| |
|
| | const [errorPreinitScripts, errorBootstrapScript] = getRequiredScripts( |
| | buildManifest, |
| | assetPrefix, |
| | crossOrigin, |
| | subresourceIntegrityManifest, |
| | getAssetQueryString(ctx, false), |
| | nonce, |
| | '/_not-found/page' |
| | ) |
| |
|
| | let errorRSCPayload: InitialRSCPayload |
| | let errorServerStream: ReturnType<typeof serverRenderToReadableStream> |
| |
|
| | try { |
| | errorRSCPayload = await workUnitAsyncStorage.run( |
| | requestStore, |
| | getErrorRSCPayload, |
| | tree, |
| | ctx, |
| | reactServerErrorsByDigest.has((err as any).digest) ? null : err, |
| | errorType |
| | ) |
| |
|
| | errorServerStream = workUnitAsyncStorage.run( |
| | requestStore, |
| | serverRenderToReadableStream, |
| | errorRSCPayload, |
| | clientModules, |
| | { |
| | filterStackFrame, |
| | onError: serverComponentsErrorHandler, |
| | } |
| | ) |
| |
|
| | if (reactServerResult === null) { |
| | |
| | |
| | endSpanWithError(err) |
| | throw err |
| | } |
| | } catch (setupErr) { |
| | endSpanWithError(setupErr) |
| | throw setupErr |
| | } |
| |
|
| | try { |
| | const fizzStream = await workUnitAsyncStorage.run( |
| | requestStore, |
| | renderToInitialFizzStream, |
| | { |
| | ReactDOMServer: |
| | require('react-dom/server') as typeof import('react-dom/server'), |
| | element: ( |
| | <ErrorApp |
| | reactServerStream={errorServerStream} |
| | ServerInsertedHTMLProvider={ServerInsertedHTMLProvider} |
| | preinitScripts={errorPreinitScripts} |
| | nonce={nonce} |
| | images={ctx.renderOpts.images} |
| | /> |
| | ), |
| | streamOptions: { |
| | nonce, |
| | bootstrapScriptContent, |
| | |
| | bootstrapScripts: [errorBootstrapScript], |
| | formState, |
| | }, |
| | } |
| | ) |
| |
|
| | |
| | fizzStream.allReady.finally(() => { |
| | if (renderSpan.isRecording()) renderSpan.end() |
| | }) |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | const generateStaticHTML = |
| | supportsDynamicResponse !== true || !!shouldWaitOnAllReady |
| | return await continueFizzStream(fizzStream, { |
| | inlinedDataStream: createInlinedDataReadableStream( |
| | |
| | |
| | |
| | reactServerResult.consume(), |
| | nonce, |
| | formState |
| | ), |
| | isStaticGeneration: generateStaticHTML, |
| | isBuildTimePrerendering: |
| | ctx.workStore.isBuildTimePrerendering === true, |
| | buildId: ctx.workStore.buildId, |
| | getServerInsertedHTML: makeGetServerInsertedHTML({ |
| | polyfills, |
| | renderServerInsertedHTML, |
| | serverCapturedErrors: [], |
| | basePath, |
| | tracingMetadata: tracingMetadata, |
| | }), |
| | getServerInsertedMetadata, |
| | validateRootLayout: dev, |
| | }) |
| | } catch (finalErr: any) { |
| | if ( |
| | process.env.NODE_ENV === 'development' && |
| | isHTTPAccessFallbackError(finalErr) |
| | ) { |
| | const { bailOnRootNotFound } = |
| | require('../../client/components/dev-root-http-access-fallback-boundary') as typeof import('../../client/components/dev-root-http-access-fallback-boundary') |
| | bailOnRootNotFound() |
| | } |
| | endSpanWithError(finalErr) |
| | throw finalErr |
| | } |
| | } |
| | }) |
| | |
| | } |
| |
|
| | async function renderWithRestartOnCacheMissInDev( |
| | ctx: AppRenderContext, |
| | initialRequestStore: RequestStore, |
| | createRequestStore: () => RequestStore, |
| | getPayload: (requestStore: RequestStore) => Promise<RSCPayload>, |
| | onError: (error: unknown) => void |
| | ) { |
| | const { |
| | htmlRequestId, |
| | renderOpts, |
| | componentMod: { |
| | routeModule: { |
| | userland: { loaderTree }, |
| | }, |
| | }, |
| | } = ctx |
| |
|
| | const { ComponentMod, setCacheStatus, setReactDebugChannel } = renderOpts |
| |
|
| | const hasRuntimePrefetch = |
| | await anySegmentHasRuntimePrefetchEnabled(loaderTree) |
| |
|
| | |
| | let requestStore: RequestStore = initialRequestStore |
| |
|
| | const environmentName = () => { |
| | const currentStage = requestStore.stagedRendering!.currentStage |
| | switch (currentStage) { |
| | case RenderStage.Before: |
| | case RenderStage.Static: |
| | return 'Prerender' |
| | case RenderStage.Runtime: |
| | return hasRuntimePrefetch ? 'Prefetch' : 'Prefetchable' |
| | case RenderStage.Dynamic: |
| | case RenderStage.Abandoned: |
| | return 'Server' |
| | default: |
| | currentStage satisfies never |
| | throw new InvariantError(`Invalid render stage: ${currentStage}`) |
| | } |
| | } |
| |
|
| | |
| | |
| | |
| |
|
| | |
| | |
| |
|
| | |
| | |
| | const cacheSignal = new CacheSignal() |
| |
|
| | |
| | |
| | |
| | trackPendingModules(cacheSignal) |
| |
|
| | const prerenderResumeDataCache = createPrerenderResumeDataCache() |
| |
|
| | const initialReactController = new AbortController() |
| | const initialDataController = new AbortController() |
| | const initialStageController = new StagedRenderingController( |
| | initialDataController.signal, |
| | hasRuntimePrefetch |
| | ) |
| |
|
| | requestStore.prerenderResumeDataCache = prerenderResumeDataCache |
| | |
| | |
| | requestStore.renderResumeDataCache = null |
| | requestStore.stagedRendering = initialStageController |
| | requestStore.asyncApiPromises = createAsyncApiPromisesInDev( |
| | initialStageController, |
| | requestStore.cookies, |
| | requestStore.mutableCookies, |
| | requestStore.headers |
| | ) |
| | requestStore.cacheSignal = cacheSignal |
| |
|
| | let debugChannel = setReactDebugChannel && createDebugChannel() |
| | const { clientModules } = getClientReferenceManifest() |
| |
|
| | |
| | |
| | const initialRscPayload = await getPayload(requestStore) |
| |
|
| | const maybeInitialStreamResult = await workUnitAsyncStorage.run( |
| | requestStore, |
| | () => |
| | pipelineInSequentialTasks( |
| | () => { |
| | |
| | initialStageController.advanceStage(RenderStage.Static) |
| |
|
| | const stream = ComponentMod.renderToReadableStream( |
| | initialRscPayload, |
| | clientModules, |
| | { |
| | onError, |
| | environmentName, |
| | filterStackFrame, |
| | debugChannel: debugChannel?.serverSide, |
| | signal: initialReactController.signal, |
| | } |
| | ) |
| | |
| | |
| | |
| | initialReactController.signal.addEventListener('abort', () => { |
| | initialDataController.abort(initialReactController.signal.reason) |
| | }) |
| |
|
| | const [continuationStream, accumulatingStream] = stream.tee() |
| | const accumulatedChunksPromise = accumulateStreamChunks( |
| | accumulatingStream, |
| | initialStageController, |
| | initialDataController.signal |
| | ) |
| | return { stream: continuationStream, accumulatedChunksPromise } |
| | }, |
| | ({ stream, accumulatedChunksPromise }) => { |
| | |
| |
|
| | if (initialStageController.currentStage === RenderStage.Abandoned) { |
| | |
| | return null |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | if (cacheSignal.hasPendingReads()) { |
| | |
| | |
| | |
| | initialStageController.abandonRender() |
| | return null |
| | } |
| |
|
| | initialStageController.advanceStage(RenderStage.Runtime) |
| | return { stream, accumulatedChunksPromise } |
| | }, |
| | (result) => { |
| | |
| | if ( |
| | result === null || |
| | initialStageController.currentStage === RenderStage.Abandoned |
| | ) { |
| | |
| | return null |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | if (cacheSignal.hasPendingReads()) { |
| | initialStageController.abandonRender() |
| | return null |
| | } |
| |
|
| | |
| | |
| | |
| | initialStageController.advanceStage(RenderStage.Dynamic) |
| | return result |
| | } |
| | ) |
| | ) |
| |
|
| | if (maybeInitialStreamResult !== null) { |
| | |
| | return { |
| | stream: maybeInitialStreamResult.stream, |
| | accumulatedChunksPromise: |
| | maybeInitialStreamResult.accumulatedChunksPromise, |
| | staticInterruptReason: initialStageController.getStaticInterruptReason(), |
| | runtimeInterruptReason: |
| | initialStageController.getRuntimeInterruptReason(), |
| | staticStageEndTime: initialStageController.getStaticStageEndTime(), |
| | runtimeStageEndTime: initialStageController.getRuntimeStageEndTime(), |
| | debugChannel, |
| | requestStore, |
| | } |
| | } |
| |
|
| | if (process.env.NODE_ENV === 'development' && setCacheStatus) { |
| | setCacheStatus('filling', htmlRequestId) |
| | } |
| |
|
| | |
| | |
| |
|
| | |
| | |
| | |
| | |
| | |
| |
|
| | await cacheSignal.cacheReady() |
| | initialReactController.abort() |
| |
|
| | |
| | |
| | |
| |
|
| | |
| | requestStore = createRequestStore() |
| |
|
| | |
| | |
| | const abortSignal = null |
| | const finalStageController = new StagedRenderingController( |
| | abortSignal, |
| | hasRuntimePrefetch |
| | ) |
| |
|
| | |
| | |
| | requestStore.prerenderResumeDataCache = null |
| | requestStore.renderResumeDataCache = createRenderResumeDataCache( |
| | prerenderResumeDataCache |
| | ) |
| | requestStore.stagedRendering = finalStageController |
| | requestStore.cacheSignal = null |
| | requestStore.asyncApiPromises = createAsyncApiPromisesInDev( |
| | finalStageController, |
| | requestStore.cookies, |
| | requestStore.mutableCookies, |
| | requestStore.headers |
| | ) |
| |
|
| | |
| | |
| | debugChannel = setReactDebugChannel && createDebugChannel() |
| |
|
| | |
| | |
| | const finalRscPayload = await getPayload(requestStore) |
| |
|
| | const finalStreamResult = await workUnitAsyncStorage.run(requestStore, () => |
| | pipelineInSequentialTasks( |
| | () => { |
| | |
| | finalStageController.advanceStage(RenderStage.Static) |
| |
|
| | const stream = ComponentMod.renderToReadableStream( |
| | finalRscPayload, |
| | clientModules, |
| | { |
| | onError, |
| | environmentName, |
| | filterStackFrame, |
| | debugChannel: debugChannel?.serverSide, |
| | } |
| | ) |
| |
|
| | const [continuationStream, accumulatingStream] = stream.tee() |
| | const accumulatedChunksPromise = accumulateStreamChunks( |
| | accumulatingStream, |
| | finalStageController, |
| | null |
| | ) |
| | return { stream: continuationStream, accumulatedChunksPromise } |
| | }, |
| | (result) => { |
| | |
| | finalStageController.advanceStage(RenderStage.Runtime) |
| | return result |
| | }, |
| | (result) => { |
| | |
| | finalStageController.advanceStage(RenderStage.Dynamic) |
| | return result |
| | } |
| | ) |
| | ) |
| |
|
| | if (process.env.NODE_ENV === 'development' && setCacheStatus) { |
| | setCacheStatus('filled', htmlRequestId) |
| | } |
| |
|
| | return { |
| | stream: finalStreamResult.stream, |
| | accumulatedChunksPromise: finalStreamResult.accumulatedChunksPromise, |
| | staticInterruptReason: finalStageController.getStaticInterruptReason(), |
| | runtimeInterruptReason: finalStageController.getRuntimeInterruptReason(), |
| | staticStageEndTime: finalStageController.getStaticStageEndTime(), |
| | runtimeStageEndTime: finalStageController.getRuntimeStageEndTime(), |
| | debugChannel, |
| | requestStore, |
| | } |
| | } |
| |
|
| | interface AccumulatedStreamChunks { |
| | readonly staticChunks: Array<Uint8Array> |
| | readonly runtimeChunks: Array<Uint8Array> |
| | readonly dynamicChunks: Array<Uint8Array> |
| | } |
| |
|
| | async function accumulateStreamChunks( |
| | stream: ReadableStream<Uint8Array>, |
| | stageController: StagedRenderingController, |
| | signal: AbortSignal | null |
| | ): Promise<AccumulatedStreamChunks> { |
| | const staticChunks: Array<Uint8Array> = [] |
| | const runtimeChunks: Array<Uint8Array> = [] |
| | const dynamicChunks: Array<Uint8Array> = [] |
| | const reader = stream.getReader() |
| |
|
| | let cancelled = false |
| | function cancel() { |
| | if (!cancelled) { |
| | cancelled = true |
| | reader.cancel() |
| | } |
| | } |
| |
|
| | if (signal) { |
| | signal.addEventListener('abort', cancel, { once: true }) |
| | } |
| |
|
| | try { |
| | while (!cancelled) { |
| | const { done, value } = await reader.read() |
| | if (done) { |
| | cancel() |
| | break |
| | } |
| | switch (stageController.currentStage) { |
| | case RenderStage.Before: |
| | throw new InvariantError( |
| | 'Unexpected stream chunk while in Before stage' |
| | ) |
| | case RenderStage.Static: |
| | staticChunks.push(value) |
| | |
| | case RenderStage.Runtime: |
| | runtimeChunks.push(value) |
| | |
| | case RenderStage.Dynamic: |
| | dynamicChunks.push(value) |
| | break |
| | case RenderStage.Abandoned: |
| | |
| | |
| | break |
| | default: |
| | stageController.currentStage satisfies never |
| | break |
| | } |
| | } |
| | } catch { |
| | |
| | } |
| |
|
| | return { staticChunks, runtimeChunks, dynamicChunks } |
| | } |
| |
|
| | function createAsyncApiPromisesInDev( |
| | stagedRendering: StagedRenderingController, |
| | cookies: RequestStore['cookies'], |
| | mutableCookies: RequestStore['mutableCookies'], |
| | headers: RequestStore['headers'] |
| | ): NonNullable<RequestStore['asyncApiPromises']> { |
| | return { |
| | |
| | cookies: stagedRendering.delayUntilStage( |
| | RenderStage.Runtime, |
| | 'cookies', |
| | cookies |
| | ), |
| | mutableCookies: stagedRendering.delayUntilStage( |
| | RenderStage.Runtime, |
| | 'cookies', |
| | mutableCookies as RequestStore['cookies'] |
| | ), |
| | headers: stagedRendering.delayUntilStage( |
| | RenderStage.Runtime, |
| | 'headers', |
| | headers |
| | ), |
| | |
| | sharedParamsParent: stagedRendering.delayUntilStage( |
| | RenderStage.Runtime, |
| | undefined, |
| | '<internal params>' |
| | ), |
| | sharedSearchParamsParent: stagedRendering.delayUntilStage( |
| | RenderStage.Runtime, |
| | undefined, |
| | '<internal searchParams>' |
| | ), |
| | connection: stagedRendering.delayUntilStage( |
| | RenderStage.Dynamic, |
| | 'connection', |
| | undefined |
| | ), |
| | } |
| | } |
| |
|
| | type DebugChannelPair = { |
| | serverSide: DebugChannelServer |
| | clientSide: DebugChannelClient |
| | } |
| |
|
| | type DebugChannelServer = { |
| | readable?: ReadableStream<Uint8Array> |
| | writable: WritableStream<Uint8Array> |
| | } |
| | type DebugChannelClient = { |
| | readable: ReadableStream<Uint8Array> |
| | writable?: WritableStream<Uint8Array> |
| | } |
| |
|
| | function createDebugChannel(): DebugChannelPair | undefined { |
| | if (process.env.NODE_ENV === 'production') { |
| | return undefined |
| | } |
| |
|
| | let readableController: ReadableStreamDefaultController | undefined |
| |
|
| | let clientSideReadable = new ReadableStream<Uint8Array>({ |
| | start(controller) { |
| | readableController = controller |
| | }, |
| | }) |
| |
|
| | return { |
| | serverSide: { |
| | writable: new WritableStream<Uint8Array>({ |
| | write(chunk) { |
| | readableController?.enqueue(chunk) |
| | }, |
| | close() { |
| | readableController?.close() |
| | }, |
| | abort(err) { |
| | readableController?.error(err) |
| | }, |
| | }), |
| | }, |
| | clientSide: { readable: clientSideReadable }, |
| | } |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | async function logMessagesAndSendErrorsToBrowser( |
| | messages: unknown[], |
| | ctx: AppRenderContext |
| | ): Promise<void> { |
| | const { componentMod: ComponentMod, htmlRequestId, renderOpts } = ctx |
| | const { sendErrorsToBrowser } = renderOpts |
| |
|
| | const errors: Error[] = [] |
| | for (const message of messages) { |
| | |
| | |
| | consoleAsyncStorage.exit(() => { |
| | console.error(message) |
| | }) |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | if (message instanceof Error) { |
| | errors.push(message) |
| | } |
| | } |
| |
|
| | if (errors.length > 0) { |
| | if (!sendErrorsToBrowser) { |
| | throw new InvariantError( |
| | 'Expected `sendErrorsToBrowser` to be defined in renderOpts.' |
| | ) |
| | } |
| |
|
| | const { clientModules } = getClientReferenceManifest() |
| |
|
| | const errorsRscStream = ComponentMod.renderToReadableStream( |
| | errors, |
| | clientModules, |
| | { filterStackFrame } |
| | ) |
| |
|
| | sendErrorsToBrowser(errorsRscStream, htmlRequestId) |
| | } |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | async function spawnStaticShellValidationInDev( |
| | accumulatedChunksPromise: Promise<AccumulatedStreamChunks>, |
| | staticInterruptReason: Error | null, |
| | runtimeInterruptReason: Error | null, |
| | staticStageEndTime: number, |
| | runtimeStageEndTime: number, |
| | ctx: AppRenderContext, |
| | requestStore: RequestStore, |
| | fallbackRouteParams: OpaqueFallbackRouteParams | null, |
| | debugChannelClient: Readable | undefined |
| | ): Promise<void> { |
| | const { |
| | componentMod: ComponentMod, |
| | getDynamicParamFromSegment, |
| | renderOpts, |
| | workStore, |
| | } = ctx |
| |
|
| | const { allowEmptyStaticShell = false } = renderOpts |
| |
|
| | const rootParams = getRootParams( |
| | ComponentMod.routeModule.userland.loaderTree, |
| | getDynamicParamFromSegment |
| | ) |
| |
|
| | const hmrRefreshHash = getHmrRefreshHash(workStore, requestStore) |
| |
|
| | |
| | |
| | const { invalidDynamicUsageError } = workStore |
| | if (invalidDynamicUsageError) { |
| | return logMessagesAndSendErrorsToBrowser([invalidDynamicUsageError], ctx) |
| | } |
| |
|
| | if (staticInterruptReason) { |
| | return logMessagesAndSendErrorsToBrowser([staticInterruptReason], ctx) |
| | } |
| |
|
| | if (runtimeInterruptReason) { |
| | return logMessagesAndSendErrorsToBrowser([runtimeInterruptReason], ctx) |
| | } |
| |
|
| | const { staticChunks, runtimeChunks, dynamicChunks } = |
| | await accumulatedChunksPromise |
| |
|
| | |
| | |
| | |
| | await warmupModuleCacheForRuntimeValidationInDev( |
| | runtimeChunks, |
| | dynamicChunks, |
| | rootParams, |
| | fallbackRouteParams, |
| | allowEmptyStaticShell, |
| | ctx |
| | ) |
| |
|
| | let debugChunks: Uint8Array[] | null = null |
| | if (debugChannelClient) { |
| | debugChunks = [] |
| | debugChannelClient.on('data', (c) => debugChunks!.push(c)) |
| | } |
| |
|
| | const runtimeResult = await validateStagedShell( |
| | runtimeChunks, |
| | dynamicChunks, |
| | debugChunks, |
| | runtimeStageEndTime, |
| | rootParams, |
| | fallbackRouteParams, |
| | allowEmptyStaticShell, |
| | ctx, |
| | hmrRefreshHash, |
| | trackDynamicHoleInRuntimeShell |
| | ) |
| |
|
| | if (runtimeResult.length > 0) { |
| | |
| | |
| | return logMessagesAndSendErrorsToBrowser(runtimeResult, ctx) |
| | } |
| |
|
| | const staticResult = await validateStagedShell( |
| | staticChunks, |
| | dynamicChunks, |
| | debugChunks, |
| | staticStageEndTime, |
| | rootParams, |
| | fallbackRouteParams, |
| | allowEmptyStaticShell, |
| | ctx, |
| | hmrRefreshHash, |
| | trackDynamicHoleInStaticShell |
| | ) |
| |
|
| | return logMessagesAndSendErrorsToBrowser(staticResult, ctx) |
| | } |
| |
|
| | async function warmupModuleCacheForRuntimeValidationInDev( |
| | runtimeServerChunks: Array<Uint8Array>, |
| | allServerChunks: Array<Uint8Array>, |
| | rootParams: Params, |
| | fallbackRouteParams: OpaqueFallbackRouteParams | null, |
| | allowEmptyStaticShell: boolean, |
| | ctx: AppRenderContext |
| | ) { |
| | const { implicitTags, nonce, workStore } = ctx |
| |
|
| | |
| | const initialClientPrerenderController = new AbortController() |
| | const initialClientReactController = new AbortController() |
| | const initialClientRenderController = new AbortController() |
| |
|
| | const preinitScripts = () => {} |
| | const { ServerInsertedHTMLProvider } = createServerInsertedHTML() |
| |
|
| | const initialClientPrerenderStore: PrerenderStore = { |
| | type: 'prerender-client', |
| | phase: 'render', |
| | rootParams, |
| | fallbackRouteParams, |
| | implicitTags, |
| | renderSignal: initialClientRenderController.signal, |
| | controller: initialClientPrerenderController, |
| | |
| | |
| | cacheSignal: null, |
| | dynamicTracking: null, |
| | allowEmptyStaticShell, |
| | revalidate: INFINITE_CACHE, |
| | expire: INFINITE_CACHE, |
| | stale: INFINITE_CACHE, |
| | tags: [...implicitTags.tags], |
| | |
| | prerenderResumeDataCache: null, |
| | renderResumeDataCache: null, |
| | hmrRefreshHash: undefined, |
| | } |
| |
|
| | const runtimeServerStream = createNodeStreamFromChunks( |
| | runtimeServerChunks, |
| | allServerChunks, |
| | initialClientReactController.signal |
| | ) |
| |
|
| | const prerender = ( |
| | require('react-dom/static') as typeof import('react-dom/static') |
| | ).prerender |
| | const pendingInitialClientResult = workUnitAsyncStorage.run( |
| | initialClientPrerenderStore, |
| | prerender, |
| | |
| | <App |
| | reactServerStream={runtimeServerStream} |
| | reactDebugStream={undefined} |
| | debugEndTime={undefined} |
| | preinitScripts={preinitScripts} |
| | ServerInsertedHTMLProvider={ServerInsertedHTMLProvider} |
| | nonce={nonce} |
| | images={ctx.renderOpts.images} |
| | />, |
| | { |
| | signal: initialClientReactController.signal, |
| | onError: (err) => { |
| | const digest = getDigestForWellKnownError(err) |
| |
|
| | if (digest) { |
| | return digest |
| | } |
| |
|
| | if (isReactLargeShellError(err)) { |
| | |
| | console.error(err) |
| | return undefined |
| | } |
| |
|
| | if (initialClientReactController.signal.aborted) { |
| | |
| | } else if ( |
| | process.env.NEXT_DEBUG_BUILD || |
| | process.env.__NEXT_VERBOSE_LOGGING |
| | ) { |
| | |
| | |
| | printDebugThrownValueForProspectiveRender( |
| | err, |
| | workStore.route, |
| | Phase.ProspectiveRender |
| | ) |
| | } |
| | }, |
| | |
| | |
| | } |
| | ) |
| |
|
| | |
| | |
| | |
| | initialClientReactController.signal.addEventListener( |
| | 'abort', |
| | () => { |
| | initialClientRenderController.abort() |
| | }, |
| | { once: true } |
| | ) |
| |
|
| | pendingInitialClientResult.catch((err) => { |
| | if ( |
| | initialClientReactController.signal.aborted || |
| | isPrerenderInterruptedError(err) |
| | ) { |
| | |
| | } else if ( |
| | process.env.NEXT_DEBUG_BUILD || |
| | process.env.__NEXT_VERBOSE_LOGGING |
| | ) { |
| | |
| | |
| | printDebugThrownValueForProspectiveRender( |
| | err, |
| | workStore.route, |
| | Phase.ProspectiveRender |
| | ) |
| | } |
| | }) |
| |
|
| | |
| | |
| | const cacheSignal = new CacheSignal() |
| | trackPendingModules(cacheSignal) |
| | await cacheSignal.cacheReady() |
| | initialClientReactController.abort() |
| | } |
| |
|
| | async function validateStagedShell( |
| | stageChunks: Array<Uint8Array>, |
| | allServerChunks: Array<Uint8Array>, |
| | debugChunks: null | Array<Uint8Array>, |
| | debugEndTime: number | undefined, |
| | rootParams: Params, |
| | fallbackRouteParams: OpaqueFallbackRouteParams | null, |
| | allowEmptyStaticShell: boolean, |
| | ctx: AppRenderContext, |
| | hmrRefreshHash: string | undefined, |
| | trackDynamicHole: |
| | | typeof trackDynamicHoleInStaticShell |
| | | typeof trackDynamicHoleInRuntimeShell |
| | ): Promise<Array<unknown>> { |
| | const { implicitTags, nonce, workStore } = ctx |
| |
|
| | const clientDynamicTracking = createDynamicTrackingState( |
| | false |
| | ) |
| | const clientReactController = new AbortController() |
| | const clientRenderController = new AbortController() |
| |
|
| | const preinitScripts = () => {} |
| | const { ServerInsertedHTMLProvider } = createServerInsertedHTML() |
| |
|
| | const finalClientPrerenderStore: PrerenderStore = { |
| | type: 'prerender-client', |
| | phase: 'render', |
| | rootParams, |
| | fallbackRouteParams, |
| | implicitTags, |
| | renderSignal: clientRenderController.signal, |
| | controller: clientReactController, |
| | |
| | cacheSignal: null, |
| | dynamicTracking: clientDynamicTracking, |
| | allowEmptyStaticShell, |
| | revalidate: INFINITE_CACHE, |
| | expire: INFINITE_CACHE, |
| | stale: INFINITE_CACHE, |
| | tags: [...implicitTags.tags], |
| | |
| | prerenderResumeDataCache: null, |
| | renderResumeDataCache: null, |
| | hmrRefreshHash, |
| | } |
| |
|
| | let runtimeDynamicValidation = createDynamicValidationState() |
| |
|
| | const serverStream = createNodeStreamFromChunks( |
| | stageChunks, |
| | allServerChunks, |
| | clientReactController.signal |
| | ) |
| |
|
| | const debugChannelClient = debugChunks |
| | ? createNodeStreamFromChunks( |
| | debugChunks, |
| | debugChunks, |
| | clientReactController.signal |
| | ) |
| | : undefined |
| |
|
| | const prerender = ( |
| | require('react-dom/static') as typeof import('react-dom/static') |
| | ).prerender |
| | try { |
| | let { prelude: unprocessedPrelude } = |
| | await prerenderAndAbortInSequentialTasks( |
| | () => { |
| | const pendingFinalClientResult = workUnitAsyncStorage.run( |
| | finalClientPrerenderStore, |
| | prerender, |
| | |
| | <App |
| | reactServerStream={serverStream} |
| | reactDebugStream={debugChannelClient} |
| | debugEndTime={debugEndTime} |
| | preinitScripts={preinitScripts} |
| | ServerInsertedHTMLProvider={ServerInsertedHTMLProvider} |
| | nonce={nonce} |
| | images={ctx.renderOpts.images} |
| | />, |
| | { |
| | signal: clientReactController.signal, |
| | onError: (err: unknown, errorInfo: ErrorInfo) => { |
| | if ( |
| | isPrerenderInterruptedError(err) || |
| | clientReactController.signal.aborted |
| | ) { |
| | const componentStack = errorInfo.componentStack |
| | if (typeof componentStack === 'string') { |
| | trackDynamicHole( |
| | workStore, |
| | componentStack, |
| | runtimeDynamicValidation, |
| | clientDynamicTracking |
| | ) |
| | } |
| | return |
| | } |
| |
|
| | if (isReactLargeShellError(err)) { |
| | |
| | console.error(err) |
| | return undefined |
| | } |
| |
|
| | return getDigestForWellKnownError(err) |
| | }, |
| | |
| | |
| | } |
| | ) |
| |
|
| | |
| | |
| | |
| | clientReactController.signal.addEventListener( |
| | 'abort', |
| | () => { |
| | clientRenderController.abort() |
| | }, |
| | { once: true } |
| | ) |
| |
|
| | return pendingFinalClientResult |
| | }, |
| | () => { |
| | clientReactController.abort() |
| | } |
| | ) |
| |
|
| | const { preludeIsEmpty } = await processPrelude(unprocessedPrelude) |
| | return getStaticShellDisallowedDynamicReasons( |
| | workStore, |
| | preludeIsEmpty ? PreludeState.Empty : PreludeState.Full, |
| | runtimeDynamicValidation |
| | ) |
| | } catch (thrownValue) { |
| | |
| | |
| | let errors: Array<unknown> = getStaticShellDisallowedDynamicReasons( |
| | workStore, |
| | PreludeState.Errored, |
| | runtimeDynamicValidation |
| | ) |
| |
|
| | if (process.env.NEXT_DEBUG_BUILD || process.env.__NEXT_VERBOSE_LOGGING) { |
| | errors.unshift( |
| | 'During dynamic validation the root of the page errored. The next logged error is the thrown value. It may be a duplicate of errors reported during the normal development mode render.', |
| | thrownValue |
| | ) |
| | } |
| |
|
| | return errors |
| | } |
| | } |
| |
|
| | type PrerenderToStreamResult = { |
| | stream: ReadableStream<Uint8Array> |
| | digestErrorsMap: Map<string, DigestedError> |
| | ssrErrors: Array<unknown> |
| | dynamicAccess?: null | Array<DynamicAccess> |
| | collectedRevalidate: number |
| | collectedExpire: number |
| | collectedStale: number |
| | collectedTags: null | string[] |
| | renderResumeDataCache?: RenderResumeDataCache |
| | } |
| |
|
| | |
| | |
| | |
| | function shouldGenerateStaticFlightData(workStore: WorkStore): boolean { |
| | const { isStaticGeneration } = workStore |
| | if (!isStaticGeneration) return false |
| |
|
| | return true |
| | } |
| |
|
| | async function prerenderToStream( |
| | req: BaseNextRequest, |
| | res: BaseNextResponse, |
| | ctx: AppRenderContext, |
| | metadata: AppPageRenderResultMetadata, |
| | tree: LoaderTree, |
| | fallbackRouteParams: OpaqueFallbackRouteParams | null |
| | ): Promise<PrerenderToStreamResult> { |
| | |
| | |
| | |
| | const formState = null |
| |
|
| | const { |
| | assetPrefix, |
| | getDynamicParamFromSegment, |
| | implicitTags, |
| | nonce, |
| | pagePath, |
| | renderOpts, |
| | workStore, |
| | } = ctx |
| |
|
| | const { |
| | allowEmptyStaticShell = false, |
| | basePath, |
| | buildManifest, |
| | ComponentMod, |
| | crossOrigin, |
| | dev = false, |
| | experimental, |
| | isDebugDynamicAccesses, |
| | nextExport = false, |
| | onInstrumentationRequestError, |
| | page, |
| | reactMaxHeadersLength, |
| | subresourceIntegrityManifest, |
| | cacheComponents, |
| | } = renderOpts |
| |
|
| | const rootParams = getRootParams(tree, getDynamicParamFromSegment) |
| |
|
| | const { ServerInsertedHTMLProvider, renderServerInsertedHTML } = |
| | createServerInsertedHTML() |
| | const getServerInsertedMetadata = createServerInsertedMetadata(nonce) |
| |
|
| | const tracingMetadata = getTracedMetadata( |
| | getTracer().getTracePropagationData(), |
| | experimental.clientTraceMetadata |
| | ) |
| |
|
| | const polyfills: JSX.IntrinsicElements['script'][] = |
| | buildManifest.polyfillFiles |
| | .filter( |
| | (polyfill) => |
| | polyfill.endsWith('.js') && !polyfill.endsWith('.module.js') |
| | ) |
| | .map((polyfill) => ({ |
| | src: `${assetPrefix}/_next/${polyfill}${getAssetQueryString( |
| | ctx, |
| | false |
| | )}`, |
| | integrity: subresourceIntegrityManifest?.[polyfill], |
| | crossOrigin, |
| | noModule: true, |
| | nonce, |
| | })) |
| |
|
| | const [preinitScripts, bootstrapScript] = getRequiredScripts( |
| | buildManifest, |
| | |
| | |
| | assetPrefix, |
| | crossOrigin, |
| | subresourceIntegrityManifest, |
| | getAssetQueryString(ctx, true), |
| | nonce, |
| | page |
| | ) |
| |
|
| | const { reactServerErrorsByDigest } = workStore |
| | |
| | const reportErrors = !experimental.isRoutePPREnabled |
| | function onHTMLRenderRSCError(err: DigestedError, silenceLog: boolean) { |
| | if (reportErrors) { |
| | return onInstrumentationRequestError?.( |
| | err, |
| | req, |
| | createErrorContext(ctx, 'react-server-components'), |
| | silenceLog |
| | ) |
| | } |
| | } |
| | const serverComponentsErrorHandler = createReactServerErrorHandler( |
| | dev, |
| | nextExport, |
| | reactServerErrorsByDigest, |
| | onHTMLRenderRSCError |
| | ) |
| |
|
| | function onHTMLRenderSSRError(err: DigestedError) { |
| | if (reportErrors) { |
| | |
| | |
| | const silenceLog = false |
| | return onInstrumentationRequestError?.( |
| | err, |
| | req, |
| | createErrorContext(ctx, 'server-rendering'), |
| | silenceLog |
| | ) |
| | } |
| | } |
| | const allCapturedErrors: Array<unknown> = [] |
| | const htmlRendererErrorHandler = createHTMLErrorHandler( |
| | dev, |
| | nextExport, |
| | reactServerErrorsByDigest, |
| | allCapturedErrors, |
| | onHTMLRenderSSRError |
| | ) |
| |
|
| | let reactServerPrerenderResult: null | ReactServerPrerenderResult = null |
| | const setMetadataHeader = (name: string) => { |
| | metadata.headers ??= {} |
| | metadata.headers[name] = res.getHeader(name) |
| | } |
| | const setHeader = (name: string, value: string | string[]) => { |
| | res.setHeader(name, value) |
| | setMetadataHeader(name) |
| | return res |
| | } |
| | const appendHeader = (name: string, value: string | string[]) => { |
| | if (Array.isArray(value)) { |
| | value.forEach((item) => { |
| | res.appendHeader(name, item) |
| | }) |
| | } else { |
| | res.appendHeader(name, value) |
| | } |
| | setMetadataHeader(name) |
| | } |
| |
|
| | const selectStaleTime = createSelectStaleTime(experimental) |
| | const { clientModules } = getClientReferenceManifest() |
| |
|
| | let prerenderStore: PrerenderStore | null = null |
| |
|
| | try { |
| | if (cacheComponents) { |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| |
|
| | |
| | |
| | |
| | |
| | |
| | const initialServerPrerenderController = new AbortController() |
| |
|
| | |
| | const initialServerReactController = new AbortController() |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | const initialServerRenderController = new AbortController() |
| |
|
| | |
| | |
| | const cacheSignal = new CacheSignal() |
| |
|
| | let resumeDataCache: RenderResumeDataCache | PrerenderResumeDataCache |
| | let renderResumeDataCache: RenderResumeDataCache | null = null |
| | let prerenderResumeDataCache: PrerenderResumeDataCache | null = null |
| |
|
| | if (renderOpts.renderResumeDataCache) { |
| | |
| | |
| | |
| | |
| | resumeDataCache = renderResumeDataCache = |
| | renderOpts.renderResumeDataCache |
| | } else { |
| | |
| | resumeDataCache = prerenderResumeDataCache = |
| | createPrerenderResumeDataCache() |
| | } |
| |
|
| | const initialServerPayloadPrerenderStore: PrerenderStore = { |
| | type: 'prerender', |
| | phase: 'render', |
| | rootParams, |
| | fallbackRouteParams, |
| | implicitTags, |
| | |
| | |
| | renderSignal: initialServerRenderController.signal, |
| | |
| | |
| | |
| | controller: new AbortController(), |
| | |
| | |
| | |
| | cacheSignal, |
| | dynamicTracking: null, |
| | allowEmptyStaticShell, |
| | revalidate: INFINITE_CACHE, |
| | expire: INFINITE_CACHE, |
| | stale: INFINITE_CACHE, |
| | tags: [...implicitTags.tags], |
| | prerenderResumeDataCache, |
| | renderResumeDataCache, |
| | hmrRefreshHash: undefined, |
| | } |
| |
|
| | |
| | |
| | const initialServerPayload = await workUnitAsyncStorage.run( |
| | initialServerPayloadPrerenderStore, |
| | getRSCPayload, |
| | tree, |
| | ctx, |
| | res.statusCode === 404 |
| | ) |
| |
|
| | const initialServerPrerenderStore: PrerenderStore = (prerenderStore = { |
| | type: 'prerender', |
| | phase: 'render', |
| | rootParams, |
| | fallbackRouteParams, |
| | implicitTags, |
| | renderSignal: initialServerRenderController.signal, |
| | controller: initialServerPrerenderController, |
| | |
| | |
| | |
| | cacheSignal, |
| | dynamicTracking: null, |
| | allowEmptyStaticShell, |
| | revalidate: INFINITE_CACHE, |
| | expire: INFINITE_CACHE, |
| | stale: INFINITE_CACHE, |
| | tags: [...implicitTags.tags], |
| | prerenderResumeDataCache, |
| | renderResumeDataCache, |
| | hmrRefreshHash: undefined, |
| | }) |
| |
|
| | const pendingInitialServerResult = workUnitAsyncStorage.run( |
| | initialServerPrerenderStore, |
| | ComponentMod.prerender, |
| | initialServerPayload, |
| | clientModules, |
| | { |
| | filterStackFrame, |
| | onError: (err) => { |
| | const digest = getDigestForWellKnownError(err) |
| |
|
| | if (digest) { |
| | return digest |
| | } |
| |
|
| | if (isReactLargeShellError(err)) { |
| | |
| | console.error(err) |
| | return undefined |
| | } |
| |
|
| | if (initialServerPrerenderController.signal.aborted) { |
| | |
| | |
| | return |
| | } else if ( |
| | process.env.NEXT_DEBUG_BUILD || |
| | process.env.__NEXT_VERBOSE_LOGGING |
| | ) { |
| | printDebugThrownValueForProspectiveRender( |
| | err, |
| | workStore.route, |
| | Phase.ProspectiveRender |
| | ) |
| | } |
| | }, |
| | |
| | |
| | |
| | signal: initialServerReactController.signal, |
| | } |
| | ) |
| |
|
| | |
| | |
| | |
| | initialServerReactController.signal.addEventListener( |
| | 'abort', |
| | () => { |
| | initialServerRenderController.abort() |
| | initialServerPrerenderController.abort() |
| | }, |
| | { once: true } |
| | ) |
| |
|
| | |
| | trackPendingModules(cacheSignal) |
| | await cacheSignal.cacheReady() |
| |
|
| | initialServerReactController.abort() |
| |
|
| | |
| | |
| | if (workStore.invalidDynamicUsageError) { |
| | logDisallowedDynamicError(workStore, workStore.invalidDynamicUsageError) |
| | throw new StaticGenBailoutError() |
| | } |
| |
|
| | let initialServerResult |
| | try { |
| | initialServerResult = await createReactServerPrerenderResult( |
| | pendingInitialServerResult |
| | ) |
| | } catch (err) { |
| | if ( |
| | initialServerReactController.signal.aborted || |
| | initialServerPrerenderController.signal.aborted |
| | ) { |
| | |
| | } else if ( |
| | process.env.NEXT_DEBUG_BUILD || |
| | process.env.__NEXT_VERBOSE_LOGGING |
| | ) { |
| | |
| | |
| | printDebugThrownValueForProspectiveRender( |
| | err, |
| | workStore.route, |
| | Phase.ProspectiveRender |
| | ) |
| | } |
| | } |
| |
|
| | if (initialServerResult) { |
| | const initialClientPrerenderController = new AbortController() |
| | const initialClientReactController = new AbortController() |
| | const initialClientRenderController = new AbortController() |
| |
|
| | const initialClientPrerenderStore: PrerenderStore = { |
| | type: 'prerender-client', |
| | phase: 'render', |
| | rootParams, |
| | fallbackRouteParams, |
| | implicitTags, |
| | renderSignal: initialClientRenderController.signal, |
| | controller: initialClientPrerenderController, |
| | |
| | |
| | cacheSignal: null, |
| | dynamicTracking: null, |
| | allowEmptyStaticShell, |
| | revalidate: INFINITE_CACHE, |
| | expire: INFINITE_CACHE, |
| | stale: INFINITE_CACHE, |
| | tags: [...implicitTags.tags], |
| | prerenderResumeDataCache, |
| | renderResumeDataCache, |
| | hmrRefreshHash: undefined, |
| | } |
| |
|
| | const prerender = ( |
| | require('react-dom/static') as typeof import('react-dom/static') |
| | ).prerender |
| | const pendingInitialClientResult = workUnitAsyncStorage.run( |
| | initialClientPrerenderStore, |
| | prerender, |
| | |
| | <App |
| | reactServerStream={initialServerResult.asUnclosingStream()} |
| | reactDebugStream={undefined} |
| | debugEndTime={undefined} |
| | preinitScripts={preinitScripts} |
| | ServerInsertedHTMLProvider={ServerInsertedHTMLProvider} |
| | nonce={nonce} |
| | images={ctx.renderOpts.images} |
| | />, |
| | { |
| | signal: initialClientReactController.signal, |
| | onError: (err) => { |
| | const digest = getDigestForWellKnownError(err) |
| |
|
| | if (digest) { |
| | return digest |
| | } |
| |
|
| | if (isReactLargeShellError(err)) { |
| | |
| | console.error(err) |
| | return undefined |
| | } |
| |
|
| | if (initialClientReactController.signal.aborted) { |
| | |
| | } else if ( |
| | process.env.NEXT_DEBUG_BUILD || |
| | process.env.__NEXT_VERBOSE_LOGGING |
| | ) { |
| | |
| | |
| | printDebugThrownValueForProspectiveRender( |
| | err, |
| | workStore.route, |
| | Phase.ProspectiveRender |
| | ) |
| | } |
| | }, |
| | bootstrapScripts: [bootstrapScript], |
| | } |
| | ) |
| |
|
| | |
| | |
| | |
| | initialClientReactController.signal.addEventListener( |
| | 'abort', |
| | () => { |
| | initialClientRenderController.abort() |
| | }, |
| | { once: true } |
| | ) |
| |
|
| | pendingInitialClientResult.catch((err) => { |
| | if ( |
| | initialClientReactController.signal.aborted || |
| | isPrerenderInterruptedError(err) |
| | ) { |
| | |
| | } else if ( |
| | process.env.NEXT_DEBUG_BUILD || |
| | process.env.__NEXT_VERBOSE_LOGGING |
| | ) { |
| | |
| | |
| | printDebugThrownValueForProspectiveRender( |
| | err, |
| | workStore.route, |
| | Phase.ProspectiveRender |
| | ) |
| | } |
| | }) |
| |
|
| | |
| | |
| | trackPendingModules(cacheSignal) |
| | await cacheSignal.cacheReady() |
| | initialClientReactController.abort() |
| | } |
| |
|
| | const finalServerReactController = new AbortController() |
| | const finalServerRenderController = new AbortController() |
| |
|
| | const finalServerPayloadPrerenderStore: PrerenderStore = { |
| | type: 'prerender', |
| | phase: 'render', |
| | rootParams, |
| | fallbackRouteParams, |
| | implicitTags, |
| | |
| | |
| | renderSignal: finalServerRenderController.signal, |
| | |
| | |
| | |
| | controller: new AbortController(), |
| | |
| | cacheSignal: null, |
| | dynamicTracking: null, |
| | allowEmptyStaticShell, |
| | revalidate: INFINITE_CACHE, |
| | expire: INFINITE_CACHE, |
| | stale: INFINITE_CACHE, |
| | tags: [...implicitTags.tags], |
| | prerenderResumeDataCache, |
| | renderResumeDataCache, |
| | hmrRefreshHash: undefined, |
| | } |
| |
|
| | const finalAttemptRSCPayload = await workUnitAsyncStorage.run( |
| | finalServerPayloadPrerenderStore, |
| | getRSCPayload, |
| | tree, |
| | ctx, |
| | res.statusCode === 404 |
| | ) |
| |
|
| | const serverDynamicTracking = createDynamicTrackingState( |
| | isDebugDynamicAccesses |
| | ) |
| | let serverIsDynamic = false |
| |
|
| | const finalServerPrerenderStore: PrerenderStore = (prerenderStore = { |
| | type: 'prerender', |
| | phase: 'render', |
| | rootParams, |
| | fallbackRouteParams, |
| | implicitTags, |
| | renderSignal: finalServerRenderController.signal, |
| | controller: finalServerReactController, |
| | |
| | cacheSignal: null, |
| | dynamicTracking: serverDynamicTracking, |
| | allowEmptyStaticShell, |
| | revalidate: INFINITE_CACHE, |
| | expire: INFINITE_CACHE, |
| | stale: INFINITE_CACHE, |
| | tags: [...implicitTags.tags], |
| | prerenderResumeDataCache, |
| | renderResumeDataCache, |
| | hmrRefreshHash: undefined, |
| | }) |
| |
|
| | let prerenderIsPending = true |
| | const reactServerResult = (reactServerPrerenderResult = |
| | await createReactServerPrerenderResult( |
| | prerenderAndAbortInSequentialTasks( |
| | async () => { |
| | const pendingPrerenderResult = workUnitAsyncStorage.run( |
| | |
| | finalServerPrerenderStore, |
| | |
| | ComponentMod.prerender, |
| | |
| | finalAttemptRSCPayload, |
| | clientModules, |
| | { |
| | filterStackFrame, |
| | onError: (err: unknown) => { |
| | return serverComponentsErrorHandler(err) |
| | }, |
| | signal: finalServerReactController.signal, |
| | } |
| | ) |
| |
|
| | |
| | |
| | |
| | finalServerReactController.signal.addEventListener( |
| | 'abort', |
| | () => { |
| | finalServerRenderController.abort() |
| | }, |
| | { once: true } |
| | ) |
| |
|
| | const prerenderResult = await pendingPrerenderResult |
| | prerenderIsPending = false |
| |
|
| | return prerenderResult |
| | }, |
| | () => { |
| | if (finalServerReactController.signal.aborted) { |
| | |
| | |
| | serverIsDynamic = true |
| | return |
| | } |
| |
|
| | if (prerenderIsPending) { |
| | |
| | |
| | serverIsDynamic = true |
| | } |
| |
|
| | finalServerReactController.abort() |
| | } |
| | ) |
| | )) |
| |
|
| | const clientDynamicTracking = createDynamicTrackingState( |
| | isDebugDynamicAccesses |
| | ) |
| |
|
| | const finalClientReactController = new AbortController() |
| | const finalClientRenderController = new AbortController() |
| |
|
| | const finalClientPrerenderStore: PrerenderStore = { |
| | type: 'prerender-client', |
| | phase: 'render', |
| | rootParams, |
| | fallbackRouteParams, |
| | implicitTags, |
| | renderSignal: finalClientRenderController.signal, |
| | controller: finalClientReactController, |
| | |
| | cacheSignal: null, |
| | dynamicTracking: clientDynamicTracking, |
| | allowEmptyStaticShell, |
| | revalidate: INFINITE_CACHE, |
| | expire: INFINITE_CACHE, |
| | stale: INFINITE_CACHE, |
| | tags: [...implicitTags.tags], |
| | prerenderResumeDataCache, |
| | renderResumeDataCache, |
| | hmrRefreshHash: undefined, |
| | } |
| |
|
| | let dynamicValidation = createDynamicValidationState() |
| |
|
| | const prerender = ( |
| | require('react-dom/static') as typeof import('react-dom/static') |
| | ).prerender |
| | let { prelude: unprocessedPrelude, postponed } = |
| | await prerenderAndAbortInSequentialTasks( |
| | () => { |
| | const pendingFinalClientResult = workUnitAsyncStorage.run( |
| | finalClientPrerenderStore, |
| | prerender, |
| | |
| | <App |
| | reactServerStream={reactServerResult.asUnclosingStream()} |
| | reactDebugStream={undefined} |
| | debugEndTime={undefined} |
| | preinitScripts={preinitScripts} |
| | ServerInsertedHTMLProvider={ServerInsertedHTMLProvider} |
| | nonce={nonce} |
| | images={ctx.renderOpts.images} |
| | />, |
| | { |
| | signal: finalClientReactController.signal, |
| | onError: (err: unknown, errorInfo: ErrorInfo) => { |
| | if ( |
| | isPrerenderInterruptedError(err) || |
| | finalClientReactController.signal.aborted |
| | ) { |
| | const componentStack: string | undefined = ( |
| | errorInfo as any |
| | ).componentStack |
| | if (typeof componentStack === 'string') { |
| | trackAllowedDynamicAccess( |
| | workStore, |
| | componentStack, |
| | dynamicValidation, |
| | clientDynamicTracking |
| | ) |
| | } |
| | return |
| | } |
| |
|
| | return htmlRendererErrorHandler(err, errorInfo) |
| | }, |
| | onHeaders: (headers: Headers) => { |
| | headers.forEach((value, key) => { |
| | appendHeader(key, value) |
| | }) |
| | }, |
| | maxHeadersLength: reactMaxHeadersLength, |
| | bootstrapScripts: [bootstrapScript], |
| | } |
| | ) |
| |
|
| | |
| | |
| | |
| | finalClientReactController.signal.addEventListener( |
| | 'abort', |
| | () => { |
| | finalClientRenderController.abort() |
| | }, |
| | { once: true } |
| | ) |
| |
|
| | return pendingFinalClientResult |
| | }, |
| | () => { |
| | finalClientReactController.abort() |
| | } |
| | ) |
| |
|
| | const { prelude, preludeIsEmpty } = |
| | await processPrelude(unprocessedPrelude) |
| |
|
| | |
| | |
| | |
| | if (!allowEmptyStaticShell) { |
| | throwIfDisallowedDynamic( |
| | workStore, |
| | preludeIsEmpty ? PreludeState.Empty : PreludeState.Full, |
| | dynamicValidation, |
| | serverDynamicTracking |
| | ) |
| | } |
| |
|
| | const getServerInsertedHTML = makeGetServerInsertedHTML({ |
| | polyfills, |
| | renderServerInsertedHTML, |
| | serverCapturedErrors: allCapturedErrors, |
| | basePath, |
| | tracingMetadata: tracingMetadata, |
| | }) |
| |
|
| | const flightData = await streamToBuffer(reactServerResult.asStream()) |
| | metadata.flightData = flightData |
| | metadata.segmentData = await collectSegmentData( |
| | flightData, |
| | finalServerPrerenderStore, |
| | ComponentMod, |
| | renderOpts |
| | ) |
| |
|
| | if (serverIsDynamic) { |
| | |
| | |
| | |
| | |
| | |
| | if (postponed != null) { |
| | |
| | metadata.postponed = await getDynamicHTMLPostponedState( |
| | postponed, |
| | preludeIsEmpty |
| | ? DynamicHTMLPreludeState.Empty |
| | : DynamicHTMLPreludeState.Full, |
| | fallbackRouteParams, |
| | resumeDataCache, |
| | cacheComponents |
| | ) |
| | } else { |
| | |
| | metadata.postponed = await getDynamicDataPostponedState( |
| | resumeDataCache, |
| | cacheComponents |
| | ) |
| | } |
| | reactServerResult.consume() |
| | return { |
| | digestErrorsMap: reactServerErrorsByDigest, |
| | ssrErrors: allCapturedErrors, |
| | stream: await continueDynamicPrerender(prelude, { |
| | getServerInsertedHTML, |
| | getServerInsertedMetadata, |
| | }), |
| | dynamicAccess: consumeDynamicAccess( |
| | serverDynamicTracking, |
| | clientDynamicTracking |
| | ), |
| | |
| | collectedRevalidate: finalServerPrerenderStore.revalidate, |
| | collectedExpire: finalServerPrerenderStore.expire, |
| | collectedStale: selectStaleTime(finalServerPrerenderStore.stale), |
| | collectedTags: finalServerPrerenderStore.tags, |
| | renderResumeDataCache: createRenderResumeDataCache(resumeDataCache), |
| | } |
| | } else { |
| | |
| | |
| | |
| | if (workStore.forceDynamic) { |
| | throw new StaticGenBailoutError( |
| | 'Invariant: a Page with `dynamic = "force-dynamic"` did not trigger the dynamic pathway. This is a bug in Next.js' |
| | ) |
| | } |
| |
|
| | let htmlStream = prelude |
| | if (postponed != null) { |
| | |
| | |
| | const resume = ( |
| | require('react-dom/server') as typeof import('react-dom/server') |
| | ).resume |
| |
|
| | |
| | |
| | const foreverStream = new ReadableStream<Uint8Array>() |
| |
|
| | const resumeStream = await resume( |
| | |
| | <App |
| | reactServerStream={foreverStream} |
| | reactDebugStream={undefined} |
| | debugEndTime={undefined} |
| | preinitScripts={() => {}} |
| | ServerInsertedHTMLProvider={ServerInsertedHTMLProvider} |
| | nonce={nonce} |
| | images={ctx.renderOpts.images} |
| | />, |
| | JSON.parse(JSON.stringify(postponed)), |
| | { |
| | signal: createRenderInBrowserAbortSignal(), |
| | onError: htmlRendererErrorHandler, |
| | nonce, |
| | } |
| | ) |
| |
|
| | |
| | htmlStream = chainStreams(prelude, resumeStream) |
| | } |
| |
|
| | let finalStream |
| | const hasFallbackRouteParams = |
| | fallbackRouteParams && fallbackRouteParams.size > 0 |
| | if (hasFallbackRouteParams) { |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | const emptyReactServerResult = |
| | await createReactServerPrerenderResultFromRender( |
| | ComponentMod.renderToReadableStream([], clientModules, { |
| | filterStackFrame, |
| | onError: serverComponentsErrorHandler, |
| | }) |
| | ) |
| | finalStream = await continueStaticFallbackPrerender(htmlStream, { |
| | inlinedDataStream: createInlinedDataReadableStream( |
| | emptyReactServerResult.consumeAsStream(), |
| | nonce, |
| | formState |
| | ), |
| | getServerInsertedHTML, |
| | getServerInsertedMetadata, |
| | isBuildTimePrerendering: |
| | ctx.workStore.isBuildTimePrerendering === true, |
| | buildId: ctx.workStore.buildId, |
| | }) |
| | } else { |
| | |
| | finalStream = await continueStaticPrerender(htmlStream, { |
| | inlinedDataStream: createInlinedDataReadableStream( |
| | reactServerResult.consumeAsStream(), |
| | nonce, |
| | formState |
| | ), |
| | getServerInsertedHTML, |
| | getServerInsertedMetadata, |
| | isBuildTimePrerendering: |
| | ctx.workStore.isBuildTimePrerendering === true, |
| | buildId: ctx.workStore.buildId, |
| | }) |
| | } |
| |
|
| | return { |
| | digestErrorsMap: reactServerErrorsByDigest, |
| | ssrErrors: allCapturedErrors, |
| | stream: finalStream, |
| | dynamicAccess: consumeDynamicAccess( |
| | serverDynamicTracking, |
| | clientDynamicTracking |
| | ), |
| | |
| | collectedRevalidate: finalServerPrerenderStore.revalidate, |
| | collectedExpire: finalServerPrerenderStore.expire, |
| | collectedStale: selectStaleTime(finalServerPrerenderStore.stale), |
| | collectedTags: finalServerPrerenderStore.tags, |
| | renderResumeDataCache: createRenderResumeDataCache(resumeDataCache), |
| | } |
| | } |
| | } else if (experimental.isRoutePPREnabled) { |
| | |
| | let dynamicTracking = createDynamicTrackingState(isDebugDynamicAccesses) |
| |
|
| | const prerenderResumeDataCache = createPrerenderResumeDataCache() |
| | const reactServerPrerenderStore: PrerenderStore = (prerenderStore = { |
| | type: 'prerender-ppr', |
| | phase: 'render', |
| | rootParams, |
| | fallbackRouteParams, |
| | implicitTags, |
| | dynamicTracking, |
| | revalidate: INFINITE_CACHE, |
| | expire: INFINITE_CACHE, |
| | stale: INFINITE_CACHE, |
| | tags: [...implicitTags.tags], |
| | prerenderResumeDataCache, |
| | }) |
| | const RSCPayload = await workUnitAsyncStorage.run( |
| | reactServerPrerenderStore, |
| | getRSCPayload, |
| | tree, |
| | ctx, |
| | res.statusCode === 404 |
| | ) |
| | const reactServerResult = (reactServerPrerenderResult = |
| | await createReactServerPrerenderResultFromRender( |
| | workUnitAsyncStorage.run( |
| | reactServerPrerenderStore, |
| | ComponentMod.renderToReadableStream, |
| | |
| | RSCPayload, |
| | clientModules, |
| | { |
| | filterStackFrame, |
| | onError: serverComponentsErrorHandler, |
| | } |
| | ) |
| | )) |
| |
|
| | const ssrPrerenderStore: PrerenderStore = { |
| | type: 'prerender-ppr', |
| | phase: 'render', |
| | rootParams, |
| | fallbackRouteParams, |
| | implicitTags, |
| | dynamicTracking, |
| | revalidate: INFINITE_CACHE, |
| | expire: INFINITE_CACHE, |
| | stale: INFINITE_CACHE, |
| | tags: [...implicitTags.tags], |
| | prerenderResumeDataCache, |
| | } |
| | const prerender = ( |
| | require('react-dom/static') as typeof import('react-dom/static') |
| | ).prerender |
| | const { prelude: unprocessedPrelude, postponed } = |
| | await workUnitAsyncStorage.run( |
| | ssrPrerenderStore, |
| | prerender, |
| | |
| | <App |
| | reactServerStream={reactServerResult.asUnclosingStream()} |
| | reactDebugStream={undefined} |
| | debugEndTime={undefined} |
| | preinitScripts={preinitScripts} |
| | ServerInsertedHTMLProvider={ServerInsertedHTMLProvider} |
| | nonce={nonce} |
| | images={ctx.renderOpts.images} |
| | />, |
| | { |
| | onError: htmlRendererErrorHandler, |
| | onHeaders: (headers: Headers) => { |
| | headers.forEach((value, key) => { |
| | appendHeader(key, value) |
| | }) |
| | }, |
| | maxHeadersLength: reactMaxHeadersLength, |
| | bootstrapScripts: [bootstrapScript], |
| | } |
| | ) |
| | const getServerInsertedHTML = makeGetServerInsertedHTML({ |
| | polyfills, |
| | renderServerInsertedHTML, |
| | serverCapturedErrors: allCapturedErrors, |
| | basePath, |
| | tracingMetadata: tracingMetadata, |
| | }) |
| |
|
| | |
| | |
| | |
| | const flightData = await streamToBuffer(reactServerResult.asStream()) |
| |
|
| | if (shouldGenerateStaticFlightData(workStore)) { |
| | metadata.flightData = flightData |
| | metadata.segmentData = await collectSegmentData( |
| | flightData, |
| | ssrPrerenderStore, |
| | ComponentMod, |
| | renderOpts |
| | ) |
| | } |
| |
|
| | const { prelude, preludeIsEmpty } = |
| | await processPrelude(unprocessedPrelude) |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | if (accessedDynamicData(dynamicTracking.dynamicAccesses)) { |
| | if (postponed != null) { |
| | |
| | metadata.postponed = await getDynamicHTMLPostponedState( |
| | postponed, |
| | preludeIsEmpty |
| | ? DynamicHTMLPreludeState.Empty |
| | : DynamicHTMLPreludeState.Full, |
| | fallbackRouteParams, |
| | prerenderResumeDataCache, |
| | cacheComponents |
| | ) |
| | } else { |
| | |
| | metadata.postponed = await getDynamicDataPostponedState( |
| | prerenderResumeDataCache, |
| | cacheComponents |
| | ) |
| | } |
| | |
| | |
| | |
| | |
| | reactServerResult.consume() |
| | return { |
| | digestErrorsMap: reactServerErrorsByDigest, |
| | ssrErrors: allCapturedErrors, |
| | stream: await continueDynamicPrerender(prelude, { |
| | getServerInsertedHTML, |
| | getServerInsertedMetadata, |
| | }), |
| | dynamicAccess: dynamicTracking.dynamicAccesses, |
| | |
| | collectedRevalidate: reactServerPrerenderStore.revalidate, |
| | collectedExpire: reactServerPrerenderStore.expire, |
| | collectedStale: selectStaleTime(reactServerPrerenderStore.stale), |
| | collectedTags: reactServerPrerenderStore.tags, |
| | } |
| | } else if (fallbackRouteParams && fallbackRouteParams.size > 0) { |
| | |
| | metadata.postponed = await getDynamicDataPostponedState( |
| | prerenderResumeDataCache, |
| | cacheComponents |
| | ) |
| |
|
| | return { |
| | digestErrorsMap: reactServerErrorsByDigest, |
| | ssrErrors: allCapturedErrors, |
| | stream: await continueDynamicPrerender(prelude, { |
| | getServerInsertedHTML, |
| | getServerInsertedMetadata, |
| | }), |
| | dynamicAccess: dynamicTracking.dynamicAccesses, |
| | |
| | collectedRevalidate: reactServerPrerenderStore.revalidate, |
| | collectedExpire: reactServerPrerenderStore.expire, |
| | collectedStale: selectStaleTime(reactServerPrerenderStore.stale), |
| | collectedTags: reactServerPrerenderStore.tags, |
| | } |
| | } else { |
| | |
| | |
| | if (workStore.forceDynamic) { |
| | throw new StaticGenBailoutError( |
| | 'Invariant: a Page with `dynamic = "force-dynamic"` did not trigger the dynamic pathway. This is a bug in Next.js' |
| | ) |
| | } |
| |
|
| | let htmlStream = prelude |
| | if (postponed != null) { |
| | |
| | |
| | const resume = ( |
| | require('react-dom/server') as typeof import('react-dom/server') |
| | ).resume |
| |
|
| | |
| | |
| | const foreverStream = new ReadableStream<Uint8Array>() |
| |
|
| | const resumeStream = await resume( |
| | |
| | <App |
| | reactServerStream={foreverStream} |
| | reactDebugStream={undefined} |
| | debugEndTime={undefined} |
| | preinitScripts={() => {}} |
| | ServerInsertedHTMLProvider={ServerInsertedHTMLProvider} |
| | nonce={nonce} |
| | images={ctx.renderOpts.images} |
| | />, |
| | JSON.parse(JSON.stringify(postponed)), |
| | { |
| | signal: createRenderInBrowserAbortSignal(), |
| | onError: htmlRendererErrorHandler, |
| | nonce, |
| | } |
| | ) |
| |
|
| | |
| | htmlStream = chainStreams(prelude, resumeStream) |
| | } |
| |
|
| | return { |
| | digestErrorsMap: reactServerErrorsByDigest, |
| | ssrErrors: allCapturedErrors, |
| | stream: await continueStaticPrerender(htmlStream, { |
| | inlinedDataStream: createInlinedDataReadableStream( |
| | reactServerResult.consumeAsStream(), |
| | nonce, |
| | formState |
| | ), |
| | getServerInsertedHTML, |
| | getServerInsertedMetadata, |
| | isBuildTimePrerendering: |
| | ctx.workStore.isBuildTimePrerendering === true, |
| | buildId: ctx.workStore.buildId, |
| | }), |
| | dynamicAccess: dynamicTracking.dynamicAccesses, |
| | |
| | collectedRevalidate: reactServerPrerenderStore.revalidate, |
| | collectedExpire: reactServerPrerenderStore.expire, |
| | collectedStale: selectStaleTime(reactServerPrerenderStore.stale), |
| | collectedTags: reactServerPrerenderStore.tags, |
| | } |
| | } |
| | } else { |
| | const prerenderLegacyStore: PrerenderStore = (prerenderStore = { |
| | type: 'prerender-legacy', |
| | phase: 'render', |
| | rootParams, |
| | implicitTags, |
| | revalidate: INFINITE_CACHE, |
| | expire: INFINITE_CACHE, |
| | stale: INFINITE_CACHE, |
| | tags: [...implicitTags.tags], |
| | }) |
| | |
| | |
| | const RSCPayload = await workUnitAsyncStorage.run( |
| | prerenderLegacyStore, |
| | getRSCPayload, |
| | tree, |
| | ctx, |
| | res.statusCode === 404 |
| | ) |
| |
|
| | const reactServerResult = (reactServerPrerenderResult = |
| | await createReactServerPrerenderResultFromRender( |
| | workUnitAsyncStorage.run( |
| | prerenderLegacyStore, |
| | ComponentMod.renderToReadableStream, |
| | RSCPayload, |
| | clientModules, |
| | { |
| | filterStackFrame, |
| | onError: serverComponentsErrorHandler, |
| | } |
| | ) |
| | )) |
| |
|
| | const renderToReadableStream = ( |
| | require('react-dom/server') as typeof import('react-dom/server') |
| | ).renderToReadableStream |
| | const htmlStream = await workUnitAsyncStorage.run( |
| | prerenderLegacyStore, |
| | renderToReadableStream, |
| | |
| | <App |
| | reactServerStream={reactServerResult.asUnclosingStream()} |
| | reactDebugStream={undefined} |
| | debugEndTime={undefined} |
| | preinitScripts={preinitScripts} |
| | ServerInsertedHTMLProvider={ServerInsertedHTMLProvider} |
| | nonce={nonce} |
| | images={ctx.renderOpts.images} |
| | />, |
| | { |
| | onError: htmlRendererErrorHandler, |
| | nonce, |
| | bootstrapScripts: [bootstrapScript], |
| | } |
| | ) |
| |
|
| | if (shouldGenerateStaticFlightData(workStore)) { |
| | const flightData = await streamToBuffer(reactServerResult.asStream()) |
| | metadata.flightData = flightData |
| | metadata.segmentData = await collectSegmentData( |
| | flightData, |
| | prerenderLegacyStore, |
| | ComponentMod, |
| | renderOpts |
| | ) |
| | } |
| |
|
| | const getServerInsertedHTML = makeGetServerInsertedHTML({ |
| | polyfills, |
| | renderServerInsertedHTML, |
| | serverCapturedErrors: allCapturedErrors, |
| | basePath, |
| | tracingMetadata: tracingMetadata, |
| | }) |
| | return { |
| | digestErrorsMap: reactServerErrorsByDigest, |
| | ssrErrors: allCapturedErrors, |
| | stream: await continueFizzStream(htmlStream, { |
| | inlinedDataStream: createInlinedDataReadableStream( |
| | reactServerResult.consumeAsStream(), |
| | nonce, |
| | formState |
| | ), |
| | isStaticGeneration: true, |
| | isBuildTimePrerendering: |
| | ctx.workStore.isBuildTimePrerendering === true, |
| | buildId: ctx.workStore.buildId, |
| | getServerInsertedHTML, |
| | getServerInsertedMetadata, |
| | }), |
| | |
| | collectedRevalidate: prerenderLegacyStore.revalidate, |
| | collectedExpire: prerenderLegacyStore.expire, |
| | collectedStale: selectStaleTime(prerenderLegacyStore.stale), |
| | collectedTags: prerenderLegacyStore.tags, |
| | } |
| | } |
| | } catch (err) { |
| | if ( |
| | isStaticGenBailoutError(err) || |
| | (typeof err === 'object' && |
| | err !== null && |
| | 'message' in err && |
| | typeof err.message === 'string' && |
| | err.message.includes( |
| | 'https://nextjs.org/docs/advanced-features/static-html-export' |
| | )) |
| | ) { |
| | |
| | throw err |
| | } |
| |
|
| | |
| | |
| | if (isDynamicServerError(err)) { |
| | throw err |
| | } |
| |
|
| | |
| | |
| | const shouldBailoutToCSR = isBailoutToCSRError(err) |
| | if (shouldBailoutToCSR) { |
| | const stack = getStackWithoutErrorMessage(err) |
| | error( |
| | `${err.reason} should be wrapped in a suspense boundary at page "${pagePath}". Read more: https://nextjs.org/docs/messages/missing-suspense-with-csr-bailout\n${stack}` |
| | ) |
| |
|
| | throw err |
| | } |
| |
|
| | |
| | |
| | if (reactServerPrerenderResult === null) { |
| | throw err |
| | } |
| |
|
| | let errorType: MetadataErrorType | 'redirect' | undefined |
| |
|
| | if (isHTTPAccessFallbackError(err)) { |
| | res.statusCode = getAccessFallbackHTTPStatus(err) |
| | metadata.statusCode = res.statusCode |
| | errorType = getAccessFallbackErrorTypeByStatus(res.statusCode) |
| | } else if (isRedirectError(err)) { |
| | errorType = 'redirect' |
| | res.statusCode = getRedirectStatusCodeFromError(err) |
| | metadata.statusCode = res.statusCode |
| |
|
| | const redirectUrl = addPathPrefix(getURLFromRedirectError(err), basePath) |
| |
|
| | setHeader('location', redirectUrl) |
| | } else if (!shouldBailoutToCSR) { |
| | res.statusCode = 500 |
| | metadata.statusCode = res.statusCode |
| | } |
| |
|
| | const [errorPreinitScripts, errorBootstrapScript] = getRequiredScripts( |
| | buildManifest, |
| | assetPrefix, |
| | crossOrigin, |
| | subresourceIntegrityManifest, |
| | getAssetQueryString(ctx, false), |
| | nonce, |
| | '/_not-found/page' |
| | ) |
| |
|
| | const prerenderLegacyStore: PrerenderStore = (prerenderStore = { |
| | type: 'prerender-legacy', |
| | phase: 'render', |
| | rootParams, |
| | implicitTags: implicitTags, |
| | revalidate: |
| | typeof prerenderStore?.revalidate !== 'undefined' |
| | ? prerenderStore.revalidate |
| | : INFINITE_CACHE, |
| | expire: |
| | typeof prerenderStore?.expire !== 'undefined' |
| | ? prerenderStore.expire |
| | : INFINITE_CACHE, |
| | stale: |
| | typeof prerenderStore?.stale !== 'undefined' |
| | ? prerenderStore.stale |
| | : INFINITE_CACHE, |
| | tags: [...(prerenderStore?.tags || implicitTags.tags)], |
| | }) |
| | const errorRSCPayload = await workUnitAsyncStorage.run( |
| | prerenderLegacyStore, |
| | getErrorRSCPayload, |
| | tree, |
| | ctx, |
| | reactServerErrorsByDigest.has((err as any).digest) ? undefined : err, |
| | errorType |
| | ) |
| |
|
| | const errorServerStream = workUnitAsyncStorage.run( |
| | prerenderLegacyStore, |
| | ComponentMod.renderToReadableStream, |
| | errorRSCPayload, |
| | clientModules, |
| | { |
| | filterStackFrame, |
| | onError: serverComponentsErrorHandler, |
| | } |
| | ) |
| |
|
| | try { |
| | |
| | |
| | |
| | const fizzStream = await workUnitAsyncStorage.run( |
| | prerenderLegacyStore, |
| | renderToInitialFizzStream, |
| | { |
| | ReactDOMServer: |
| | require('react-dom/server') as typeof import('react-dom/server'), |
| | element: ( |
| | |
| | <ErrorApp |
| | reactServerStream={errorServerStream} |
| | ServerInsertedHTMLProvider={ServerInsertedHTMLProvider} |
| | preinitScripts={errorPreinitScripts} |
| | nonce={nonce} |
| | images={ctx.renderOpts.images} |
| | /> |
| | ), |
| | streamOptions: { |
| | nonce, |
| | |
| | bootstrapScripts: [errorBootstrapScript], |
| | formState, |
| | }, |
| | } |
| | ) |
| |
|
| | if (shouldGenerateStaticFlightData(workStore)) { |
| | const flightData = await streamToBuffer( |
| | reactServerPrerenderResult.asStream() |
| | ) |
| | metadata.flightData = flightData |
| | metadata.segmentData = await collectSegmentData( |
| | flightData, |
| | prerenderLegacyStore, |
| | ComponentMod, |
| | renderOpts |
| | ) |
| | } |
| |
|
| | |
| | |
| | const flightStream = reactServerPrerenderResult.consumeAsStream() |
| |
|
| | return { |
| | |
| | |
| | digestErrorsMap: reactServerErrorsByDigest, |
| | ssrErrors: allCapturedErrors, |
| | stream: await continueFizzStream(fizzStream, { |
| | inlinedDataStream: createInlinedDataReadableStream( |
| | flightStream, |
| | nonce, |
| | formState |
| | ), |
| | isStaticGeneration: true, |
| | isBuildTimePrerendering: |
| | ctx.workStore.isBuildTimePrerendering === true, |
| | buildId: ctx.workStore.buildId, |
| | getServerInsertedHTML: makeGetServerInsertedHTML({ |
| | polyfills, |
| | renderServerInsertedHTML, |
| | serverCapturedErrors: [], |
| | basePath, |
| | tracingMetadata: tracingMetadata, |
| | }), |
| | getServerInsertedMetadata, |
| | validateRootLayout: dev, |
| | }), |
| | dynamicAccess: null, |
| | collectedRevalidate: |
| | prerenderStore !== null ? prerenderStore.revalidate : INFINITE_CACHE, |
| | collectedExpire: |
| | prerenderStore !== null ? prerenderStore.expire : INFINITE_CACHE, |
| | collectedStale: selectStaleTime( |
| | prerenderStore !== null ? prerenderStore.stale : INFINITE_CACHE |
| | ), |
| | collectedTags: prerenderStore !== null ? prerenderStore.tags : null, |
| | } |
| | } catch (finalErr: any) { |
| | if ( |
| | process.env.NODE_ENV === 'development' && |
| | isHTTPAccessFallbackError(finalErr) |
| | ) { |
| | const { bailOnRootNotFound } = |
| | require('../../client/components/dev-root-http-access-fallback-boundary') as typeof import('../../client/components/dev-root-http-access-fallback-boundary') |
| | bailOnRootNotFound() |
| | } |
| | throw finalErr |
| | } |
| | } |
| | } |
| |
|
| | const getGlobalErrorStyles = async ( |
| | tree: LoaderTree, |
| | ctx: AppRenderContext |
| | ): Promise<{ |
| | GlobalError: GlobalErrorComponent |
| | styles: ReactNode | undefined |
| | }> => { |
| | const { |
| | modules: { 'global-error': globalErrorModule }, |
| | } = parseLoaderTree(tree) |
| |
|
| | const { |
| | componentMod: { createElement }, |
| | } = ctx |
| | const GlobalErrorComponent: GlobalErrorComponent = |
| | ctx.componentMod.GlobalError |
| | let globalErrorStyles |
| | if (globalErrorModule) { |
| | const [, styles] = await createComponentStylesAndScripts({ |
| | ctx, |
| | filePath: globalErrorModule[1], |
| | getComponent: globalErrorModule[0], |
| | injectedCSS: new Set(), |
| | injectedJS: new Set(), |
| | }) |
| | globalErrorStyles = styles |
| | } |
| | if (ctx.renderOpts.dev) { |
| | const dir = |
| | (process.env.NEXT_RUNTIME === 'edge' |
| | ? process.env.__NEXT_EDGE_PROJECT_DIR |
| | : ctx.renderOpts.dir) || '' |
| |
|
| | const globalErrorModulePath = normalizeConventionFilePath( |
| | dir, |
| | globalErrorModule?.[1] |
| | ) |
| | if (globalErrorModulePath) { |
| | const SegmentViewNode = ctx.componentMod.SegmentViewNode |
| | globalErrorStyles = |
| | |
| | |
| | createElement( |
| | SegmentViewNode, |
| | { |
| | key: 'ge-svn', |
| | type: 'global-error', |
| | pagePath: globalErrorModulePath, |
| | }, |
| | globalErrorStyles |
| | ) |
| | } |
| | } |
| |
|
| | return { |
| | GlobalError: GlobalErrorComponent, |
| | styles: globalErrorStyles, |
| | } |
| | } |
| |
|
| | function createSelectStaleTime(experimental: ExperimentalConfig) { |
| | return (stale: number) => |
| | stale === INFINITE_CACHE && |
| | typeof experimental.staleTimes?.static === 'number' |
| | ? experimental.staleTimes.static |
| | : stale |
| | } |
| |
|
| | async function collectSegmentData( |
| | fullPageDataBuffer: Buffer, |
| | prerenderStore: PrerenderStore, |
| | ComponentMod: AppPageModule, |
| | renderOpts: RenderOpts |
| | ): Promise<Map<string, Buffer> | undefined> { |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| |
|
| | const { clientModules, edgeRscModuleMapping, rscModuleMapping } = |
| | getClientReferenceManifest() |
| |
|
| | |
| | |
| | const isEdgeRuntime = process.env.NEXT_RUNTIME === 'edge' |
| | const serverConsumerManifest = { |
| | |
| | |
| | |
| | moduleLoading: null, |
| | moduleMap: isEdgeRuntime ? edgeRscModuleMapping : rscModuleMapping, |
| | serverModuleMap: getServerModuleMap(), |
| | } |
| |
|
| | const selectStaleTime = createSelectStaleTime(renderOpts.experimental) |
| | const staleTime = selectStaleTime(prerenderStore.stale) |
| | return await ComponentMod.collectSegmentData( |
| | renderOpts.cacheComponents, |
| | fullPageDataBuffer, |
| | staleTime, |
| | clientModules, |
| | serverConsumerManifest |
| | ) |
| | } |
| |
|
| | function isBypassingCachesInDev( |
| | renderOpts: RenderOpts, |
| | requestStore: RequestStore |
| | ): boolean { |
| | return ( |
| | process.env.NODE_ENV === 'development' && |
| | !!renderOpts.dev && |
| | requestStore.headers.get('cache-control') === 'no-cache' |
| | ) |
| | } |
| |
|
| | function WarnForBypassCachesInDev({ route }: { route: string }) { |
| | warnOnce( |
| | `Route ${route} is rendering with server caches disabled. For this navigation, Component Metadata in React DevTools will not accurately reflect what is statically prerenderable and runtime prefetchable. See more info here: https://nextjs.org/docs/messages/cache-bypass-in-dev` |
| | ) |
| | return null |
| | } |
| |
|
| | function nodeStreamFromReadableStream<T>(stream: ReadableStream<T>) { |
| | if (process.env.NEXT_RUNTIME === 'edge') { |
| | throw new InvariantError( |
| | 'nodeStreamFromReadableStream cannot be used in the edge runtime' |
| | ) |
| | } else { |
| | const reader = stream.getReader() |
| |
|
| | const { Readable } = require('node:stream') as typeof import('node:stream') |
| |
|
| | return new Readable({ |
| | read() { |
| | reader |
| | .read() |
| | .then(({ done, value }) => { |
| | if (done) { |
| | this.push(null) |
| | } else { |
| | this.push(value) |
| | } |
| | }) |
| | .catch((err) => this.destroy(err)) |
| | }, |
| | }) |
| | } |
| | } |
| |
|
| | function createNodeStreamFromChunks( |
| | partialChunks: Array<Uint8Array>, |
| | allChunks: Array<Uint8Array>, |
| | signal: AbortSignal |
| | ): Readable { |
| | if (process.env.NEXT_RUNTIME === 'edge') { |
| | throw new InvariantError( |
| | 'createNodeStreamFromChunks cannot be used in the edge runtime' |
| | ) |
| | } else { |
| | const { Readable } = require('node:stream') as typeof import('node:stream') |
| |
|
| | let nextIndex = 0 |
| |
|
| | const readable = new Readable({ |
| | read() { |
| | while (nextIndex < partialChunks.length) { |
| | this.push(partialChunks[nextIndex]) |
| | nextIndex++ |
| | } |
| | }, |
| | }) |
| |
|
| | signal.addEventListener( |
| | 'abort', |
| | () => { |
| | |
| | while (nextIndex < partialChunks.length) { |
| | readable.push(partialChunks[nextIndex]) |
| | nextIndex++ |
| | } |
| | |
| | |
| | while (nextIndex < allChunks.length) { |
| | readable.push(allChunks[nextIndex]) |
| | nextIndex++ |
| | } |
| |
|
| | setImmediate(() => { |
| | readable.push(null) |
| | }) |
| | }, |
| | { once: true } |
| | ) |
| |
|
| | return readable |
| | } |
| | } |
| |
|