diff --git a/packages/next/src/build/index.ts b/packages/next/src/build/index.ts index fe3148a1facef..0b3693c9c4b26 100644 --- a/packages/next/src/build/index.ts +++ b/packages/next/src/build/index.ts @@ -119,7 +119,12 @@ import { isAppBuiltinNotFoundPage, collectRoutesUsingEdgeRuntime, } from './utils' -import type { PageInfo, PageInfos, AppConfig } from './utils' +import type { + PageInfo, + PageInfos, + AppConfig, + GeneratedStaticPath, +} from './utils' import { writeBuildId } from './write-build-id' import { normalizeLocalePath } from '../shared/lib/i18n/normalize-locale-path' import isError from '../lib/is-error' @@ -1784,10 +1789,10 @@ export default async function build( const invalidPages = new Set() const hybridAmpPages = new Set() const serverPropsPages = new Set() - const additionalSsgPaths = new Map>() - const additionalSsgPathsEncoded = new Map>() - const appStaticPaths = new Map>() - const appStaticPathsEncoded = new Map>() + const paths = { + additional: new Map(), + static: new Map(), + } const appNormalizedPaths = new Map() const appDynamicParamPaths = new Set() const appDefaultConfigs = new Map() @@ -2116,23 +2121,20 @@ export default async function build( isSSG = true isStatic = true - appStaticPaths.set(originalAppPath, []) - appStaticPathsEncoded.set(originalAppPath, []) + paths.static.set(originalAppPath, []) } if ( - workerResult.encodedPrerenderRoutes && - workerResult.prerenderRoutes + workerResult.generatedRoutes && + workerResult.generatedRoutes.length > 0 ) { - appStaticPaths.set( + paths.static.set( originalAppPath, - workerResult.prerenderRoutes + workerResult.generatedRoutes ) - appStaticPathsEncoded.set( - originalAppPath, - workerResult.encodedPrerenderRoutes + ssgPageRoutes = workerResult.generatedRoutes.map( + (route) => route.path ) - ssgPageRoutes = workerResult.prerenderRoutes isSSG = true } @@ -2140,7 +2142,8 @@ export default async function build( if (appConfig.revalidate !== 0) { const isDynamic = isDynamicRoute(page) const hasGenerateStaticParams = - !!workerResult.prerenderRoutes?.length + workerResult.generatedRoutes && + workerResult.generatedRoutes.length > 0 if ( config.output === 'export' && @@ -2157,16 +2160,19 @@ export default async function build( // - It doesn't have generateStaticParams but `dynamic` is set to // `error` or `force-static` if (!isDynamic) { - appStaticPaths.set(originalAppPath, [page]) - appStaticPathsEncoded.set(originalAppPath, [page]) + paths.static.set(originalAppPath, [ + { + path: page, + encoded: page, + }, + ]) isStatic = true } else if ( !hasGenerateStaticParams && (appConfig.dynamic === 'error' || appConfig.dynamic === 'force-static') ) { - appStaticPaths.set(originalAppPath, []) - appStaticPathsEncoded.set(originalAppPath, []) + paths.static.set(originalAppPath, []) isStatic = true isRoutePPREnabled = false } @@ -2213,18 +2219,16 @@ export default async function build( isSSG = true if ( - workerResult.prerenderRoutes && - workerResult.encodedPrerenderRoutes + workerResult.generatedRoutes && + workerResult.generatedRoutes.length > 0 ) { - additionalSsgPaths.set( + paths.additional.set( page, - workerResult.prerenderRoutes + workerResult.generatedRoutes ) - additionalSsgPathsEncoded.set( - page, - workerResult.encodedPrerenderRoutes + ssgPageRoutes = workerResult.generatedRoutes.map( + (route) => route.path ) - ssgPageRoutes = workerResult.prerenderRoutes } if (workerResult.prerenderFallback === 'blocking') { @@ -2529,10 +2533,15 @@ export default async function build( } } - const finalPrerenderRoutes: { [route: string]: SsgRoute } = {} - const finalDynamicRoutes: PrerenderManifest['dynamicRoutes'] = {} + const prerenderManifest: PrerenderManifest = { + version: 4, + routes: {}, + dynamicRoutes: {}, + notFoundRoutes: [], + preview: previewProps, + } + const tbdPrerenderRoutes: string[] = [] - let ssgNotFoundPaths: string[] = [] const { i18n } = config @@ -2552,9 +2561,7 @@ export default async function build( !hasPages500 && !hasNonStaticErrorPage && !customAppGetInitialProps const combinedPages = [...staticPages, ...ssgPages] - const isApp404Static = appStaticPaths.has( - UNDERSCORE_NOT_FOUND_ROUTE_ENTRY - ) + const isApp404Static = paths.static.has(UNDERSCORE_NOT_FOUND_ROUTE_ENTRY) const hasStaticApp404 = hasApp404 && isApp404Static await updateBuildDiagnostics({ @@ -2581,7 +2588,13 @@ export default async function build( ...pageKeys.pages.filter((page) => !combinedPages.includes(page)), ], ssgPages, - additionalSsgPaths + new Map( + Array.from(paths.additional.entries()).map( + ([page, routes]): [string, string[]] => { + return [page, routes.map((route) => route.path)] + } + ) + ) ) const exportApp = require('../export') .default as typeof import('../export').default @@ -2627,13 +2640,11 @@ export default async function build( // Append the "well-known" routes we should prerender for, e.g. blog // post slugs. - additionalSsgPaths.forEach((routes, page) => { - const encodedRoutes = additionalSsgPathsEncoded.get(page) - - routes.forEach((route, routeIdx) => { - defaultMap[route] = { + paths.additional.forEach((routes, page) => { + routes.forEach((route) => { + defaultMap[route.path] = { page, - query: { __nextSsgPath: encodedRoutes?.[routeIdx] }, + query: { __nextSsgPath: route.encoded }, } }) }) @@ -2652,22 +2663,21 @@ export default async function build( // TODO: output manifest specific to app paths and their // revalidate periods and dynamicParams settings - appStaticPaths.forEach((routes, originalAppPath) => { - const encodedRoutes = appStaticPathsEncoded.get(originalAppPath) + paths.static.forEach((routes, originalAppPath) => { const appConfig = appDefaultConfigs.get(originalAppPath) + const isDynamicError = appConfig?.dynamic === 'error' - routes.forEach((route, routeIdx) => { - defaultMap[route] = { + const isRoutePPREnabled = appConfig + ? checkIsRoutePPREnabled(config.experimental.ppr, appConfig) + : undefined + + routes.forEach((route) => { + defaultMap[route.path] = { page: originalAppPath, - query: { __nextSsgPath: encodedRoutes?.[routeIdx] }, - _isDynamicError: appConfig?.dynamic === 'error', + query: { __nextSsgPath: route.encoded }, + _isDynamicError: isDynamicError, _isAppDir: true, - _isRoutePPREnabled: appConfig - ? checkIsRoutePPREnabled( - config.experimental.ppr, - appConfig - ) - : undefined, + _isRoutePPREnabled: isRoutePPREnabled, } }) }) @@ -2748,7 +2758,9 @@ export default async function build( ], }) - ssgNotFoundPaths = Array.from(exportResult.ssgNotFoundPaths) + prerenderManifest.notFoundRoutes = Array.from( + exportResult.ssgNotFoundPaths + ) // remove server bundles that were exported for (const page of staticPages) { @@ -2756,7 +2768,7 @@ export default async function build( await fs.unlink(serverBundle) } - for (const [originalAppPath, routes] of appStaticPaths) { + paths.static.forEach((routes, originalAppPath) => { const page = appNormalizedPaths.get(originalAppPath) || '' const appConfig = appDefaultConfigs.get(originalAppPath) || {} let hasDynamicData = @@ -2795,106 +2807,110 @@ export default async function build( ] // Always sort the routes to get consistent output in manifests - getSortedRoutes(routes).forEach((route) => { - if (isDynamicRoute(page) && route === page) return - if (route === UNDERSCORE_NOT_FOUND_ROUTE) return - - const { - revalidate = appConfig.revalidate ?? false, - metadata = {}, - hasEmptyPrelude, - hasPostponed, - } = exportResult.byPath.get(route) ?? {} - - pageInfos.set(route, { - ...(pageInfos.get(route) as PageInfo), - hasPostponed, - hasEmptyPrelude, - }) + getSortedRoutes(routes.map((route) => route.path)).forEach( + (route) => { + if (isDynamicRoute(page) && route === page) return + if (route === UNDERSCORE_NOT_FOUND_ROUTE) return + + const { + revalidate = appConfig.revalidate ?? false, + metadata = {}, + hasEmptyPrelude, + hasPostponed, + } = exportResult.byPath.get(route) ?? {} - // update the page (eg /blog/[slug]) to also have the postpone metadata - pageInfos.set(page, { - ...(pageInfos.get(page) as PageInfo), - hasPostponed, - hasEmptyPrelude, - }) + pageInfos.set(route, { + ...(pageInfos.get(route) as PageInfo), + hasPostponed, + hasEmptyPrelude, + }) - if (revalidate !== 0) { - const normalizedRoute = normalizePagePath(route) + // update the page (eg /blog/[slug]) to also have the postpone metadata + pageInfos.set(page, { + ...(pageInfos.get(page) as PageInfo), + hasPostponed, + hasEmptyPrelude, + }) - let dataRoute: string | null - if (isRouteHandler) { - dataRoute = null - } else { - dataRoute = path.posix.join(`${normalizedRoute}${RSC_SUFFIX}`) - } + if (revalidate !== 0) { + const normalizedRoute = normalizePagePath(route) - let prefetchDataRoute: string | null | undefined - // While we may only write the `.rsc` when the route does not - // have PPR enabled, we still want to generate the route when - // deployed so it doesn't 404. If the app has PPR enabled, we - // should add this key. - if (!isRouteHandler && isAppPPREnabled) { - prefetchDataRoute = path.posix.join( - `${normalizedRoute}${RSC_PREFETCH_SUFFIX}` - ) - } + let dataRoute: string | null + if (isRouteHandler) { + dataRoute = null + } else { + dataRoute = path.posix.join( + `${normalizedRoute}${RSC_SUFFIX}` + ) + } - const routeMeta: Partial = {} + let prefetchDataRoute: string | null | undefined + // While we may only write the `.rsc` when the route does not + // have PPR enabled, we still want to generate the route when + // deployed so it doesn't 404. If the app has PPR enabled, we + // should add this key. + if (!isRouteHandler && isAppPPREnabled) { + prefetchDataRoute = path.posix.join( + `${normalizedRoute}${RSC_PREFETCH_SUFFIX}` + ) + } - if (metadata.status !== 200) { - routeMeta.initialStatus = metadata.status - } + const routeMeta: Partial = {} - const exportHeaders = metadata.headers - const headerKeys = Object.keys(exportHeaders || {}) + if (metadata.status !== 200) { + routeMeta.initialStatus = metadata.status + } - if (exportHeaders && headerKeys.length) { - routeMeta.initialHeaders = {} + const exportHeaders = metadata.headers + const headerKeys = Object.keys(exportHeaders || {}) - // normalize header values as initialHeaders - // must be Record - for (const key of headerKeys) { - // set-cookie is already handled - the middleware cookie setting case - // isn't needed for the prerender manifest since it can't read cookies - if (key === 'x-middleware-set-cookie') continue + if (exportHeaders && headerKeys.length) { + routeMeta.initialHeaders = {} - let value = exportHeaders[key] + // normalize header values as initialHeaders + // must be Record + for (const key of headerKeys) { + // set-cookie is already handled - the middleware cookie setting case + // isn't needed for the prerender manifest since it can't read cookies + if (key === 'x-middleware-set-cookie') continue - if (Array.isArray(value)) { - if (key === 'set-cookie') { - value = value.join(',') - } else { - value = value[value.length - 1] + let value = exportHeaders[key] + + if (Array.isArray(value)) { + if (key === 'set-cookie') { + value = value.join(',') + } else { + value = value[value.length - 1] + } } - } - if (typeof value === 'string') { - routeMeta.initialHeaders[key] = value + if (typeof value === 'string') { + routeMeta.initialHeaders[key] = value + } } } - } - finalPrerenderRoutes[route] = { - ...routeMeta, - experimentalPPR, - experimentalBypassFor: bypassFor, - initialRevalidateSeconds: revalidate, - srcRoute: page, - dataRoute, - prefetchDataRoute, + prerenderManifest.routes[route] = { + ...routeMeta, + experimentalPPR, + experimentalBypassFor: bypassFor, + initialRevalidateSeconds: revalidate, + srcRoute: page, + dataRoute, + prefetchDataRoute, + } + } else { + hasDynamicData = true + // we might have determined during prerendering that this page + // used dynamic data + pageInfos.set(route, { + ...(pageInfos.get(route) as PageInfo), + isSSG: false, + isStatic: false, + }) } - } else { - hasDynamicData = true - // we might have determined during prerendering that this page - // used dynamic data - pageInfos.set(route, { - ...(pageInfos.get(route) as PageInfo), - isSSG: false, - isStatic: false, - }) } - }) + ) if (!hasDynamicData && isDynamicRoute(originalAppPath)) { const normalizedRoute = normalizePagePath(page) @@ -2924,9 +2940,7 @@ export default async function build( hasPostponed: experimentalPPR, }) - // TODO: create a separate manifest to allow enforcing - // dynamicParams for non-static paths? - finalDynamicRoutes[page] = { + prerenderManifest.dynamicRoutes[page] = { experimentalPPR, experimentalBypassFor: bypassFor, routeRegex: normalizeRouteRegex( @@ -2960,7 +2974,7 @@ export default async function build( ), } } - } + }) const moveExportedPage = async ( originPage: string, @@ -3016,7 +3030,8 @@ export default async function build( } const dest = path.join(distDir, SERVER_DIRECTORY, relativeDest) - const isNotFound = ssgNotFoundPaths.includes(page) + const isNotFound = + prerenderManifest.notFoundRoutes.includes(page) // for SSG files with i18n the non-prerendered variants are // output with the locale prefixed so don't attempt moving @@ -3041,7 +3056,10 @@ export default async function build( for (const locale of i18n.locales) { const curPath = `/${locale}${page === '/' ? '' : page}` - if (isSsg && ssgNotFoundPaths.includes(curPath)) { + if ( + isSsg && + prerenderManifest.notFoundRoutes.includes(curPath) + ) { continue } @@ -3171,7 +3189,7 @@ export default async function build( for (const locale of i18n.locales) { const localePage = `/${locale}${page === '/' ? '' : page}` - finalPrerenderRoutes[localePage] = { + prerenderManifest.routes[localePage] = { initialRevalidateSeconds: exportResult.byPath.get(localePage)?.revalidate ?? false, @@ -3186,7 +3204,7 @@ export default async function build( } } } else { - finalPrerenderRoutes[page] = { + prerenderManifest.routes[page] = { initialRevalidateSeconds: exportResult.byPath.get(page)?.revalidate ?? false, experimentalPPR: undefined, @@ -3210,12 +3228,11 @@ export default async function build( // copy the fallback HTML file (if present). // We must also copy specific versions of this page as defined by // `getStaticPaths` (additionalSsgPaths). - const extraRoutes = additionalSsgPaths.get(page) || [] - for (const route of extraRoutes) { - const pageFile = normalizePagePath(route) + for (const route of paths.additional.get(page) ?? []) { + const pageFile = normalizePagePath(route.path) await moveExportedPage( page, - route, + route.path, pageFile, isSsg, 'html', @@ -3223,7 +3240,7 @@ export default async function build( ) await moveExportedPage( page, - route, + route.path, pageFile, isSsg, 'json', @@ -3251,20 +3268,20 @@ export default async function build( } const initialRevalidateSeconds = - exportResult.byPath.get(route)?.revalidate ?? false + exportResult.byPath.get(route.path)?.revalidate ?? false if (typeof initialRevalidateSeconds === 'undefined') { throw new Error("Invariant: page wasn't built") } - finalPrerenderRoutes[route] = { + prerenderManifest.routes[route.path] = { initialRevalidateSeconds, experimentalPPR: undefined, srcRoute: page, dataRoute: path.posix.join( '/_next/data', buildId, - `${normalizePagePath(route)}.json` + `${normalizePagePath(route.path)}.json` ), // Pages does not have a prefetch data route. prefetchDataRoute: undefined, @@ -3342,7 +3359,7 @@ export default async function build( `${normalizedRoute}.json` ) - finalDynamicRoutes[tbdRoute] = { + prerenderManifest.dynamicRoutes[tbdRoute] = { routeRegex: normalizeRouteRegex( getNamedRouteRegex(tbdRoute, false).re.source ), @@ -3371,13 +3388,6 @@ export default async function build( NextBuildContext.allowedRevalidateHeaderKeys = config.experimental.allowedRevalidateHeaderKeys - const prerenderManifest: DeepReadonly = { - version: 4, - routes: finalPrerenderRoutes, - dynamicRoutes: finalDynamicRoutes, - notFoundRoutes: ssgNotFoundPaths, - preview: previewProps, - } await writePrerenderManifest(distDir, prerenderManifest) await writeClientSsgManifest(prerenderManifest, { distDir, diff --git a/packages/next/src/build/utils.ts b/packages/next/src/build/utils.ts index 8f8a212da99cd..30187b840cb41 100644 --- a/packages/next/src/build/utils.ts +++ b/packages/next/src/build/utils.ts @@ -939,6 +939,16 @@ export async function getJsPageSizeInKb( return [-1, -1] } +export type GeneratedStaticPath = { + path: string + encoded: string +} + +export type StaticPathsResult = { + fallback: boolean | 'blocking' + generatedRoutes: GeneratedStaticPath[] +} + export async function buildStaticPaths({ page, getStaticPaths, @@ -955,14 +965,8 @@ export async function buildStaticPaths({ locales?: string[] defaultLocale?: string appDir?: boolean -}): Promise< - Omit & { - paths: string[] - encodedPaths: string[] - } -> { - const prerenderPaths = new Set() - const encodedPrerenderPaths = new Set() +}): Promise { + const generatedRoutes: GeneratedStaticPath[] = [] const _routeRegex = getRouteRegex(page) const _routeMatcher = getRouteMatcher(_routeRegex) @@ -1051,15 +1055,15 @@ export async function buildStaticPaths({ // If leveraging the string paths variant the entry should already be // encoded so we decode the segments ensuring we only escape path // delimiters - prerenderPaths.add( - entry + generatedRoutes.push({ + path: entry .split('/') .map((segment) => escapePathDelimiters(decodeURIComponent(segment), true) ) - .join('/') - ) - encodedPrerenderPaths.add(entry) + .join('/'), + encoded: entry, + }) } // For the object-provided path, we must make sure it specifies all // required keys. @@ -1154,23 +1158,28 @@ export async function buildStaticPaths({ } const curLocale = entry.locale || defaultLocale || '' - prerenderPaths.add( - `${curLocale ? `/${curLocale}` : ''}${ + generatedRoutes.push({ + path: `${curLocale ? `/${curLocale}` : ''}${ curLocale && builtPage === '/' ? '' : builtPage - }` - ) - encodedPrerenderPaths.add( - `${curLocale ? `/${curLocale}` : ''}${ + }`, + encoded: `${curLocale ? `/${curLocale}` : ''}${ curLocale && encodedBuiltPage === '/' ? '' : encodedBuiltPage - }` - ) + }`, + }) } }) + const seen = new Set() + return { - paths: [...prerenderPaths], fallback: staticPathsResult.fallback, - encodedPaths: [...encodedPrerenderPaths], + generatedRoutes: generatedRoutes.filter((route) => { + if (seen.has(route.path)) return false + + // Filter out duplicate paths. + seen.add(route.path) + return true + }), } } @@ -1345,7 +1354,7 @@ export async function buildAppStaticPaths({ maxMemoryCacheSize?: number requestHeaders: IncrementalCache['requestHeaders'] ComponentMod: AppPageModule -}) { +}): Promise> { ComponentMod.patchFetch() let CacheHandler: any @@ -1393,7 +1402,7 @@ export async function buildAppStaticPaths({ }, }, }, - async () => { + async (): Promise> => { const pageEntry = generateParams[generateParams.length - 1] // if the page has legacy getStaticPaths we call it like normal @@ -1479,12 +1488,11 @@ export async function buildAppStaticPaths({ if (!hadAllParamsGenerated) { return { - paths: undefined, fallback: process.env.NODE_ENV === 'production' && isDynamicRoute(page) ? true : undefined, - encodedPaths: undefined, + generatedRoutes: undefined, } } @@ -1502,6 +1510,21 @@ export async function buildAppStaticPaths({ ) } +type PageIsStaticResult = { + isRoutePPREnabled?: boolean + isStatic?: boolean + isAmpOnly?: boolean + isHybridAmp?: boolean + hasServerProps?: boolean + hasStaticProps?: boolean + generatedRoutes: GeneratedStaticPath[] | undefined + prerenderFallback?: boolean | 'blocking' + isNextImageImported?: boolean + traceIncludes?: string[] + traceExcludes?: string[] + appConfig?: AppConfig +} + export async function isPageStatic({ dir, page, @@ -1539,24 +1562,10 @@ export async function isPageStatic({ cacheHandler?: string nextConfigOutput: 'standalone' | 'export' pprConfig: ExperimentalPPRConfig | undefined -}): Promise<{ - isRoutePPREnabled?: boolean - isStatic?: boolean - isAmpOnly?: boolean - isHybridAmp?: boolean - hasServerProps?: boolean - hasStaticProps?: boolean - prerenderRoutes?: string[] - encodedPrerenderRoutes?: string[] - prerenderFallback?: boolean | 'blocking' - isNextImageImported?: boolean - traceIncludes?: string[] - traceExcludes?: string[] - appConfig?: AppConfig -}> { +}): Promise { const isPageStaticSpan = trace('is-page-static-utils', parentId) return isPageStaticSpan - .traceAsyncFn(async () => { + .traceAsyncFn(async (): Promise => { require('../shared/lib/runtime-config.external').setConfig( runtimeEnvConfig ) @@ -1565,8 +1574,7 @@ export async function isPageStatic({ }) let componentsResult: LoadComponentsReturnType - let prerenderRoutes: Array | undefined - let encodedPrerenderRoutes: Array | undefined + let generatedRoutes: GeneratedStaticPath[] | undefined let prerenderFallback: boolean | 'blocking' | undefined let appConfig: AppConfig = {} let isClientComponent: boolean = false @@ -1640,49 +1648,7 @@ export async function isPageStatic({ ] : await collectGenerateParams(tree) - appConfig = generateParams.reduce( - (builtConfig: AppConfig, curGenParams): AppConfig => { - const { - dynamic, - fetchCache, - preferredRegion, - revalidate: curRevalidate, - experimental_ppr, - } = curGenParams?.config || {} - - // TODO: should conflicting configs here throw an error - // e.g. if layout defines one region but page defines another - if (typeof builtConfig.preferredRegion === 'undefined') { - builtConfig.preferredRegion = preferredRegion - } - if (typeof builtConfig.dynamic === 'undefined') { - builtConfig.dynamic = dynamic - } - if (typeof builtConfig.fetchCache === 'undefined') { - builtConfig.fetchCache = fetchCache - } - // If partial prerendering has been set, only override it if the current value is - // provided as it's resolved from root layout to leaf page. - if (typeof experimental_ppr !== 'undefined') { - builtConfig.experimental_ppr = experimental_ppr - } - - // any revalidate number overrides false - // shorter revalidate overrides longer (initially) - if (typeof builtConfig.revalidate === 'undefined') { - builtConfig.revalidate = curRevalidate - } - if ( - typeof curRevalidate === 'number' && - (typeof builtConfig.revalidate !== 'number' || - curRevalidate < builtConfig.revalidate) - ) { - builtConfig.revalidate = curRevalidate - } - return builtConfig - }, - {} - ) + appConfig = reduceAppConfig(generateParams) if (appConfig.dynamic === 'force-static' && pathIsEdgeRuntime) { Log.warn( @@ -1706,22 +1672,19 @@ export async function isPageStatic({ } if (isDynamicRoute(page)) { - ;({ - paths: prerenderRoutes, - fallback: prerenderFallback, - encodedPaths: encodedPrerenderRoutes, - } = await buildAppStaticPaths({ - dir, - page, - configFileName, - generateParams, - distDir, - requestHeaders: {}, - isrFlushToDisk, - maxMemoryCacheSize, - cacheHandler, - ComponentMod, - })) + ;({ fallback: prerenderFallback, generatedRoutes } = + await buildAppStaticPaths({ + dir, + page, + configFileName, + generateParams, + distDir, + requestHeaders: {}, + isrFlushToDisk, + maxMemoryCacheSize, + cacheHandler, + ComponentMod, + })) } } else { if (!Comp || !isValidElementType(Comp) || typeof Comp === 'string') { @@ -1765,18 +1728,15 @@ export async function isPageStatic({ } if ((hasStaticProps && hasStaticPaths) || staticPathsResult) { - ;({ - paths: prerenderRoutes, - fallback: prerenderFallback, - encodedPaths: encodedPrerenderRoutes, - } = await buildStaticPaths({ - page, - locales, - defaultLocale, - configFileName, - staticPathsResult, - getStaticPaths: componentsResult.getStaticPaths!, - })) + ;({ fallback: prerenderFallback, generatedRoutes } = + await buildStaticPaths({ + page, + locales, + defaultLocale, + configFileName, + staticPathsResult, + getStaticPaths: componentsResult.getStaticPaths!, + })) } const isNextImageImported = (globalThis as any).__NEXT_IMAGE_IMPORTED @@ -1800,9 +1760,8 @@ export async function isPageStatic({ isRoutePPREnabled, isHybridAmp: config.amp === 'hybrid', isAmpOnly: config.amp === true, - prerenderRoutes, prerenderFallback, - encodedPrerenderRoutes, + generatedRoutes, hasStaticProps, hasServerProps, isNextImageImported, @@ -1818,6 +1777,54 @@ export async function isPageStatic({ }) } +export function reduceAppConfig( + generateParams: GenerateParamsResults +): AppConfig { + return generateParams.reduce( + (builtConfig: AppConfig, curGenParams): AppConfig => { + const { + dynamic, + fetchCache, + preferredRegion, + revalidate: curRevalidate, + experimental_ppr, + } = curGenParams?.config || {} + + // TODO: should conflicting configs here throw an error + // e.g. if layout defines one region but page defines another + if (typeof builtConfig.preferredRegion === 'undefined') { + builtConfig.preferredRegion = preferredRegion + } + if (typeof builtConfig.dynamic === 'undefined') { + builtConfig.dynamic = dynamic + } + if (typeof builtConfig.fetchCache === 'undefined') { + builtConfig.fetchCache = fetchCache + } + // If partial prerendering has been set, only override it if the current value is + // provided as it's resolved from root layout to leaf page. + if (typeof experimental_ppr !== 'undefined') { + builtConfig.experimental_ppr = experimental_ppr + } + + // any revalidate number overrides false + // shorter revalidate overrides longer (initially) + if (typeof builtConfig.revalidate === 'undefined') { + builtConfig.revalidate = curRevalidate + } + if ( + typeof curRevalidate === 'number' && + (typeof builtConfig.revalidate !== 'number' || + curRevalidate < builtConfig.revalidate) + ) { + builtConfig.revalidate = curRevalidate + } + return builtConfig + }, + {} + ) +} + export async function hasCustomGetInitialProps({ page, distDir, @@ -1871,7 +1878,7 @@ export async function getDefinedNamedExports({ export function detectConflictingPaths( combinedPages: string[], ssgPages: Set, - additionalSsgPaths: Map + additionalGeneratedSSGPaths: Map ) { const conflictingPaths = new Map< string, @@ -1886,7 +1893,7 @@ export function detectConflictingPaths( [page: string]: { [path: string]: string } } = {} - additionalSsgPaths.forEach((paths, pathsPage) => { + additionalGeneratedSSGPaths.forEach((paths, pathsPage) => { additionalSsgPathsByPath[pathsPage] ||= {} paths.forEach((curPath) => { const currentPath = curPath.toLowerCase() @@ -1894,7 +1901,7 @@ export function detectConflictingPaths( }) }) - additionalSsgPaths.forEach((paths, pathsPage) => { + additionalGeneratedSSGPaths.forEach((paths, pathsPage) => { paths.forEach((curPath) => { const lowerPath = curPath.toLowerCase() let conflictingPage = combinedPages.find( @@ -1913,7 +1920,7 @@ export function detectConflictingPaths( if (page === pathsPage) return false conflictingPath = - additionalSsgPaths.get(page) == null + additionalGeneratedSSGPaths.get(page) == null ? undefined : additionalSsgPathsByPath[page][lowerPath] return conflictingPath diff --git a/packages/next/src/server/dev/next-dev-server.ts b/packages/next/src/server/dev/next-dev-server.ts index 29c4e2a899625..1de1e63f6be9b 100644 --- a/packages/next/src/server/dev/next-dev-server.ts +++ b/packages/next/src/server/dev/next-dev-server.ts @@ -746,7 +746,7 @@ export default class DevServer extends Server { [] ) .then((res) => { - const { paths: staticPaths = [], fallback } = res.value + const { generatedRoutes: staticPaths = [], fallback } = res.value if (!isAppPath && this.nextConfig.output === 'export') { if (fallback === 'blocking') { throw new Error( @@ -762,7 +762,7 @@ export default class DevServer extends Server { staticPaths: string[] fallbackMode: FallbackMode } = { - staticPaths, + staticPaths: staticPaths.map((route) => route.path), fallbackMode: fallback === 'blocking' ? 'blocking' diff --git a/packages/next/src/server/dev/static-paths-worker.ts b/packages/next/src/server/dev/static-paths-worker.ts index c1258e1234d94..16df5350d4456 100644 --- a/packages/next/src/server/dev/static-paths-worker.ts +++ b/packages/next/src/server/dev/static-paths-worker.ts @@ -8,7 +8,10 @@ import { buildStaticPaths, collectGenerateParams, } from '../../build/utils' -import type { GenerateParamsResults } from '../../build/utils' +import type { + GenerateParamsResults, + StaticPathsResult, +} from '../../build/utils' import { loadComponents } from '../load-components' import { setHttpClientAndAgentOptions } from '../setup-http-agent-env' import type { IncrementalCache } from '../lib/incremental-cache' @@ -53,11 +56,7 @@ export async function loadStaticPaths({ maxMemoryCacheSize?: number requestHeaders: IncrementalCache['requestHeaders'] cacheHandler?: string -}): Promise<{ - paths?: string[] - encodedPaths?: string[] - fallback?: boolean | 'blocking' -}> { +}): Promise> { // update work memory runtime-config require('../../shared/lib/runtime-config.external').setConfig(config) setHttpClientAndAgentOptions({