From a428fa18234670e22ad081fc1f25365be68b900b Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Wed, 25 Sep 2024 10:52:12 -0700 Subject: [PATCH] refactor: added more strict app segment config parsing --- .../next/src/build/app-segment-config.test.ts | 110 +++++ packages/next/src/build/app-segment-config.ts | 87 ++++ packages/next/src/build/index.ts | 5 +- packages/next/src/build/utils.ts | 409 ++++++++---------- .../next/src/lib/metadata/resolve-metadata.ts | 5 +- .../app-render/create-component-tree.tsx | 2 +- .../src/server/dev/static-paths-worker.ts | 16 +- .../next/src/server/lib/app-dir-module.ts | 14 +- .../server/route-modules/app-route/module.ts | 7 +- 9 files changed, 408 insertions(+), 247 deletions(-) create mode 100644 packages/next/src/build/app-segment-config.test.ts create mode 100644 packages/next/src/build/app-segment-config.ts diff --git a/packages/next/src/build/app-segment-config.test.ts b/packages/next/src/build/app-segment-config.test.ts new file mode 100644 index 00000000000000..2fee918effb07c --- /dev/null +++ b/packages/next/src/build/app-segment-config.test.ts @@ -0,0 +1,110 @@ +import { AppSegmentConfigSchema } from './app-segment-config' + +describe('AppConfigSchema', () => { + it('should only support zero, a positive number or false for revalidate', () => { + const valid = [0, 1, 100, false] + + for (const value of valid) { + expect( + AppSegmentConfigSchema.safeParse({ revalidate: value }).success + ).toBe(true) + } + + const invalid = [-1, -100, true] + + for (const value of invalid) { + expect( + AppSegmentConfigSchema.safeParse({ revalidate: value }).success + ).toBe(false) + } + }) + + it('should support an empty config', () => { + expect(AppSegmentConfigSchema.safeParse({}).success).toBe(true) + }) + + it('should support a boolean for dynamicParams', () => { + expect( + AppSegmentConfigSchema.safeParse({ dynamicParams: true }).success + ).toBe(true) + expect( + AppSegmentConfigSchema.safeParse({ dynamicParams: false }).success + ).toBe(true) + expect( + AppSegmentConfigSchema.safeParse({ dynamicParams: 'foo' }).success + ).toBe(false) + }) + + it('should support "auto" | "force-dynamic" | "error" | "force-static" for dynamic', () => { + expect(AppSegmentConfigSchema.safeParse({ dynamic: 'auto' }).success).toBe( + true + ) + expect( + AppSegmentConfigSchema.safeParse({ dynamic: 'force-dynamic' }).success + ).toBe(true) + expect(AppSegmentConfigSchema.safeParse({ dynamic: 'error' }).success).toBe( + true + ) + expect( + AppSegmentConfigSchema.safeParse({ dynamic: 'force-static' }).success + ).toBe(true) + }) + + it('should support "edge" | "nodejs" for runtime', () => { + expect(AppSegmentConfigSchema.safeParse({ runtime: 'edge' }).success).toBe( + true + ) + expect( + AppSegmentConfigSchema.safeParse({ runtime: 'nodejs' }).success + ).toBe(true) + expect(AppSegmentConfigSchema.safeParse({ runtime: 'foo' }).success).toBe( + false + ) + }) + + it('should support a positive number or zero for maxDuration', () => { + expect(AppSegmentConfigSchema.safeParse({ maxDuration: 0 }).success).toBe( + true + ) + expect(AppSegmentConfigSchema.safeParse({ maxDuration: 100 }).success).toBe( + true + ) + expect(AppSegmentConfigSchema.safeParse({ maxDuration: -1 }).success).toBe( + false + ) + }) + + it('should support "force-cache" | "only-cache" for fetchCache', () => { + expect( + AppSegmentConfigSchema.safeParse({ fetchCache: 'force-cache' }).success + ).toBe(true) + expect( + AppSegmentConfigSchema.safeParse({ fetchCache: 'only-cache' }).success + ).toBe(true) + expect( + AppSegmentConfigSchema.safeParse({ fetchCache: 'foo' }).success + ).toBe(false) + }) + + it('should support a string or an array of strings for preferredRegion', () => { + expect( + AppSegmentConfigSchema.safeParse({ preferredRegion: 'foo' }).success + ).toBe(true) + expect( + AppSegmentConfigSchema.safeParse({ preferredRegion: ['foo', 'bar'] }) + .success + ).toBe(true) + }) + + it('should support a boolean for experimental_ppr', () => { + expect( + AppSegmentConfigSchema.safeParse({ experimental_ppr: true }).success + ).toBe(true) + expect( + AppSegmentConfigSchema.safeParse({ experimental_ppr: false }).success + ).toBe(true) + expect( + AppSegmentConfigSchema.safeParse({ experimental_ppr: 'foo' }).success + ).toBe(false) + }) +}) diff --git a/packages/next/src/build/app-segment-config.ts b/packages/next/src/build/app-segment-config.ts new file mode 100644 index 00000000000000..7bcaf1bd7e498e --- /dev/null +++ b/packages/next/src/build/app-segment-config.ts @@ -0,0 +1,87 @@ +import { z } from 'next/dist/compiled/zod' + +/** + * The schema for the dynamic behavior of a page. + * + * @internal + */ +export const AppSegmentConfigDynamicSchema = z.enum([ + 'auto', + 'error', + 'force-static', + 'force-dynamic', +]) + +/** + * The dynamic behavior of the page. + * + * @internal + */ +export type AppSegmentConfigDynamic = z.infer< + typeof AppSegmentConfigDynamicSchema +> + +/** + * The schema for configuration for a page. + * + * @internal + */ +export const AppSegmentConfigSchema = z.object({ + /** + * The number of seconds to revalidate the page or false to disable revalidation. + */ + revalidate: z + .union([z.number().int().nonnegative(), z.literal(false)]) + .optional(), + + /** + * Whether the page supports dynamic parameters. + */ + dynamicParams: z.boolean().optional(), + + /** + * The dynamic behavior of the page. + */ + dynamic: AppSegmentConfigDynamicSchema.optional(), + + /** + * The caching behavior of the page. + */ + fetchCache: z.enum(['force-cache', 'only-cache']).optional(), + + /** + * The preferred region for the page. + */ + preferredRegion: z.union([z.string(), z.array(z.string())]).optional(), + + /** + * Whether the page supports partial prerendering. When true, the page will be + * served using partial prerendering. This setting will only take affect if + * it's enabled via the `experimental.ppr = "incremental"` option. + */ + experimental_ppr: z.boolean().optional(), + + /** + * The runtime to use for the page. + */ + runtime: z.enum(['edge', 'nodejs']).optional(), + + /** + * The maximum duration for the page in seconds. + */ + maxDuration: z.number().int().nonnegative().optional(), +}) + +/** + * The configuration for a page. + * + * @internal + */ +export type AppSegmentConfig = z.infer + +/** + * The keys of the configuration for a page. + * + * @internal + */ +export const AppSegmentConfigSchemaKeys = AppSegmentConfigSchema.keyof().options diff --git a/packages/next/src/build/index.ts b/packages/next/src/build/index.ts index f954d5eb9d23f4..c358f06d0f6a91 100644 --- a/packages/next/src/build/index.ts +++ b/packages/next/src/build/index.ts @@ -130,7 +130,8 @@ import { collectMeta, // getSupportedBrowsers, } from './utils' -import type { PageInfo, PageInfos, AppConfig, PrerenderedRoute } from './utils' +import type { PageInfo, PageInfos, PrerenderedRoute } from './utils' +import type { AppSegmentConfig } from './app-segment-config' import { writeBuildId } from './write-build-id' import { normalizeLocalePath } from '../shared/lib/i18n/normalize-locale-path' import isError from '../lib/is-error' @@ -1815,7 +1816,7 @@ export default async function build( const staticPaths = new Map() const appNormalizedPaths = new Map() const fallbackModes = new Map() - const appDefaultConfigs = new Map() + const appDefaultConfigs = new Map() const pageInfos: PageInfos = new Map() let pagesManifest = await readManifest(pagesManifestPath) const buildManifest = await readManifest(buildManifestPath) diff --git a/packages/next/src/build/utils.ts b/packages/next/src/build/utils.ts index c772a0bcbc3bdb..cbd7d84cddc572 100644 --- a/packages/next/src/build/utils.ts +++ b/packages/next/src/build/utils.ts @@ -22,7 +22,10 @@ import type { import type { WebpackLayerName } from '../lib/constants' import type { AppPageModule } from '../server/route-modules/app-page/module' import type { RouteModule } from '../server/route-modules/route-module' -import type { LoaderTree } from '../server/lib/app-dir-module' +import { + getLayoutOrPageModule, + type LoaderTree, +} from '../server/lib/app-dir-module' import type { NextComponentType } from '../shared/lib/utils' import '../server/require-hook' @@ -97,6 +100,11 @@ import { } from '../lib/fallback' import { getParamKeys } from '../server/request/fallback-params' import type { OutgoingHttpHeaders } from 'http' +import { + AppSegmentConfigSchema, + type AppSegmentConfig, +} from './app-segment-config' +import { getSegmentParam } from '../server/app-render/get-segment-param' export type ROUTER_TYPE = 'pages' | 'app' @@ -1121,7 +1129,7 @@ export async function buildStaticPaths({ (!repeat && typeof paramValue !== 'string') ) { // If this is from app directory, and not all params were provided, - // then filter this out if the route is not PPR enabled. + // then filter this out. if (appDir && typeof paramValue === 'undefined') { builtPage = '' encodedBuiltPage = '' @@ -1200,73 +1208,15 @@ export async function buildStaticPaths({ } } -export type AppConfigDynamic = - | 'auto' - | 'error' - | 'force-static' - | 'force-dynamic' - -export type AppConfig = { - revalidate?: number | false - dynamicParams?: true | false - dynamic?: AppConfigDynamic - fetchCache?: 'force-cache' | 'only-cache' - preferredRegion?: string - - /** - * When true, the page will be served using partial prerendering. - * This setting will only take affect if it's enabled via - * the `experimental.ppr = "incremental"` option. - */ - experimental_ppr?: boolean -} - type GenerateStaticParams = (options: { params?: Params }) => Promise -type GenerateParamsResult = { - config?: AppConfig +export type AppSegment = { + name?: string + param?: string + filePath?: string + config: AppSegmentConfig | undefined isDynamicSegment?: boolean - segmentPath: string generateStaticParams?: GenerateStaticParams - isLayout?: boolean -} - -export type GenerateParamsResults = GenerateParamsResult[] - -const collectAppConfig = ( - mod: Partial | undefined -): AppConfig | undefined => { - let hasConfig = false - const config: AppConfig = {} - - if (typeof mod?.revalidate !== 'undefined') { - config.revalidate = mod.revalidate - hasConfig = true - } - if (typeof mod?.dynamicParams !== 'undefined') { - config.dynamicParams = mod.dynamicParams - hasConfig = true - } - if (typeof mod?.dynamic !== 'undefined') { - config.dynamic = mod.dynamic - hasConfig = true - } - if (typeof mod?.fetchCache !== 'undefined') { - config.fetchCache = mod.fetchCache - hasConfig = true - } - if (typeof mod?.preferredRegion !== 'undefined') { - config.preferredRegion = mod.preferredRegion - hasConfig = true - } - if (typeof mod?.experimental_ppr !== 'undefined') { - config.experimental_ppr = mod.experimental_ppr - hasConfig = true - } - - if (!hasConfig) return undefined - - return config } /** @@ -1275,69 +1225,54 @@ const collectAppConfig = ( * @param tree the loader tree * @returns the generate parameters for each segment */ -export async function collectGenerateParams(tree: LoaderTree) { - const generateParams: GenerateParamsResults = [] - const parentSegments: string[] = [] - - let currentLoaderTree = tree - while (currentLoaderTree) { - const [ - // TODO: check if this is ever undefined - page = '', - parallelRoutes, - components, - ] = currentLoaderTree - - // If the segment doesn't have any components, then skip it. - if (!components) continue - - const isLayout = !!components.layout - const mod = await (isLayout - ? components.layout?.[0]?.() - : components.page?.[0]?.()) - - if (page) { - parentSegments.push(page) +export async function collectSegments(tree: LoaderTree) { + const segments: AppSegment[] = [] + + let current = tree + while (current) { + const [name, parallelRoutes] = current + const { mod, filePath } = await getLayoutOrPageModule(current) + + const isClientComponent: boolean = mod && isClientReference(mod) + const isDynamicSegment = /^\[.*\]$/.test(name) + const param = isDynamicSegment ? getSegmentParam(name)?.param : undefined + + const segment: AppSegment = { + name, + param, + filePath, + config: undefined, + isDynamicSegment, } - const config = mod ? collectAppConfig(mod) : undefined - const isClientComponent = isClientReference(mod) - - const isDynamicSegment = /^\[.+\]$/.test(page) - - const { generateStaticParams } = mod || {} - - if (isDynamicSegment && isClientComponent && generateStaticParams) { - throw new Error( - `Page "${page}" cannot export "generateStaticParams()" because it is a client component` - ) - } + if (!isClientComponent && mod && typeof mod === 'object') { + const { generateStaticParams } = mod + if (typeof generateStaticParams === 'function') { + segment.generateStaticParams = generateStaticParams + } - const segmentPath = `/${parentSegments.join('/')}${ - page && parentSegments.length > 0 ? '/' : '' - }${page}` + const config = AppSegmentConfigSchema.safeParse(mod) - const result: GenerateParamsResult = { - isLayout, - isDynamicSegment, - segmentPath, - config, - generateStaticParams: !isClientComponent - ? generateStaticParams - : undefined, + // If parsing was successful and the config has some keys, then use it. + if (config.success && Object.keys(config.data).length > 0) { + segment.config = config.data + } } - // If the configuration contributes to the static generation, then add it - // to the list. - if (result.config || result.generateStaticParams || isDynamicSegment) { - generateParams.push(result) - } + // Add the result to the generate params list. + segments.push(segment) // Use this route's parallel route children as the next segment. - currentLoaderTree = parallelRoutes.children + current = parallelRoutes.children } - return generateParams + // If the configuration contributes to the static generation, then add it + // to the list. + return segments.filter((result) => { + return ( + result.config || result.generateStaticParams || result.isDynamicSegment + ) + }) } export type PartialStaticPathsResult = { @@ -1350,7 +1285,7 @@ export async function buildAppStaticPaths({ distDir, dynamicIO, configFileName, - generateParams, + segments, isrFlushToDisk, cacheHandler, requestHeaders, @@ -1365,7 +1300,7 @@ export async function buildAppStaticPaths({ page: string dynamicIO: boolean configFileName: string - generateParams: GenerateParamsResults + segments: AppSegment[] distDir: string isrFlushToDisk?: boolean fetchCacheKeyPrefix?: string @@ -1409,7 +1344,20 @@ export async function buildAppStaticPaths({ minimalMode: ciEnvironment.hasNextSupport, }) - return withStaticGenerationStore( + const paramKeys = new Set() + + const staticParamKeys = new Set() + for (const segment of segments) { + if (segment.param) { + paramKeys.add(segment.param) + + if (segment.config?.dynamicParams === false) { + staticParamKeys.add(segment.param) + } + } + } + + const routeParams = await withStaticGenerationStore( ComponentMod.staticGenerationAsyncStorage, { page, @@ -1426,134 +1374,148 @@ export async function buildAppStaticPaths({ }, }, }, - async (): Promise => { - let hadAllParamsGenerated = false - - const buildParams = async ( - paramsItems: Params[] = [{}], + async (store) => { + async function builtRouteParams( + parentsParams: Params[] = [{}], idx = 0 - ): Promise => { - const current = generateParams[idx] + ): Promise { + // If we don't have any more to process, then we're done. + if (idx === segments.length) return parentsParams - if (idx === generateParams.length) { - return paramsItems - } + const current = segments[idx] if ( typeof current.generateStaticParams !== 'function' && - idx < generateParams.length + idx < segments.length ) { - if (current.isDynamicSegment) { - // This dynamic level has no generateStaticParams so we change - // this flag to false, but it could be covered by a later - // generateStaticParams so it could be set back to true. - hadAllParamsGenerated = false - } - return buildParams(paramsItems, idx + 1) + return builtRouteParams(parentsParams, idx + 1) } - hadAllParamsGenerated = true - const newParams: Params[] = [] + const params: Params[] = [] if (current.generateStaticParams) { - const store = ComponentMod.staticGenerationAsyncStorage.getStore() - - if (store) { - if (typeof current?.config?.fetchCache !== 'undefined') { - store.fetchCache = current.config.fetchCache - } - if (typeof current?.config?.revalidate !== 'undefined') { - store.revalidate = current.config.revalidate - } - if (current?.config?.dynamic === 'force-dynamic') { - store.forceDynamic = true - } + if (typeof current.config?.fetchCache !== 'undefined') { + store.fetchCache = current.config.fetchCache + } + if (typeof current.config?.revalidate !== 'undefined') { + store.revalidate = current.config.revalidate + } + if (current.config?.dynamic === 'force-dynamic') { + store.forceDynamic = true } - for (const params of paramsItems) { + for (const parentParams of parentsParams) { const result = await current.generateStaticParams({ - params, + params: parentParams, }) - // TODO: validate the result is valid here or wait for buildStaticPaths to validate? for (const item of result) { - newParams.push({ ...params, ...item }) + params.push({ ...parentParams, ...item }) } } } - if (idx < generateParams.length) { - return buildParams(newParams, idx + 1) + if (idx < segments.length) { + return builtRouteParams(params, idx + 1) } - return newParams + return params } - const builtParams = await buildParams() + return builtRouteParams() + } + ) + + if ( + segments.some((generate) => generate.config?.dynamicParams === true) && + nextConfigOutput === 'export' + ) { + throw new Error( + '"dynamicParams: true" cannot be used with "output: export". See more info here: https://nextjs.org/docs/app/building-your-application/deploying/static-exports' + ) + } + + for (const segment of segments) { + // Check to see if there are any missing params for segments that have + // dynamicParams set to false. + if ( + segment.param && + segment.isDynamicSegment && + segment.config?.dynamicParams === false + ) { + for (const params of routeParams) { + if (segment.param in params) continue + + const relative = segment.filePath + ? path.relative(dir, segment.filePath) + : undefined - if ( - generateParams.some( - (generate) => generate.config?.dynamicParams === true - ) && - nextConfigOutput === 'export' - ) { throw new Error( - '"dynamicParams: true" cannot be used with "output: export". See more info here: https://nextjs.org/docs/app/building-your-application/deploying/static-exports' + `Segment "${relative}" exports "dynamicParams: false" but the param "${segment.param}" is missing from the generated route params.` ) } + } + } - // TODO: dynamic params should be allowed to be granular per segment but - // we need additional information stored/leveraged in the prerender - // manifest to allow this behavior. - const dynamicParams = generateParams.every( - (param) => param.config?.dynamicParams !== false - ) + // Determine if all the segments have had their parameters provided. + const hadAllParamsGenerated = routeParams.every((params) => { + for (const key of paramKeys) { + if (key in params) continue + return false + } + return true + }) - const isProduction = process.env.NODE_ENV === 'production' + // TODO: dynamic params should be allowed to be granular per segment but + // we need additional information stored/leveraged in the prerender + // manifest to allow this behavior. + const dynamicParams = segments.every( + (param) => param.config?.dynamicParams !== false + ) - const supportsStaticGeneration = hadAllParamsGenerated || isProduction + const isProduction = process.env.NODE_ENV === 'production' - const supportsPPRFallbacks = isRoutePPREnabled && isAppPPRFallbacksEnabled + const supportsRoutePregeneration = hadAllParamsGenerated || isProduction - const fallbackMode = dynamicParams - ? supportsStaticGeneration - ? supportsPPRFallbacks - ? FallbackMode.PRERENDER - : FallbackMode.BLOCKING_STATIC_RENDER - : undefined - : FallbackMode.NOT_FOUND + const supportsPPRFallbacks = isRoutePPREnabled && isAppPPRFallbacksEnabled - let result: PartialStaticPathsResult = { - fallbackMode, - prerenderedRoutes: undefined, - } + const fallbackMode = dynamicParams + ? supportsRoutePregeneration + ? supportsPPRFallbacks + ? FallbackMode.PRERENDER + : FallbackMode.BLOCKING_STATIC_RENDER + : undefined + : FallbackMode.NOT_FOUND - if (hadAllParamsGenerated && fallbackMode) { - result = await buildStaticPaths({ - staticPathsResult: { - fallback: fallbackModeToStaticPathsResult(fallbackMode), - paths: builtParams.map((params) => ({ params })), - }, - page, - configFileName, - appDir: true, - }) - } + let result: PartialStaticPathsResult = { + fallbackMode, + prerenderedRoutes: undefined, + } - // If the fallback mode is a prerender, we want to include the dynamic - // route in the prerendered routes too. - if (isRoutePPREnabled && isAppPPRFallbacksEnabled) { - result.prerenderedRoutes ??= [] - result.prerenderedRoutes.unshift({ - path: page, - encoded: page, - fallbackRouteParams: getParamKeys(page), - }) - } + if (hadAllParamsGenerated && fallbackMode) { + result = await buildStaticPaths({ + staticPathsResult: { + fallback: fallbackModeToStaticPathsResult(fallbackMode), + paths: routeParams.map((params) => ({ params })), + }, + page, + configFileName, + appDir: true, + }) + } - return result - } - ) + // If the fallback mode is a prerender, we want to include the dynamic + // route in the prerendered routes too. + if (isRoutePPREnabled && isAppPPRFallbacksEnabled) { + result.prerenderedRoutes ??= [] + result.prerenderedRoutes.unshift({ + path: page, + encoded: page, + fallbackRouteParams: getParamKeys(page), + }) + } + + return result } type PageIsStaticResult = { @@ -1568,7 +1530,7 @@ type PageIsStaticResult = { isNextImageImported?: boolean traceIncludes?: string[] traceExcludes?: string[] - appConfig?: AppConfig + appConfig?: AppSegmentConfig } export async function isPageStatic({ @@ -1627,7 +1589,7 @@ export async function isPageStatic({ let componentsResult: LoadComponentsReturnType let prerenderedRoutes: PrerenderedRoute[] | undefined let prerenderFallbackMode: FallbackMode | undefined - let appConfig: AppConfig = {} + let appConfig: AppSegmentConfig = {} let isClientComponent: boolean = false const pathIsEdgeRuntime = isEdgeRuntime(pageRuntime) @@ -1683,7 +1645,7 @@ export async function isPageStatic({ const { tree } = ComponentMod - const generateParams: GenerateParamsResults = + const segments: AppSegment[] = routeModule && isAppRouteRouteModule(routeModule) ? [ { @@ -1694,12 +1656,11 @@ export async function isPageStatic({ }, generateStaticParams: routeModule.userland.generateStaticParams, - segmentPath: page, }, ] - : await collectGenerateParams(tree) + : await collectSegments(tree) - appConfig = reduceAppConfig(generateParams) + appConfig = reduceAppConfig(segments) if (appConfig.dynamic === 'force-static' && pathIsEdgeRuntime) { Log.warn( @@ -1729,7 +1690,7 @@ export async function isPageStatic({ page, dynamicIO, configFileName, - generateParams, + segments, distDir, requestHeaders: {}, isrFlushToDisk, @@ -1833,7 +1794,7 @@ export async function isPageStatic({ } type ReducedAppConfig = Pick< - AppConfig, + AppSegmentConfig, | 'dynamic' | 'fetchCache' | 'preferredRegion' @@ -1848,21 +1809,17 @@ type ReducedAppConfig = Pick< * @param segments the generate param segments * @returns the reduced app config */ -export function reduceAppConfig( - segments: GenerateParamsResults -): ReducedAppConfig { +export function reduceAppConfig(segments: AppSegment[]): ReducedAppConfig { const config: ReducedAppConfig = {} for (const segment of segments) { - if (!segment.config) continue - const { dynamic, fetchCache, preferredRegion, revalidate, experimental_ppr, - } = segment.config + } = segment.config || {} // TODO: should conflicting configs here throw an error // e.g. if layout defines one region but page defines another diff --git a/packages/next/src/lib/metadata/resolve-metadata.ts b/packages/next/src/lib/metadata/resolve-metadata.ts index 508643c49c98c6..0a8c490bd1181d 100644 --- a/packages/next/src/lib/metadata/resolve-metadata.ts +++ b/packages/next/src/lib/metadata/resolve-metadata.ts @@ -441,7 +441,10 @@ export async function collectMetadata({ mod = await getComponentTypeModule(tree, 'layout') modType = errorConvention } else { - ;[mod, modType] = await getLayoutOrPageModule(tree) + const { mod: layoutOrPageMod, modType: layoutOrPageModType } = + await getLayoutOrPageModule(tree) + mod = layoutOrPageMod + modType = layoutOrPageModType } if (modType) { diff --git a/packages/next/src/server/app-render/create-component-tree.tsx b/packages/next/src/server/app-render/create-component-tree.tsx index dfb0c2d39cae52..20bb19cae0bf6f 100644 --- a/packages/next/src/server/app-render/create-component-tree.tsx +++ b/packages/next/src/server/app-render/create-component-tree.tsx @@ -159,7 +159,7 @@ async function createComponentTreeInternal({ const isLayout = typeof layout !== 'undefined' const isPage = typeof page !== 'undefined' - const [layoutOrPageMod] = await getTracer().trace( + const { mod: layoutOrPageMod } = await getTracer().trace( NextNodeServerSpan.getLayoutOrPageModule, { hideSpan: !(isLayout || isPage), diff --git a/packages/next/src/server/dev/static-paths-worker.ts b/packages/next/src/server/dev/static-paths-worker.ts index 5f80c98dbcf025..f69e8aa9dd4c6a 100644 --- a/packages/next/src/server/dev/static-paths-worker.ts +++ b/packages/next/src/server/dev/static-paths-worker.ts @@ -6,13 +6,10 @@ import '../node-environment' import { buildAppStaticPaths, buildStaticPaths, - collectGenerateParams, + collectSegments, reduceAppConfig, } from '../../build/utils' -import type { - GenerateParamsResults, - PartialStaticPathsResult, -} from '../../build/utils' +import type { AppSegment, PartialStaticPathsResult } from '../../build/utils' import { loadComponents } from '../load-components' import { setHttpClientAndAgentOptions } from '../setup-http-agent-env' import type { IncrementalCache } from '../lib/incremental-cache' @@ -94,7 +91,7 @@ export async function loadStaticPaths({ if (isAppPath) { const { routeModule } = components - const generateParams: GenerateParamsResults = + const segments: AppSegment[] = routeModule && isAppRouteRouteModule(routeModule) ? [ { @@ -104,20 +101,19 @@ export async function loadStaticPaths({ dynamicParams: routeModule.userland.dynamicParams, }, generateStaticParams: routeModule.userland.generateStaticParams, - segmentPath: pathname, }, ] - : await collectGenerateParams(components.ComponentMod.tree) + : await collectSegments(components.ComponentMod.tree) const isRoutePPREnabled = isAppPageRouteModule(routeModule) && - checkIsRoutePPREnabled(config.pprConfig, reduceAppConfig(generateParams)) + checkIsRoutePPREnabled(config.pprConfig, reduceAppConfig(segments)) return await buildAppStaticPaths({ dir, page: pathname, dynamicIO: config.dynamicIO, - generateParams, + segments, configFileName: config.configFileName, distDir, requestHeaders, diff --git a/packages/next/src/server/lib/app-dir-module.ts b/packages/next/src/server/lib/app-dir-module.ts index 9de970147a2a96..3e2438ed074a55 100644 --- a/packages/next/src/server/lib/app-dir-module.ts +++ b/packages/next/src/server/lib/app-dir-module.ts @@ -17,21 +17,25 @@ export async function getLayoutOrPageModule(loaderTree: LoaderTree) { const isDefaultPage = typeof defaultPage !== 'undefined' && loaderTree[0] === DEFAULT_SEGMENT_KEY - let value = undefined + let mod = undefined let modType: 'layout' | 'page' | undefined = undefined + let filePath = undefined if (isLayout) { - value = await layout[0]() + mod = await layout[0]() modType = 'layout' + filePath = layout[1] } else if (isPage) { - value = await page[0]() + mod = await page[0]() modType = 'page' + filePath = page[1] } else if (isDefaultPage) { - value = await defaultPage[0]() + mod = await defaultPage[0]() modType = 'page' + filePath = defaultPage[1] } - return [value, modType] as const + return { mod, modType, filePath } } export async function getComponentTypeModule( diff --git a/packages/next/src/server/route-modules/app-route/module.ts b/packages/next/src/server/route-modules/app-route/module.ts index 74a70bc354895e..e56277366554b7 100644 --- a/packages/next/src/server/route-modules/app-route/module.ts +++ b/packages/next/src/server/route-modules/app-route/module.ts @@ -1,6 +1,6 @@ import type { NextConfig } from '../../config-shared' import type { AppRouteRouteDefinition } from '../../route-definitions/app-route-route-definition' -import type { AppConfig } from '../../../build/utils' +import type { AppSegmentConfig } from '../../../build/app-segment-config' import type { NextRequest } from '../../web/spec-extension/request' import type { PrerenderManifest } from '../../../build' import type { NextURL } from '../../web/next-url' @@ -123,7 +123,10 @@ export type AppRouteHandlers = { * routes. This contains all the user generated code. */ export type AppRouteUserlandModule = AppRouteHandlers & - Pick & { + Pick< + AppSegmentConfig, + 'dynamic' | 'revalidate' | 'dynamicParams' | 'fetchCache' + > & { // TODO: (wyattjoh) create a type for this generateStaticParams?: any }