| | import type { ComponentType } from 'react' |
| | import type { |
| | CacheNodeSeedData, |
| | LoadingModuleData, |
| | } from '../../shared/lib/app-router-types' |
| | import type { PreloadCallbacks } from './types' |
| | import { |
| | isClientReference, |
| | isUseCacheFunction, |
| | } from '../../lib/client-and-server-references' |
| | import { getLayoutOrPageModule } from '../lib/app-dir-module' |
| | import type { LoaderTree } from '../lib/app-dir-module' |
| | import { interopDefault } from './interop-default' |
| | import { parseLoaderTree } from '../../shared/lib/router/utils/parse-loader-tree' |
| | import type { AppRenderContext, GetDynamicParamFromSegment } from './app-render' |
| | 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 type { Params } from '../request/params' |
| | import { workUnitAsyncStorage } from './work-unit-async-storage.external' |
| | import type { |
| | UseCacheLayoutProps, |
| | UseCachePageProps, |
| | } from '../use-cache/use-cache-wrapper' |
| | import { DEFAULT_SEGMENT_KEY } from '../../shared/lib/segment' |
| | import { |
| | BOUNDARY_PREFIX, |
| | BOUNDARY_SUFFIX, |
| | BUILTIN_PREFIX, |
| | getConventionPathByType, |
| | isNextjsBuiltinFilePath, |
| | } from './segment-explorer-path' |
| | import type { AppSegmentConfig } from '../../build/segment-config/app/app-segment-config' |
| |
|
| | |
| | |
| | |
| | export function createComponentTree(props: { |
| | loaderTree: LoaderTree |
| | parentParams: Params |
| | rootLayoutIncluded: boolean |
| | injectedCSS: Set<string> |
| | injectedJS: Set<string> |
| | injectedFontPreloadTags: Set<string> |
| | ctx: AppRenderContext |
| | missingSlots?: Set<string> |
| | preloadCallbacks: PreloadCallbacks |
| | authInterrupts: boolean |
| | MetadataOutlet: ComponentType |
| | }): Promise<CacheNodeSeedData> { |
| | return getTracer().trace( |
| | NextNodeServerSpan.createComponentTree, |
| | { |
| | spanName: 'build component tree', |
| | }, |
| | () => createComponentTreeInternal(props, true) |
| | ) |
| | } |
| |
|
| | function errorMissingDefaultExport( |
| | pagePath: string, |
| | convention: string |
| | ): never { |
| | 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, |
| | rootLayoutIncluded, |
| | injectedCSS, |
| | injectedJS, |
| | injectedFontPreloadTags, |
| | ctx, |
| | missingSlots, |
| | preloadCallbacks, |
| | authInterrupts, |
| | MetadataOutlet, |
| | }: { |
| | loaderTree: LoaderTree |
| | parentParams: Params |
| | rootLayoutIncluded: boolean |
| | injectedCSS: Set<string> |
| | injectedJS: Set<string> |
| | injectedFontPreloadTags: Set<string> |
| | ctx: AppRenderContext |
| | missingSlots?: Set<string> |
| | preloadCallbacks: PreloadCallbacks |
| | authInterrupts: boolean |
| | MetadataOutlet: ComponentType | null |
| | }, |
| | isRoot: boolean |
| | ): Promise<CacheNodeSeedData> { |
| | const { |
| | renderOpts: { nextConfigOutput, experimental, cacheComponents }, |
| | workStore, |
| | componentMod: { |
| | createElement, |
| | Fragment, |
| | SegmentViewNode, |
| | HTTPAccessFallbackBoundary, |
| | LayoutRouter, |
| | RenderFromTemplateContext, |
| | ClientPageRoot, |
| | ClientSegmentRoot, |
| | createServerSearchParamsForServerPage, |
| | createPrerenderSearchParamsForClientPage, |
| | createServerParamsForServerSegment, |
| | createPrerenderParamsForClientSegment, |
| | serverHooks: { DynamicServerError }, |
| | Postpone, |
| | }, |
| | pagePath, |
| | getDynamicParamFromSegment, |
| | isPrefetch, |
| | query, |
| | } = ctx |
| |
|
| | const { page, conventionPath, segment, modules, parallelRoutes } = |
| | parseLoaderTree(tree) |
| |
|
| | 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) |
| | ) |
| |
|
| | |
| | |
| | |
| | const rootLayoutAtThisLevel = isLayout && !rootLayoutIncluded |
| | |
| | |
| | |
| | const rootLayoutIncludedAtThisLevelOrAbove = |
| | rootLayoutIncluded || rootLayoutAtThisLevel |
| |
|
| | const [NotFound, notFoundStyles] = notFound |
| | ? await createComponentStylesAndScripts({ |
| | ctx, |
| | filePath: notFound[1], |
| | getComponent: notFound[0], |
| | injectedCSS: injectedCSSWithCurrentLayout, |
| | injectedJS: injectedJSWithCurrentLayout, |
| | }) |
| | : [] |
| |
|
| | const prefetchConfig = layoutOrPageMod |
| | ? (layoutOrPageMod as AppSegmentConfig).unstable_prefetch |
| | : undefined |
| | |
| | const hasRuntimePrefetch = prefetchConfig?.mode === 'runtime' |
| |
|
| | 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?.dynamic |
| |
|
| | if (nextConfigOutput === 'export') { |
| | if (!dynamic || dynamic === 'auto') { |
| | dynamic = 'error' |
| | } else if (dynamic === 'force-dynamic') { |
| | |
| | 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') { |
| | |
| | |
| | |
| | if (dynamic === 'error') { |
| | workStore.dynamicShouldError = true |
| | } else if (dynamic === 'force-dynamic') { |
| | workStore.forceDynamic = true |
| |
|
| | |
| | if (workStore.isStaticGeneration && !experimental.isRoutePPREnabled) { |
| | |
| | |
| | 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?.fetchCache === 'string') { |
| | workStore.fetchCache = layoutOrPageMod?.fetchCache |
| | } |
| |
|
| | if (typeof layoutOrPageMod?.revalidate !== 'undefined') { |
| | validateRevalidate(layoutOrPageMod?.revalidate, workStore.route) |
| | } |
| |
|
| | if (typeof layoutOrPageMod?.revalidate === 'number') { |
| | const defaultRevalidate = layoutOrPageMod.revalidate as number |
| |
|
| | const workUnitStore = workUnitAsyncStorage.getStore() |
| |
|
| | if (workUnitStore) { |
| | switch (workUnitStore.type) { |
| | case 'prerender': |
| | case 'prerender-runtime': |
| | case 'prerender-legacy': |
| | case 'prerender-ppr': |
| | if (workUnitStore.revalidate > defaultRevalidate) { |
| | workUnitStore.revalidate = defaultRevalidate |
| | } |
| | break |
| | case 'request': |
| | |
| | break |
| | |
| | case 'cache': |
| | case 'private-cache': |
| | case 'prerender-client': |
| | case 'unstable-cache': |
| | break |
| | default: |
| | workUnitStore satisfies never |
| | } |
| | } |
| |
|
| | if ( |
| | !workStore.forceStatic && |
| | workStore.isStaticGeneration && |
| | defaultRevalidate === 0 && |
| | |
| | |
| | !experimental.isRoutePPREnabled |
| | ) { |
| | const dynamicUsageDescription = `revalidate: 0 configured ${segment}` |
| | workStore.dynamicUsageDescription = dynamicUsageDescription |
| |
|
| | throw new DynamicServerError(dynamicUsageDescription) |
| | } |
| | } |
| |
|
| | const isStaticGeneration = workStore.isStaticGeneration |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | const isPossiblyPartialResponse = |
| | isStaticGeneration && experimental.isRoutePPREnabled === true |
| |
|
| | const LayoutOrPage: ComponentType<any> | undefined = layoutOrPageMod |
| | ? interopDefault(layoutOrPageMod) |
| | : undefined |
| |
|
| | |
| | |
| | |
| | let MaybeComponent = LayoutOrPage |
| |
|
| | if (process.env.NODE_ENV === 'development' || isStaticGeneration) { |
| | const { isValidElementType } = |
| | require('next/dist/compiled/react-is') as typeof import('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') |
| | } |
| | } |
| |
|
| | |
| | const segmentParam = getDynamicParamFromSegment(segment) |
| |
|
| | |
| | let currentParams: Params = parentParams |
| | if (segmentParam && segmentParam.value !== null) { |
| | currentParams = { |
| | ...parentParams, |
| | [segmentParam.param]: segmentParam.value, |
| | } |
| | } |
| |
|
| | |
| | const isSegmentViewEnabled = !!ctx.renderOpts.dev |
| | 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, |
| | }) |
| |
|
| | |
| | |
| | const parallelRouteMap = await Promise.all( |
| | Object.keys(parallelRoutes).map( |
| | async ( |
| | parallelRouteKey |
| | ): Promise<[string, React.ReactNode, CacheNodeSeedData | null]> => { |
| | const isChildrenRouteKey = parallelRouteKey === 'children' |
| | const parallelRoute = parallelRoutes[parallelRouteKey] |
| |
|
| | const notFoundComponent = isChildrenRouteKey |
| | ? notFoundElement |
| | : undefined |
| |
|
| | const forbiddenComponent = isChildrenRouteKey |
| | ? forbiddenElement |
| | : undefined |
| |
|
| | const unauthorizedComponent = isChildrenRouteKey |
| | ? unauthorizedElement |
| | : undefined |
| |
|
| | |
| | |
| | |
| | let childCacheNodeSeedData: CacheNodeSeedData | null = null |
| |
|
| | if ( |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | isPrefetch && |
| | (Loading || !hasLoadingComponentInTree(parallelRoute)) && |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | !experimental.isRoutePPREnabled |
| | ) { |
| | |
| | |
| | } else { |
| | |
| |
|
| | if (process.env.NODE_ENV === 'development' && missingSlots) { |
| | |
| | |
| | const parsedTree = parseLoaderTree(parallelRoute) |
| | if ( |
| | parsedTree.conventionPath?.endsWith(PARALLEL_ROUTE_DEFAULT_PATH) |
| | ) { |
| | missingSlots.add(parallelRouteKey) |
| | } |
| | } |
| |
|
| | const seedData = await createComponentTreeInternal( |
| | { |
| | loaderTree: parallelRoute, |
| | parentParams: currentParams, |
| | rootLayoutIncluded: rootLayoutIncludedAtThisLevelOrAbove, |
| | injectedCSS: injectedCSSWithCurrentLayout, |
| | injectedJS: injectedJSWithCurrentLayout, |
| | injectedFontPreloadTags: injectedFontPreloadTagsWithCurrentLayout, |
| | ctx, |
| | missingSlots, |
| | preloadCallbacks, |
| | authInterrupts, |
| | |
| | |
| | MetadataOutlet: isChildrenRouteKey ? MetadataOutlet : null, |
| | }, |
| | false |
| | ) |
| |
|
| | childCacheNodeSeedData = seedData |
| | } |
| |
|
| | 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 |
| |
|
| | |
| | |
| | |
| | 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, |
| | }), |
| | }), |
| | childCacheNodeSeedData, |
| | ] |
| | } |
| | ) |
| | ) |
| |
|
| | |
| | let parallelRouteProps: { [key: string]: React.ReactNode } = {} |
| | let parallelRouteCacheNodeSeedData: { |
| | [key: string]: CacheNodeSeedData | null |
| | } = {} |
| | for (const parallelRoute of parallelRouteMap) { |
| | const [parallelRouteKey, parallelRouteProp, flightData] = parallelRoute |
| | parallelRouteProps[parallelRouteKey] = parallelRouteProp |
| | parallelRouteCacheNodeSeedData[parallelRouteKey] = flightData |
| | } |
| |
|
| | 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: LoadingModuleData = loadingElement |
| | ? [loadingElement, loadingStyles, loadingScripts] |
| | : null |
| |
|
| | |
| | if (!MaybeComponent) { |
| | return [ |
| | createElement( |
| | Fragment, |
| | { |
| | key: cacheNodeKey, |
| | }, |
| | layerAssets, |
| | parallelRouteProps.children |
| | ), |
| | parallelRouteCacheNodeSeedData, |
| | loadingData, |
| | isPossiblyPartialResponse, |
| | hasRuntimePrefetch, |
| | ] |
| | } |
| |
|
| | const Component = MaybeComponent |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | if ( |
| | workStore.isStaticGeneration && |
| | workStore.forceDynamic && |
| | experimental.isRoutePPREnabled |
| | ) { |
| | return [ |
| | createElement( |
| | Fragment, |
| | { |
| | key: cacheNodeKey, |
| | }, |
| | createElement(Postpone, { |
| | reason: 'dynamic = "force-dynamic" was used', |
| | route: workStore.route, |
| | }), |
| | layerAssets |
| | ), |
| | parallelRouteCacheNodeSeedData, |
| | loadingData, |
| | true, |
| | hasRuntimePrefetch, |
| | ] |
| | } |
| |
|
| | const isClientComponent = isClientReference(layoutOrPageMod) |
| |
|
| | if ( |
| | process.env.NODE_ENV === 'development' && |
| | 'params' in parallelRouteProps |
| | ) { |
| | |
| | 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 |
| |
|
| | |
| | let pageElement: React.ReactNode |
| | if (isClientComponent) { |
| | if (cacheComponents) { |
| | |
| | pageElement = createElement(ClientPageRoot, { |
| | Component: PageComponent, |
| | serverProvidedParams: null, |
| | }) |
| | } else if (isStaticGeneration) { |
| | const promiseOfParams = |
| | createPrerenderParamsForClientSegment(currentParams) |
| | const promiseOfSearchParams = |
| | createPrerenderSearchParamsForClientPage(workStore) |
| | 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 { |
| | |
| | |
| | const params = createServerParamsForServerSegment( |
| | currentParams, |
| | workStore |
| | ) |
| |
|
| | |
| | |
| | |
| | let searchParams = createServerSearchParamsForServerPage(query, workStore) |
| |
|
| | if (isUseCacheFunction(PageComponent)) { |
| | const UseCachePageComponent: ComponentType<UseCachePageProps> = |
| | 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 [ |
| | createElement( |
| | Fragment, |
| | { |
| | key: cacheNodeKey, |
| | }, |
| | wrappedPageElement, |
| | layerAssets, |
| | MetadataOutlet ? createElement(MetadataOutlet, null) : null |
| | ), |
| | parallelRouteCacheNodeSeedData, |
| | loadingData, |
| | isPossiblyPartialResponse, |
| | hasRuntimePrefetch, |
| | ] |
| | } else { |
| | const SegmentComponent = Component |
| | const isRootLayoutWithChildrenSlotAndAtLeastOneMoreSlot = |
| | rootLayoutAtThisLevel && |
| | 'children' in parallelRoutes && |
| | Object.keys(parallelRoutes).length > 1 |
| |
|
| | let segmentNode: React.ReactNode |
| |
|
| | if (isClientComponent) { |
| | let clientSegment: React.ReactNode |
| | if (cacheComponents) { |
| | |
| | clientSegment = createElement(ClientSegmentRoot, { |
| | Component: SegmentComponent, |
| | slots: parallelRouteProps, |
| | serverProvidedParams: null, |
| | }) |
| | } else if (isStaticGeneration) { |
| | 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: React.ReactNode |
| | let forbiddenClientSegment: React.ReactNode |
| | let unauthorizedClientSegment: React.ReactNode |
| | |
| | |
| | |
| | |
| | |
| | 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, |
| | workStore |
| | ) |
| |
|
| | let serverSegment: React.ReactNode |
| |
|
| | if (isUseCacheFunction(SegmentComponent)) { |
| | const UseCacheLayoutComponent: ComponentType<UseCacheLayoutProps> = |
| | SegmentComponent |
| |
|
| | serverSegment = createElement( |
| | UseCacheLayoutComponent, |
| | { |
| | ...parallelRouteProps, |
| | params: params, |
| | $$isLayout: true, |
| | }, |
| | |
| | |
| | parallelRouteProps.children |
| | ) |
| | } else { |
| | serverSegment = createElement( |
| | SegmentComponent, |
| | { |
| | ...parallelRouteProps, |
| | params: params, |
| | }, |
| | |
| | |
| | parallelRouteProps.children |
| | ) |
| | } |
| |
|
| | if (isRootLayoutWithChildrenSlotAndAtLeastOneMoreSlot) { |
| | |
| | |
| | |
| | |
| | |
| | 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 |
| |
|
| | |
| | return [ |
| | wrappedSegmentNode, |
| | parallelRouteCacheNodeSeedData, |
| | loadingData, |
| | isPossiblyPartialResponse, |
| | hasRuntimePrefetch, |
| | ] |
| | } |
| | } |
| |
|
| | function createErrorBoundaryClientSegmentRoot({ |
| | ctx, |
| | ErrorBoundaryComponent, |
| | errorElement, |
| | ClientSegmentRoot, |
| | layerAssets, |
| | SegmentComponent, |
| | currentParams, |
| | }: { |
| | ctx: AppRenderContext |
| | ErrorBoundaryComponent: ComponentType<any> | undefined |
| | errorElement: React.ReactNode |
| | ClientSegmentRoot: ComponentType<any> |
| | layerAssets: React.ReactNode |
| | SegmentComponent: ComponentType<any> |
| | currentParams: Params |
| | }) { |
| | 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: LoaderTree, |
| | getDynamicParamFromSegment: GetDynamicParamFromSegment |
| | ): Params { |
| | return getRootParamsImpl({}, loaderTree, getDynamicParamFromSegment) |
| | } |
| |
|
| | function getRootParamsImpl( |
| | parentParams: Params, |
| | loaderTree: LoaderTree, |
| | getDynamicParamFromSegment: GetDynamicParamFromSegment |
| | ): Params { |
| | const { |
| | segment, |
| | modules: { layout }, |
| | parallelRoutes, |
| | } = parseLoaderTree(loaderTree) |
| |
|
| | const segmentParam = getDynamicParamFromSegment(segment) |
| |
|
| | let currentParams: Params = 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) { |
| | |
| | |
| | |
| | |
| | |
| | return currentParams |
| | } else { |
| | return getRootParamsImpl( |
| | currentParams, |
| | |
| | |
| | |
| | |
| | parallelRoutes.children, |
| | getDynamicParamFromSegment |
| | ) |
| | } |
| | } |
| |
|
| | async function createBoundaryConventionElement({ |
| | ctx, |
| | conventionName, |
| | Component, |
| | styles, |
| | tree, |
| | }: { |
| | ctx: AppRenderContext |
| | conventionName: |
| | | 'not-found' |
| | | 'error' |
| | | 'loading' |
| | | 'forbidden' |
| | | 'unauthorized' |
| | Component: ComponentType<any> | undefined |
| | styles: React.ReactNode | undefined |
| | tree: LoaderTree |
| | }) { |
| | const { |
| | componentMod: { createElement, Fragment }, |
| | } = ctx |
| | const isSegmentViewEnabled = !!ctx.renderOpts.dev |
| | 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, |
| | |
| | |
| | pagePath: pagePath!, |
| | }, |
| | element |
| | ) |
| | : element |
| |
|
| | return [wrappedElement, pagePath] as const |
| | } |
| |
|