UNPKG

next

Version:

The React Framework

767 lines (766 loc) • 40.4 kB
import { propagateSubtreeBits } from '../../shared/lib/app-router-types'; import { segmentToTransportSegment } from '../../shared/lib/rsc-transport'; import { computeSegmentPrefetchHints, createTransportTreeFromLoaderTree } from './create-transport-tree-from-loader-tree'; import { isClientReference, isUseCacheFunction } from '../../lib/client-and-server-references'; import { getLayoutOrPageModule } from '../lib/app-dir-module'; import { interopDefault } from './interop-default'; import { parseLoaderTree } from '../../shared/lib/router/utils/parse-loader-tree'; import { createComponentStylesAndScripts } from './create-component-styles-and-scripts'; import { getLayerAssets } from './get-layer-assets'; import { hasLoadingComponentInTree } from './has-loading-component-in-tree'; import { validateRevalidate } from '../lib/patch-fetch'; import { PARALLEL_ROUTE_DEFAULT_PATH } from '../../client/components/builtin/default'; import { getTracer } from '../lib/trace/tracer'; import { NextNodeServerSpan } from '../lib/trace/constants'; import { StaticGenBailoutError } from '../../client/components/static-generation-bailout'; import { workUnitAsyncStorage } from './work-unit-async-storage.external'; import { InvariantError } from '../../shared/lib/invariant-error'; import { createVaryParamsAccumulator, emptyVaryParamsAccumulator } from './vary-params'; import { DEFAULT_SEGMENT_KEY } from '../../shared/lib/segment'; import { BOUNDARY_PREFIX, BOUNDARY_SUFFIX, BUILTIN_PREFIX, getConventionPathByType, isNextjsBuiltinFilePath } from './segment-explorer-path'; /** * Use the provided loader tree to create the React Component tree, returned * as the response's transport tree: each node carries its segment identity, * its prefetch hints, and its render output. */ // TODO convert these arguments to non-object form. the entrypoint doesn't need most of them export function createComponentTree(props) { const workUnitStore = workUnitAsyncStorage.getStore(); if (!workUnitStore) { throw new InvariantError('Missing workUnitStore in createComponentTree'); } return getTracer().trace(NextNodeServerSpan.createComponentTree, { spanName: 'build component tree' }, ()=>createComponentTreeInternal(props, true, workUnitStore)); } /** * Variant of createComponentTree for full renders — the initial document * (and error) payloads, which are never prefetches. No subtree is cut at a * loading boundary, so every node carries its render output, which is what * FullTransportNode requires. TypeScript can't see through that invariant, * hence the cast. */ export function createFullComponentTree(props) { return createComponentTree(props); } function errorMissingDefaultExport(pagePath, convention) { const normalizedPagePath = pagePath === '/' ? '' : pagePath; throw new Error(`The default export is not a React Component in "${normalizedPagePath}/${convention}"`); } const cacheNodeKey = 'c'; async function createComponentTreeInternal({ loaderTree: tree, parentParams, parentOptionalCatchAllParamName, rootLayoutIncluded, injectedCSS, injectedJS, injectedFontPreloadTags, ctx, missingSlots, preloadCallbacks, authInterrupts, MetadataOutlet, isPrerendering, hintTree }, isRoot, workUnitStore) { const { renderOpts: { nextConfigOutput, experimental, cacheComponents }, workStore, componentMod: { createElement, Fragment, SegmentViewNode, HTTPAccessFallbackBoundary, LayoutRouter, RenderFromTemplateContext, ClientPageRoot, ClientSegmentRoot, createServerSearchParamsForServerPage, createPrerenderSearchParamsForClientPage, createServerParamsForServerSegment, createPrerenderParamsForClientSegment, serverHooks: { DynamicServerError } }, pagePath, getDynamicParamFromSegment, isPrefetch, renderCapabilities, query } = ctx; const { isPossiblyPartialResponse } = renderCapabilities; const { page, conventionPath, segment, modules, parallelRoutes } = parseLoaderTree(tree); const prefetchInliningEnabled = Boolean(experimental.prefetchInlining); const partialPrefetching = Boolean(ctx.renderOpts.partialPrefetching); const { layout, template, error, loading, 'not-found': notFound, forbidden, unauthorized } = modules; const injectedCSSWithCurrentLayout = new Set(injectedCSS); const injectedJSWithCurrentLayout = new Set(injectedJS); const injectedFontPreloadTagsWithCurrentLayout = new Set(injectedFontPreloadTags); const layerAssets = getLayerAssets({ preloadCallbacks, ctx, layoutOrPagePath: conventionPath, injectedCSS: injectedCSSWithCurrentLayout, injectedJS: injectedJSWithCurrentLayout, injectedFontPreloadTags: injectedFontPreloadTagsWithCurrentLayout }); const [Template, templateStyles, templateScripts] = template ? await createComponentStylesAndScripts({ ctx, filePath: template[1], getComponent: template[0], injectedCSS: injectedCSSWithCurrentLayout, injectedJS: injectedJSWithCurrentLayout }) : [ Fragment ]; const [ErrorComponent, errorStyles, errorScripts] = error ? await createComponentStylesAndScripts({ ctx, filePath: error[1], getComponent: error[0], injectedCSS: injectedCSSWithCurrentLayout, injectedJS: injectedJSWithCurrentLayout }) : []; const [Loading, loadingStyles, loadingScripts] = loading ? await createComponentStylesAndScripts({ ctx, filePath: loading[1], getComponent: loading[0], injectedCSS: injectedCSSWithCurrentLayout, injectedJS: injectedJSWithCurrentLayout }) : []; const isLayout = typeof layout !== 'undefined'; const isPage = typeof page !== 'undefined'; const { mod: layoutOrPageMod, modType } = await getTracer().trace(NextNodeServerSpan.getLayoutOrPageModule, { hideSpan: !(isLayout || isPage), spanName: 'resolve segment modules', attributes: { 'next.segment': segment } }, ()=>getLayoutOrPageModule(tree)); /** * Checks if the current segment is a root layout. */ const rootLayoutAtThisLevel = isLayout && !rootLayoutIncluded; /** * Checks if the current segment or any level above it has a root layout. */ const rootLayoutIncludedAtThisLevelOrAbove = rootLayoutIncluded || rootLayoutAtThisLevel; const [NotFound, notFoundStyles] = notFound ? await createComponentStylesAndScripts({ ctx, filePath: notFound[1], getComponent: notFound[0], injectedCSS: injectedCSSWithCurrentLayout, injectedJS: injectedJSWithCurrentLayout }) : []; const [Forbidden, forbiddenStyles] = authInterrupts && forbidden ? await createComponentStylesAndScripts({ ctx, filePath: forbidden[1], getComponent: forbidden[0], injectedCSS: injectedCSSWithCurrentLayout, injectedJS: injectedJSWithCurrentLayout }) : []; const [Unauthorized, unauthorizedStyles] = authInterrupts && unauthorized ? await createComponentStylesAndScripts({ ctx, filePath: unauthorized[1], getComponent: unauthorized[0], injectedCSS: injectedCSSWithCurrentLayout, injectedJS: injectedJSWithCurrentLayout }) : []; let dynamic = layoutOrPageMod == null ? void 0 : layoutOrPageMod.dynamic; if (nextConfigOutput === 'export') { if (!dynamic || dynamic === 'auto') { dynamic = 'error'; } else if (dynamic === 'force-dynamic') { // force-dynamic is always incompatible with 'export'. We must interrupt the build throw new StaticGenBailoutError(`Page with \`dynamic = "force-dynamic"\` couldn't be exported. \`output: "export"\` requires all pages be renderable statically because there is no runtime server to dynamically render routes in this output format. Learn more: https://nextjs.org/docs/app/building-your-application/deploying/static-exports`); } } if (typeof dynamic === 'string') { // the nested most config wins so we only force-static // if it's configured above any parent that configured // otherwise if (dynamic === 'error') { workStore.dynamicShouldError = true; } else if (dynamic === 'force-dynamic') { workStore.forceDynamic = true; if (isPrerendering) { const err = new DynamicServerError(`Page with \`dynamic = "force-dynamic"\` won't be rendered statically.`); workStore.dynamicUsageDescription = err.message; workStore.dynamicUsageStack = err.stack; throw err; } } else { workStore.dynamicShouldError = false; workStore.forceStatic = dynamic === 'force-static'; } } if (typeof (layoutOrPageMod == null ? void 0 : layoutOrPageMod.fetchCache) === 'string') { workStore.fetchCache = layoutOrPageMod == null ? void 0 : layoutOrPageMod.fetchCache; } if (typeof (layoutOrPageMod == null ? void 0 : layoutOrPageMod.revalidate) !== 'undefined') { validateRevalidate(layoutOrPageMod == null ? void 0 : layoutOrPageMod.revalidate, workStore.route); } if (typeof (layoutOrPageMod == null ? void 0 : layoutOrPageMod.revalidate) === 'number') { const defaultRevalidate = layoutOrPageMod.revalidate; switch(workUnitStore.type){ case 'prerender': case 'prerender-runtime': case 'prerender-legacy': if (workUnitStore.revalidate > defaultRevalidate) { workUnitStore.revalidate = defaultRevalidate; } break; case 'request': break; // createComponentTree is not called for these stores: case 'cache': case 'private-cache': case 'prerender-client': case 'validation-client': case 'unstable-cache': case 'build-time-generator': break; default: workUnitStore; } if (!workStore.forceStatic && isPrerendering && defaultRevalidate === 0) { const dynamicUsageDescription = `revalidate: 0 configured ${segment}`; workStore.dynamicUsageDescription = dynamicUsageDescription; throw new DynamicServerError(dynamicUsageDescription); } } // Read unstable_dynamicStaleTime from page modules (not layouts) and track it on // the store's stale field. This affects the segment cache stale time via // the StaleTimeIterable. if (isPage && typeof (layoutOrPageMod == null ? void 0 : layoutOrPageMod.unstable_dynamicStaleTime) === 'number') { const pageStaleTime = layoutOrPageMod.unstable_dynamicStaleTime; switch(workUnitStore.type){ case 'prerender': case 'prerender-runtime': case 'prerender-legacy': if (workUnitStore.stale > pageStaleTime) { workUnitStore.stale = pageStaleTime; } break; case 'request': if (workUnitStore.stale === undefined || workUnitStore.stale > pageStaleTime) { workUnitStore.stale = pageStaleTime; } break; // createComponentTree is not called for these stores: case 'cache': case 'private-cache': case 'prerender-client': case 'validation-client': case 'unstable-cache': case 'build-time-generator': break; default: workUnitStore; } } const LayoutOrPage = layoutOrPageMod ? interopDefault(layoutOrPageMod) : undefined; /** * The React Component to render. */ let MaybeComponent = LayoutOrPage; if (process.env.NODE_ENV === 'development' || isPrerendering) { const { isValidElementType } = require('next/dist/compiled/react-is'); if (typeof MaybeComponent !== 'undefined' && !isValidElementType(MaybeComponent)) { errorMissingDefaultExport(pagePath, modType ?? 'page'); } if (typeof ErrorComponent !== 'undefined' && !isValidElementType(ErrorComponent)) { errorMissingDefaultExport(pagePath, 'error'); } if (typeof Loading !== 'undefined' && !isValidElementType(Loading)) { errorMissingDefaultExport(pagePath, 'loading'); } if (typeof NotFound !== 'undefined' && !isValidElementType(NotFound)) { errorMissingDefaultExport(pagePath, 'not-found'); } if (typeof Forbidden !== 'undefined' && !isValidElementType(Forbidden)) { errorMissingDefaultExport(pagePath, 'forbidden'); } if (typeof Unauthorized !== 'undefined' && !isValidElementType(Unauthorized)) { errorMissingDefaultExport(pagePath, 'unauthorized'); } } // Handle dynamic segment params. const segmentParam = getDynamicParamFromSegment(tree); // The segment's identity on the wire. const transportSegment = segmentToTransportSegment(segmentParam ? segmentParam.treeSegment : segment); // Create object holding the parent params and current params let currentParams = parentParams; if (segmentParam && segmentParam.value !== null) { currentParams = { ...parentParams, [segmentParam.param]: segmentParam.value }; } // Track optional catch-all params with no value (e.g., [[...slug]] at /). // These params won't exist as properties on the params object, so vary // params tracking needs to use a Proxy to detect access. We propagate this // through the tree so that child segments (like __PAGE__) also know about // the missing param. In practice, this only gets passed down one level — // from the optional catch-all layout segment to the page segment — so it's // always very close to the leaf of the tree. const optionalCatchAllParamName = (segmentParam == null ? void 0 : segmentParam.type) === 'oc' && segmentParam.value === null ? segmentParam.param : parentOptionalCatchAllParamName; // Resolve the segment param const isSegmentViewEnabled = !!process.env.__NEXT_DEV_SERVER; const dir = (process.env.NEXT_RUNTIME === 'edge' ? process.env.__NEXT_EDGE_PROJECT_DIR : ctx.renderOpts.dir) || ''; const [notFoundElement, notFoundFilePath] = await createBoundaryConventionElement({ ctx, conventionName: 'not-found', Component: NotFound, styles: notFoundStyles, tree }); const [forbiddenElement] = await createBoundaryConventionElement({ ctx, conventionName: 'forbidden', Component: Forbidden, styles: forbiddenStyles, tree }); const [unauthorizedElement] = await createBoundaryConventionElement({ ctx, conventionName: 'unauthorized', Component: Unauthorized, styles: unauthorizedStyles, tree }); // TODO: Combine this `map` traversal with the loop below that turns the array // into an object. const parallelRouteMap = await Promise.all(Object.keys(parallelRoutes).map(async (parallelRouteKey)=>{ var _hintTree_slots; const isChildrenRouteKey = parallelRouteKey === 'children'; const parallelRoute = parallelRoutes[parallelRouteKey]; const childHintTree = (hintTree == null ? void 0 : (_hintTree_slots = hintTree.slots) == null ? void 0 : _hintTree_slots[parallelRouteKey]) ?? null; const notFoundComponent = isChildrenRouteKey ? notFoundElement : undefined; const forbiddenComponent = isChildrenRouteKey ? forbiddenElement : undefined; const unauthorizedComponent = isChildrenRouteKey ? unauthorizedElement : undefined; // if we're prefetching and that there's a Loading component, we bail out // otherwise we keep rendering for the prefetch. // We also want to bail out if there's no Loading component in the tree. let childNode; if (// Before PPR, the way instant navigations work in Next.js is we // prefetch everything up to the first route segment that defines a // loading.tsx boundary. (We do the same if there's no loading // boundary in the entire tree, because we don't want to prefetch too // much) The rest of the tree is deferred until the actual navigation. // It does not take into account whether the data is dynamic — even if // the tree is completely static, it will still defer everything // inside the loading boundary. // // This behavior predates PPR and is only relevant if the // PPR flag is not enabled. isPrefetch && (Loading || !hasLoadingComponentInTree(parallelRoute)) && // The approach with PPR is different — loading.tsx behaves like a // regular Suspense boundary and has no special behavior. // // With PPR, we prefetch as deeply as possible, and only defer when // dynamic data is accessed. If so, we only defer the nearest parent // Suspense boundary of the dynamic data access, regardless of whether // the boundary is defined by loading.tsx or a normal <Suspense> // component in userspace. // // NOTE: In practice this usually means we'll end up prefetching more // than we were before PPR, which may or may not be considered a // performance regression by some apps. The plan is to address this // before General Availability of PPR by introducing granular // per-segment fetching, so we can reuse as much of the tree as // possible during both prefetches and dynamic navigations. But during // the beta period, we should be clear about this trade off in our // communications. !experimental.isRoutePPREnabled) { // Don't render this child. Emit its structure only — no render // output on any node — which will trigger a lazy fetch by the // client router. childNode = await createTransportTreeFromLoaderTree(parallelRoute, childHintTree, prefetchInliningEnabled, ctx.missingPrefetchHintPolicy, partialPrefetching, getDynamicParamFromSegment, ctx.renderOpts.notFoundParams, rootLayoutIncludedAtThisLevelOrAbove); } else { // Create the child component if (process.env.NODE_ENV === 'development' && missingSlots) { var _parsedTree_conventionPath; // When we detect the default fallback (which triggers a 404), we collect the missing slots // to provide more helpful debug information during development mode. const parsedTree = parseLoaderTree(parallelRoute); if ((_parsedTree_conventionPath = parsedTree.conventionPath) == null ? void 0 : _parsedTree_conventionPath.endsWith(PARALLEL_ROUTE_DEFAULT_PATH)) { missingSlots.add(parallelRouteKey); } } childNode = await createComponentTreeInternal({ loaderTree: parallelRoute, parentParams: currentParams, parentOptionalCatchAllParamName: optionalCatchAllParamName, rootLayoutIncluded: rootLayoutIncludedAtThisLevelOrAbove, injectedCSS: injectedCSSWithCurrentLayout, injectedJS: injectedJSWithCurrentLayout, injectedFontPreloadTags: injectedFontPreloadTagsWithCurrentLayout, ctx, missingSlots, preloadCallbacks, authInterrupts, MetadataOutlet: experimental.parallelRouteMetadata || isChildrenRouteKey ? MetadataOutlet : null, isPrerendering, hintTree: childHintTree }, false, workUnitStore); } const templateNode = createElement(Template, null, createElement(RenderFromTemplateContext, null)); const templateFilePath = getConventionPathByType(tree, dir, 'template'); const errorFilePath = getConventionPathByType(tree, dir, 'error'); const loadingFilePath = getConventionPathByType(tree, dir, 'loading'); const globalErrorFilePath = isRoot ? getConventionPathByType(tree, dir, 'global-error') : undefined; const wrappedErrorStyles = isSegmentViewEnabled && errorFilePath ? createElement(SegmentViewNode, { type: 'error', pagePath: errorFilePath }, errorStyles) : errorStyles; // Add a suffix to avoid conflict with the segment view node representing rendered file. // existence: not-found.tsx@boundary // rendered: not-found.tsx const fileNameSuffix = BOUNDARY_SUFFIX; const segmentViewBoundaries = isSegmentViewEnabled ? createElement(Fragment, null, notFoundFilePath && createElement(SegmentViewNode, { type: `${BOUNDARY_PREFIX}not-found`, pagePath: notFoundFilePath + fileNameSuffix }), loadingFilePath && createElement(SegmentViewNode, { type: `${BOUNDARY_PREFIX}loading`, pagePath: loadingFilePath + fileNameSuffix }), errorFilePath && createElement(SegmentViewNode, { type: `${BOUNDARY_PREFIX}error`, pagePath: errorFilePath + fileNameSuffix }), globalErrorFilePath && createElement(SegmentViewNode, { type: `${BOUNDARY_PREFIX}global-error`, pagePath: isNextjsBuiltinFilePath(globalErrorFilePath) ? `${BUILTIN_PREFIX}global-error.js${fileNameSuffix}` : globalErrorFilePath })) : null; return [ parallelRouteKey, createElement(LayoutRouter, { parallelRouterKey: parallelRouteKey, error: ErrorComponent, errorStyles: wrappedErrorStyles, errorScripts: errorScripts, template: isSegmentViewEnabled && templateFilePath ? createElement(SegmentViewNode, { type: 'template', pagePath: templateFilePath }, templateNode) : templateNode, templateStyles: templateStyles, templateScripts: templateScripts, notFound: notFoundComponent, forbidden: forbiddenComponent, unauthorized: unauthorizedComponent, ...isSegmentViewEnabled && { segmentViewBoundaries } }), childNode ]; })); // The segment's local prefetch hints, plus the "subtree" bits propagated // up from the children in the loop below. let prefetchHints = await computeSegmentPrefetchHints(tree, hintTree, prefetchInliningEnabled, ctx.missingPrefetchHintPolicy, partialPrefetching, !rootLayoutIncluded, ctx.renderOpts.notFoundParams); // Convert the parallel route map into an object after all promises have been resolved. let parallelRouteProps = {}; let parallelRouteNodes; for (const parallelRoute of parallelRouteMap){ const [parallelRouteKey, parallelRouteProp, childNode] = parallelRoute; parallelRouteProps[parallelRouteKey] = parallelRouteProp; if (parallelRouteNodes === undefined) { parallelRouteNodes = new Map(); } parallelRouteNodes.set(parallelRouteKey, childNode); // Propagate subtree flags from children if (childNode.h !== undefined) { prefetchHints = propagateSubtreeBits(prefetchHints, childNode.h); } } let loadingElement = Loading ? createElement(Loading, { key: 'l' }) : null; const loadingFilePath = getConventionPathByType(tree, dir, 'loading'); if (isSegmentViewEnabled && loadingElement) { if (loadingFilePath) { loadingElement = createElement(SegmentViewNode, { key: cacheNodeKey + '-loading', type: 'loading', pagePath: loadingFilePath }, loadingElement); } } const loadingData = loadingElement ? [ loadingElement, loadingStyles, loadingScripts ] : null; // When the segment does not have a layout or page we still have to add the layout router to ensure the path holds the loading component if (!MaybeComponent) { return createTransportNode(ctx, transportSegment, prefetchHints, createElement(Fragment, { key: cacheNodeKey }, layerAssets, parallelRouteProps.children), parallelRouteNodes, loadingData, isPossiblyPartialResponse, // No user-provided component, so no params will be accessed. Use the // pre-resolved empty tracker. emptyVaryParamsAccumulator); } const Component = MaybeComponent; const isClientComponent = isClientReference(layoutOrPageMod); const varyParamsAccumulator = isClientComponent && cacheComponents ? // from the server, so they have an empty vary params set. emptyVaryParamsAccumulator : createVaryParamsAccumulator(); if (process.env.NODE_ENV === 'development' && 'params' in parallelRouteProps) { // @TODO consider making this an error and running the check in build as well console.error(`"params" is a reserved prop in Layouts and Pages and cannot be used as the name of a parallel route in ${segment}`); } if (isPage) { const PageComponent = Component; // Assign searchParams to props if this is a page let pageElement; if (isClientComponent) { if (cacheComponents) { // Params are omitted when Cache Components is enabled pageElement = createElement(ClientPageRoot, { Component: PageComponent, serverProvidedParams: null }); } else if (isPrerendering) { const promiseOfParams = createPrerenderParamsForClientSegment(currentParams); const promiseOfSearchParams = createPrerenderSearchParamsForClientPage(); pageElement = createElement(ClientPageRoot, { Component: PageComponent, serverProvidedParams: { searchParams: query, params: currentParams, promises: [ promiseOfSearchParams, promiseOfParams ] } }); } else { pageElement = createElement(ClientPageRoot, { Component: PageComponent, serverProvidedParams: { searchParams: query, params: currentParams, promises: null } }); } } else { // If we are passing params to a server component Page we need to track // their usage in case the current render mode tracks dynamic API usage. const params = createServerParamsForServerSegment(currentParams, optionalCatchAllParamName, varyParamsAccumulator); // If we are passing searchParams to a server component Page we need to // track their usage in case the current render mode tracks dynamic API // usage. let searchParams = createServerSearchParamsForServerPage(query, varyParamsAccumulator); if (isUseCacheFunction(PageComponent)) { const UseCachePageComponent = PageComponent; pageElement = createElement(UseCachePageComponent, { params: params, searchParams: searchParams, $$isPage: true }); } else { pageElement = createElement(PageComponent, { params: params, searchParams: searchParams }); } } const isDefaultSegment = segment === DEFAULT_SEGMENT_KEY; const pageFilePath = getConventionPathByType(tree, dir, 'page') ?? getConventionPathByType(tree, dir, 'defaultPage'); const segmentType = isDefaultSegment ? 'default' : 'page'; const wrappedPageElement = isSegmentViewEnabled && pageFilePath ? createElement(SegmentViewNode, { key: cacheNodeKey + '-' + segmentType, type: segmentType, pagePath: pageFilePath }, pageElement) : pageElement; return createTransportNode(ctx, transportSegment, prefetchHints, createElement(Fragment, { key: cacheNodeKey }, wrappedPageElement, layerAssets, MetadataOutlet ? createElement(MetadataOutlet, { tree }) : null), parallelRouteNodes, loadingData, isPossiblyPartialResponse, varyParamsAccumulator); } else { const SegmentComponent = Component; const isRootLayoutWithChildrenSlotAndAtLeastOneMoreSlot = rootLayoutAtThisLevel && 'children' in parallelRoutes && Object.keys(parallelRoutes).length > 1; let segmentNode; if (isClientComponent) { let clientSegment; if (cacheComponents) { // Params are omitted when Cache Components is enabled clientSegment = createElement(ClientSegmentRoot, { Component: SegmentComponent, slots: parallelRouteProps, serverProvidedParams: null }); } else if (isPrerendering) { const promiseOfParams = createPrerenderParamsForClientSegment(currentParams); clientSegment = createElement(ClientSegmentRoot, { Component: SegmentComponent, slots: parallelRouteProps, serverProvidedParams: { params: currentParams, promises: [ promiseOfParams ] } }); } else { clientSegment = createElement(ClientSegmentRoot, { Component: SegmentComponent, slots: parallelRouteProps, serverProvidedParams: { params: currentParams, promises: null } }); } if (isRootLayoutWithChildrenSlotAndAtLeastOneMoreSlot) { let notfoundClientSegment; let forbiddenClientSegment; let unauthorizedClientSegment; // TODO-APP: This is a hack to support unmatched parallel routes, which will throw `notFound()`. // This ensures that a `HTTPAccessFallbackBoundary` is available for when that happens, // but it's not ideal, as it needlessly invokes the `NotFound` component and renders the `RootLayout` twice. // We should instead look into handling the fallback behavior differently in development mode so that it doesn't // rely on the `NotFound` behavior. notfoundClientSegment = createErrorBoundaryClientSegmentRoot({ ctx, ErrorBoundaryComponent: NotFound, errorElement: notFoundElement, ClientSegmentRoot, layerAssets, SegmentComponent, currentParams }); forbiddenClientSegment = createErrorBoundaryClientSegmentRoot({ ctx, ErrorBoundaryComponent: Forbidden, errorElement: forbiddenElement, ClientSegmentRoot, layerAssets, SegmentComponent, currentParams }); unauthorizedClientSegment = createErrorBoundaryClientSegmentRoot({ ctx, ErrorBoundaryComponent: Unauthorized, errorElement: unauthorizedElement, ClientSegmentRoot, layerAssets, SegmentComponent, currentParams }); if (notfoundClientSegment || forbiddenClientSegment || unauthorizedClientSegment) { segmentNode = createElement(HTTPAccessFallbackBoundary, { key: cacheNodeKey, notFound: notfoundClientSegment, forbidden: forbiddenClientSegment, unauthorized: unauthorizedClientSegment }, layerAssets, clientSegment); } else { segmentNode = createElement(Fragment, { key: cacheNodeKey }, layerAssets, clientSegment); } } else { segmentNode = createElement(Fragment, { key: cacheNodeKey }, layerAssets, clientSegment); } } else { const params = createServerParamsForServerSegment(currentParams, optionalCatchAllParamName, varyParamsAccumulator); let serverSegment; if (isUseCacheFunction(SegmentComponent)) { const UseCacheLayoutComponent = SegmentComponent; serverSegment = createElement(UseCacheLayoutComponent, { ...parallelRouteProps, params: params, $$isLayout: true }, // Force static children here so that they're validated. // See https://github.com/facebook/react/pull/34846 parallelRouteProps.children); } else { serverSegment = createElement(SegmentComponent, { ...parallelRouteProps, params: params }, // Force static children here so that they're validated. // See https://github.com/facebook/react/pull/34846 parallelRouteProps.children); } if (isRootLayoutWithChildrenSlotAndAtLeastOneMoreSlot) { // TODO-APP: This is a hack to support unmatched parallel routes, which will throw `notFound()`. // This ensures that a `HTTPAccessFallbackBoundary` is available for when that happens, // but it's not ideal, as it needlessly invokes the `NotFound` component and renders the `RootLayout` twice. // We should instead look into handling the fallback behavior differently in development mode so that it doesn't // rely on the `NotFound` behavior. segmentNode = createElement(HTTPAccessFallbackBoundary, { key: cacheNodeKey, notFound: notFoundElement ? createElement(Fragment, null, layerAssets, createElement(SegmentComponent, { params: params }, notFoundStyles, notFoundElement)) : undefined }, layerAssets, serverSegment); } else { segmentNode = createElement(Fragment, { key: cacheNodeKey }, layerAssets, serverSegment); } } const layoutFilePath = getConventionPathByType(tree, dir, 'layout'); const wrappedSegmentNode = isSegmentViewEnabled && layoutFilePath ? createElement(SegmentViewNode, { key: 'layout', type: 'layout', pagePath: layoutFilePath }, segmentNode) : segmentNode; // For layouts we just render the component return createTransportNode(ctx, transportSegment, prefetchHints, wrappedSegmentNode, parallelRouteNodes, loadingData, isPossiblyPartialResponse, varyParamsAccumulator); } } function createErrorBoundaryClientSegmentRoot({ ctx, ErrorBoundaryComponent, errorElement, ClientSegmentRoot, layerAssets, SegmentComponent, currentParams }) { const { componentMod: { createElement, Fragment } } = ctx; if (ErrorBoundaryComponent) { const notFoundParallelRouteProps = { children: errorElement }; return createElement(Fragment, null, layerAssets, createElement(ClientSegmentRoot, { Component: SegmentComponent, slots: notFoundParallelRouteProps, params: currentParams })); } return null; } export function getRootParams(loaderTree, getDynamicParamFromSegment) { return getRootParamsImpl({}, loaderTree, getDynamicParamFromSegment); } function getRootParamsImpl(parentParams, loaderTree, getDynamicParamFromSegment) { const { modules: { layout }, parallelRoutes } = parseLoaderTree(loaderTree); const segmentParam = getDynamicParamFromSegment(loaderTree); let currentParams = parentParams; if (segmentParam && segmentParam.value !== null) { currentParams = { ...parentParams, [segmentParam.param]: segmentParam.value }; } const isRootLayout = typeof layout !== 'undefined'; if (isRootLayout) { return currentParams; } else if (!parallelRoutes.children) { // This should really be an error but there are bugs in Turbopack that cause // the _not-found LoaderTree to not have any layouts. For rootParams sake // this is somewhat irrelevant when you are not customizing the 404 page. // If you are customizing 404 // TODO update rootParams to make all params optional if `/app/not-found.tsx` is defined return currentParams; } else { return getRootParamsImpl(currentParams, // We stop looking for root params as soon as we hit the first layout // and it is not possible to use parallel route children above the root layout // so every parallelRoutes object that this function can visit will necessarily // have a single `children` prop and no others. parallelRoutes.children, getDynamicParamFromSegment); } } async function createBoundaryConventionElement({ ctx, conventionName, Component, styles, tree }) { const { componentMod: { createElement, Fragment } } = ctx; const isSegmentViewEnabled = !!process.env.__NEXT_DEV_SERVER; const dir = (process.env.NEXT_RUNTIME === 'edge' ? process.env.__NEXT_EDGE_PROJECT_DIR : ctx.renderOpts.dir) || ''; const { SegmentViewNode } = ctx.componentMod; const element = Component ? createElement(Fragment, null, createElement(Component, null), styles) : undefined; const pagePath = getConventionPathByType(tree, dir, conventionName); const wrappedElement = isSegmentViewEnabled && element ? createElement(SegmentViewNode, { key: cacheNodeKey + '-' + conventionName, type: conventionName, // TODO: Discovered when moving to `createElement`. // `SegmentViewNode` doesn't support undefined `pagePath` pagePath: pagePath }, element) : element; return [ wrappedElement, pagePath ]; } function createTransportNode(ctx, segment, prefetchHints, rsc, children, loading, isPossiblyPartialResponse, varyParamsAccumulator) { const createElement = ctx.componentMod.createElement; if (loading !== null) { // If a loading.tsx boundary is present, wrap the component data in an // additional context provider to pass the loading data to the next // set of children. // NOTE: The reason this is a separate wrapper from LayoutRouter is because // not all segments render a LayoutRouter component, e.g. the root segment. const LoadingBoundaryProvider = ctx.componentMod.LoadingBoundaryProvider; rsc = createElement(LoadingBoundaryProvider, { loading: loading, children: rsc }); } const node = { s: segment }; if (prefetchHints !== 0) { node.h = prefetchHints; } node.d = { r: rsc, p: isPossiblyPartialResponse, // The accumulator is itself the AsyncIterable<string> that Flight // serializes into the segment's render output. v: varyParamsAccumulator }; if (children !== undefined) { node.c = children; } return node; } //# sourceMappingURL=create-component-tree.js.map