UNPKG

next

Version:

The React Framework

837 lines (836 loc) • 123 kB
import "../lib/setup-exception-listeners"; import { loadEnvConfig } from "@next/env"; import chalk from "next/dist/compiled/chalk"; import crypto from "crypto"; import { isMatch, makeRe } from "next/dist/compiled/micromatch"; import { promises as fs, existsSync as fsExistsSync } from "fs"; import os from "os"; import { Worker } from "../lib/worker"; import { defaultConfig } from "../server/config-shared"; import devalue from "next/dist/compiled/devalue"; import findUp from "next/dist/compiled/find-up"; import { nanoid } from "next/dist/compiled/nanoid/index.cjs"; import { pathToRegexp } from "next/dist/compiled/path-to-regexp"; import path from "path"; import { STATIC_STATUS_PAGE_GET_INITIAL_PROPS_ERROR, PUBLIC_DIR_MIDDLEWARE_CONFLICT, MIDDLEWARE_FILENAME, PAGES_DIR_ALIAS, INSTRUMENTATION_HOOK_FILENAME } from "../lib/constants"; import { FileType, fileExists } from "../lib/file-exists"; import { findPagesDir } from "../lib/find-pages-dir"; import loadCustomRoutes, { normalizeRouteRegex } from "../lib/load-custom-routes"; import { getRedirectStatus, modifyRouteRegex } from "../lib/redirect-status"; import { nonNullable } from "../lib/non-nullable"; import { recursiveDelete } from "../lib/recursive-delete"; import { verifyPartytownSetup } from "../lib/verify-partytown-setup"; import { BUILD_ID_FILE, BUILD_MANIFEST, CLIENT_STATIC_FILES_PATH, EXPORT_DETAIL, EXPORT_MARKER, FONT_MANIFEST, IMAGES_MANIFEST, PAGES_MANIFEST, PHASE_PRODUCTION_BUILD, PRERENDER_MANIFEST, REACT_LOADABLE_MANIFEST, ROUTES_MANIFEST, SERVER_DIRECTORY, SERVER_FILES_MANIFEST, STATIC_STATUS_PAGES, MIDDLEWARE_MANIFEST, APP_PATHS_MANIFEST, APP_PATH_ROUTES_MANIFEST, APP_BUILD_MANIFEST, RSC_MODULE_TYPES, NEXT_FONT_MANIFEST, SUBRESOURCE_INTEGRITY_MANIFEST, MIDDLEWARE_BUILD_MANIFEST, MIDDLEWARE_REACT_LOADABLE_MANIFEST, TURBO_TRACE_DEFAULT_MEMORY_LIMIT, TRACE_OUTPUT_VERSION, SERVER_REFERENCE_MANIFEST, FUNCTIONS_CONFIG_MANIFEST } from "../shared/lib/constants"; import { getSortedRoutes, isDynamicRoute } from "../shared/lib/router/utils"; import loadConfig from "../server/config"; import { normalizePagePath } from "../shared/lib/page-path/normalize-page-path"; import { getPagePath } from "../server/require"; import * as ciEnvironment from "../telemetry/ci-info"; import { eventBuildOptimize, eventCliSession, eventBuildFeatureUsage, eventNextPlugins, EVENT_BUILD_FEATURE_USAGE, eventPackageUsedInGetServerSideProps, eventBuildCompleted } from "../telemetry/events"; import { Telemetry } from "../telemetry/storage"; import { isDynamicMetadataRoute, getPageStaticInfo } from "./analysis/get-page-static-info"; import { createPagesMapping, getPageFilePath } from "./entries"; import { generateBuildId } from "./generate-build-id"; import { isWriteable } from "./is-writeable"; import * as Log from "./output/log"; import createSpinner from "./spinner"; import { trace, flushAllTraces, setGlobal } from "../trace"; import { detectConflictingPaths, computeFromManifest, getJsPageSizeInKb, printCustomRoutes, printTreeView, copyTracedFiles, isReservedPage } from "./utils"; import { writeBuildId } from "./write-build-id"; import { normalizeLocalePath } from "../shared/lib/i18n/normalize-locale-path"; import isError from "../lib/is-error"; import { isEdgeRuntime } from "../lib/is-edge-runtime"; import { recursiveCopy } from "../lib/recursive-copy"; import { recursiveReadDir } from "../lib/recursive-readdir"; import { lockfilePatchPromise, teardownTraceSubscriber, teardownCrashReporter, loadBindings, teardownHeapProfiler } from "./swc"; import { getNamedRouteRegex } from "../shared/lib/router/utils/route-regex"; import { flatReaddir } from "../lib/flat-readdir"; import { eventSwcPlugins } from "../telemetry/events/swc-plugins"; import { normalizeAppPath } from "../shared/lib/router/utils/app-paths"; import { RSC, RSC_CONTENT_TYPE_HEADER, RSC_VARY_HEADER } from "../client/components/app-router-headers"; import { webpackBuild } from "./webpack-build"; import { NextBuildContext } from "./build-context"; import { normalizePathSep } from "../shared/lib/page-path/normalize-path-sep"; import { isAppRouteRoute } from "../lib/is-app-route-route"; import { createClientRouterFilter } from "../lib/create-client-router-filter"; import { createValidFileMatcher } from "../server/lib/find-page-file"; import { startTypeChecking } from "./type-check"; import { generateInterceptionRoutesRewrites } from "../lib/generate-interception-routes-rewrites"; import { buildDataRoute } from "../server/lib/router-utils/build-data-route"; import { baseOverrides, experimentalOverrides } from "../server/require-hook"; import { initialize } from "../server/lib/incremental-cache-server"; import { nodeFs } from "../server/lib/node-fs-methods"; async function generateClientSsgManifest(prerenderManifest, { buildId , distDir , locales }) { const ssgPages = new Set([ ...Object.entries(prerenderManifest.routes)// Filter out dynamic routes .filter(([, { srcRoute }])=>srcRoute == null).map(([route])=>normalizeLocalePath(route, locales).pathname), ...Object.keys(prerenderManifest.dynamicRoutes) ].sort()); const clientSsgManifestContent = `self.__SSG_MANIFEST=${devalue(ssgPages)};self.__SSG_MANIFEST_CB&&self.__SSG_MANIFEST_CB()`; await fs.writeFile(path.join(distDir, CLIENT_STATIC_FILES_PATH, buildId, "_ssgManifest.js"), clientSsgManifestContent); } function pageToRoute(page) { const routeRegex = getNamedRouteRegex(page, true); return { page, regex: normalizeRouteRegex(routeRegex.re.source), routeKeys: routeRegex.routeKeys, namedRegex: routeRegex.namedRegex }; } export default async function build(dir, reactProductionProfiling = false, debugOutput = false, runLint = true, noMangling = false, appDirOnly = false, turboNextBuild = false, turboNextBuildRoot = null, buildMode) { const isCompile = buildMode === "experimental-compile"; const isGenerate = buildMode === "experimental-generate"; let hasAppDir = false; try { const nextBuildSpan = trace("next-build", undefined, { version: "13.4.13" }); NextBuildContext.nextBuildSpan = nextBuildSpan; NextBuildContext.dir = dir; NextBuildContext.appDirOnly = appDirOnly; NextBuildContext.reactProductionProfiling = reactProductionProfiling; NextBuildContext.noMangling = noMangling; const buildResult = await nextBuildSpan.traceAsyncFn(async ()=>{ var _mappedPages_404, _config_images; // attempt to load global env values so they are available in next.config.js const { loadedEnvFiles } = nextBuildSpan.traceChild("load-dotenv").traceFn(()=>loadEnvConfig(dir, false, Log)); NextBuildContext.loadedEnvFiles = loadedEnvFiles; const config = await nextBuildSpan.traceChild("load-next-config").traceAsyncFn(()=>loadConfig(PHASE_PRODUCTION_BUILD, dir)); NextBuildContext.config = config; let configOutDir = "out"; if (config.output === "export" && config.distDir !== ".next") { // In the past, a user had to run "next build" to generate // ".next" (or whatever the distDir) followed by "next export" // to generate "out" (or whatever the outDir). However, when // "output: export" is configured, "next build" does both steps. // So the user-configured distDir is actually the outDir. configOutDir = config.distDir; config.distDir = ".next"; } const distDir = path.join(dir, config.distDir); setGlobal("phase", PHASE_PRODUCTION_BUILD); setGlobal("distDir", distDir); let buildId = ""; if (isGenerate) { buildId = await fs.readFile(path.join(distDir, "BUILD_ID"), "utf8"); } else { buildId = await nextBuildSpan.traceChild("generate-buildid").traceAsyncFn(()=>generateBuildId(config.generateBuildId, nanoid)); } NextBuildContext.buildId = buildId; const customRoutes = await nextBuildSpan.traceChild("load-custom-routes").traceAsyncFn(()=>loadCustomRoutes(config)); const { headers , rewrites , redirects } = customRoutes; NextBuildContext.rewrites = rewrites; NextBuildContext.originalRewrites = config._originalRewrites; NextBuildContext.originalRedirects = config._originalRedirects; const cacheDir = path.join(distDir, "cache"); if (ciEnvironment.isCI && !ciEnvironment.hasNextSupport) { const hasCache = await fileExists(cacheDir); if (!hasCache) { // Intentionally not piping to stderr in case people fail in CI when // stderr is detected. console.log(`${Log.prefixes.warn} No build cache found. Please configure build caching for faster rebuilds. Read more: https://nextjs.org/docs/messages/no-cache`); } } const telemetry = new Telemetry({ distDir }); setGlobal("telemetry", telemetry); const publicDir = path.join(dir, "public"); const isAppDirEnabled = !!config.experimental.appDir; const { pagesDir , appDir } = findPagesDir(dir, isAppDirEnabled); NextBuildContext.pagesDir = pagesDir; NextBuildContext.appDir = appDir; hasAppDir = Boolean(appDir); const isSrcDir = path.relative(dir, pagesDir || appDir || "").startsWith("src"); const hasPublicDir = await fileExists(publicDir); telemetry.record(eventCliSession(dir, config, { webpackVersion: 5, cliCommand: "build", isSrcDir, hasNowJson: !!await findUp("now.json", { cwd: dir }), isCustomServer: null, turboFlag: false, pagesDir: !!pagesDir, appDir: !!appDir })); eventNextPlugins(path.resolve(dir)).then((events)=>telemetry.record(events)); eventSwcPlugins(path.resolve(dir), config).then((events)=>telemetry.record(events)); const ignoreESLint = Boolean(config.eslint.ignoreDuringBuilds); const shouldLint = !ignoreESLint && runLint; const typeCheckingOptions = { dir, appDir, pagesDir, runLint, shouldLint, ignoreESLint, telemetry, nextBuildSpan, config, cacheDir }; // For app directory, we run type checking after build. That's because // we dynamically generate types for each layout and page in the app // directory. if (!appDir && !isCompile) await startTypeChecking(typeCheckingOptions); if (appDir && "exportPathMap" in config) { Log.error('The "exportPathMap" configuration cannot be used with the "app" directory. Please use generateStaticParams() instead.'); await telemetry.flush(); process.exit(1); } const buildLintEvent = { featureName: "build-lint", invocationCount: shouldLint ? 1 : 0 }; telemetry.record({ eventName: EVENT_BUILD_FEATURE_USAGE, payload: buildLintEvent }); let buildSpinner = { stopAndPersist () { return this; } }; if (!isGenerate) { buildSpinner = createSpinner({ prefixText: `${Log.prefixes.info} Creating an optimized production build` }); } NextBuildContext.buildSpinner = buildSpinner; const validFileMatcher = createValidFileMatcher(config.pageExtensions, appDir); const pagesPaths = !appDirOnly && pagesDir ? await nextBuildSpan.traceChild("collect-pages").traceAsyncFn(()=>recursiveReadDir(pagesDir, validFileMatcher.isPageFile)) : []; const middlewareDetectionRegExp = new RegExp(`^${MIDDLEWARE_FILENAME}\\.(?:${config.pageExtensions.join("|")})$`); const instrumentationHookDetectionRegExp = new RegExp(`^${INSTRUMENTATION_HOOK_FILENAME}\\.(?:${config.pageExtensions.join("|")})$`); const rootDir = path.join(pagesDir || appDir, ".."); const instrumentationHookEnabled = Boolean(config.experimental.instrumentationHook); const rootPaths = (await flatReaddir(rootDir, [ middlewareDetectionRegExp, ...instrumentationHookEnabled ? [ instrumentationHookDetectionRegExp ] : [] ])).map((absoluteFile)=>absoluteFile.replace(dir, "")); const hasInstrumentationHook = rootPaths.some((p)=>p.includes(INSTRUMENTATION_HOOK_FILENAME)); NextBuildContext.hasInstrumentationHook = hasInstrumentationHook; const previewProps = { previewModeId: crypto.randomBytes(16).toString("hex"), previewModeSigningKey: crypto.randomBytes(32).toString("hex"), previewModeEncryptionKey: crypto.randomBytes(32).toString("hex") }; NextBuildContext.previewProps = previewProps; const mappedPages = nextBuildSpan.traceChild("create-pages-mapping").traceFn(()=>createPagesMapping({ isDev: false, pageExtensions: config.pageExtensions, pagesType: "pages", pagePaths: pagesPaths, pagesDir })); NextBuildContext.mappedPages = mappedPages; let mappedAppPages; let denormalizedAppPages; if (appDir) { const appPaths = await nextBuildSpan.traceChild("collect-app-paths").traceAsyncFn(()=>recursiveReadDir(appDir, (absolutePath)=>validFileMatcher.isAppRouterPage(absolutePath) || // For now we only collect the root /not-found page in the app // directory as the 404 fallback validFileMatcher.isRootNotFound(absolutePath), undefined, (part)=>part.startsWith("_"))); mappedAppPages = nextBuildSpan.traceChild("create-app-mapping").traceFn(()=>createPagesMapping({ pagePaths: appPaths, isDev: false, pagesType: "app", pageExtensions: config.pageExtensions, pagesDir: pagesDir })); // If the metadata route doesn't contain generating dynamic exports, // we can replace the dynamic catch-all route and use the static route instead. for (const [pageKey, pagePath] of Object.entries(mappedAppPages)){ if (pageKey.includes("[[...__metadata_id__]]")) { const pageFilePath = getPageFilePath({ absolutePagePath: pagePath, pagesDir, appDir, rootDir }); const isDynamic = await isDynamicMetadataRoute(pageFilePath); if (!isDynamic) { delete mappedAppPages[pageKey]; mappedAppPages[pageKey.replace("[[...__metadata_id__]]/", "")] = pagePath; } if (pageKey.includes("sitemap.xml/[[...__metadata_id__]]") && isDynamic) { delete mappedAppPages[pageKey]; mappedAppPages[pageKey.replace("sitemap.xml/[[...__metadata_id__]]", "sitemap/[__metadata_id__]")] = pagePath; } } } NextBuildContext.mappedAppPages = mappedAppPages; } let mappedRootPaths = {}; if (rootPaths.length > 0) { mappedRootPaths = createPagesMapping({ isDev: false, pageExtensions: config.pageExtensions, pagePaths: rootPaths, pagesType: "root", pagesDir: pagesDir }); } NextBuildContext.mappedRootPaths = mappedRootPaths; const pagesPageKeys = Object.keys(mappedPages); const conflictingAppPagePaths = []; const appPageKeys = []; if (mappedAppPages) { denormalizedAppPages = Object.keys(mappedAppPages); for (const appKey of denormalizedAppPages){ const normalizedAppPageKey = normalizeAppPath(appKey); const pagePath = mappedPages[normalizedAppPageKey]; if (pagePath) { const appPath = mappedAppPages[appKey]; conflictingAppPagePaths.push([ pagePath.replace(/^private-next-pages/, "pages"), appPath.replace(/^private-next-app-dir/, "app") ]); } appPageKeys.push(normalizedAppPageKey); } } // Interception routes are modelled as beforeFiles rewrites rewrites.beforeFiles.push(...generateInterceptionRoutesRewrites(appPageKeys)); const totalAppPagesCount = appPageKeys.length; const pageKeys = { pages: pagesPageKeys, app: appPageKeys.length > 0 ? appPageKeys : undefined }; if (turboNextBuild) { // TODO(WEB-397) This is a temporary workaround to allow for filtering a // subset of pages when building with --experimental-turbo, until we // have complete support for all pages. if (process.env.NEXT_TURBO_FILTER_PAGES) { var _pageKeys_app; const filterPages = process.env.NEXT_TURBO_FILTER_PAGES.split(","); pageKeys.pages = pageKeys.pages.filter((page)=>{ return filterPages.some((filterPage)=>{ return isMatch(page, filterPage); }); }); pageKeys.app = (_pageKeys_app = pageKeys.app) == null ? void 0 : _pageKeys_app.filter((page)=>{ return filterPages.some((filterPage)=>{ return isMatch(page, filterPage); }); }); } } const numConflictingAppPaths = conflictingAppPagePaths.length; if (mappedAppPages && numConflictingAppPaths > 0) { Log.error(`Conflicting app and page file${numConflictingAppPaths === 1 ? " was" : "s were"} found, please remove the conflicting files to continue:`); for (const [pagePath, appPath] of conflictingAppPagePaths){ Log.error(` "${pagePath}" - "${appPath}"`); } await telemetry.flush(); process.exit(1); } const conflictingPublicFiles = []; const hasPages404 = (_mappedPages_404 = mappedPages["/404"]) == null ? void 0 : _mappedPages_404.startsWith(PAGES_DIR_ALIAS); const hasApp404 = !!(mappedAppPages == null ? void 0 : mappedAppPages["/_not-found"]); const hasCustomErrorPage = mappedPages["/_error"].startsWith(PAGES_DIR_ALIAS); if (hasPublicDir) { const hasPublicUnderScoreNextDir = await fileExists(path.join(publicDir, "_next")); if (hasPublicUnderScoreNextDir) { throw new Error(PUBLIC_DIR_MIDDLEWARE_CONFLICT); } } await nextBuildSpan.traceChild("public-dir-conflict-check").traceAsyncFn(async ()=>{ // Check if pages conflict with files in `public` // Only a page of public file can be served, not both. for(const page in mappedPages){ const hasPublicPageFile = await fileExists(path.join(publicDir, page === "/" ? "/index" : page), FileType.File); if (hasPublicPageFile) { conflictingPublicFiles.push(page); } } const numConflicting = conflictingPublicFiles.length; if (numConflicting) { throw new Error(`Conflicting public and page file${numConflicting === 1 ? " was" : "s were"} found. https://nextjs.org/docs/messages/conflicting-public-file-page\n${conflictingPublicFiles.join("\n")}`); } }); const nestedReservedPages = pageKeys.pages.filter((page)=>{ return page.match(/\/(_app|_document|_error)$/) && path.dirname(page) !== "/"; }); if (nestedReservedPages.length) { Log.warn(`The following reserved Next.js pages were detected not directly under the pages directory:\n` + nestedReservedPages.join("\n") + `\nSee more info here: https://nextjs.org/docs/messages/nested-reserved-page\n`); } const restrictedRedirectPaths = [ "/_next" ].map((p)=>config.basePath ? `${config.basePath}${p}` : p); const buildCustomRoute = (r, type)=>{ const keys = []; const routeRegex = pathToRegexp(r.source, keys, { strict: true, sensitive: false, delimiter: "/" }); let regexSource = routeRegex.source; if (!r.internal) { regexSource = modifyRouteRegex(routeRegex.source, type === "redirect" ? restrictedRedirectPaths : undefined); } return { ...r, ...type === "redirect" ? { statusCode: getRedirectStatus(r), permanent: undefined } : {}, regex: normalizeRouteRegex(regexSource) }; }; const routesManifestPath = path.join(distDir, ROUTES_MANIFEST); const routesManifest = nextBuildSpan.traceChild("generate-routes-manifest").traceFn(()=>{ const sortedRoutes = getSortedRoutes([ ...pageKeys.pages, ...pageKeys.app ?? [] ]); const dynamicRoutes = []; const staticRoutes = []; for (const route of sortedRoutes){ if (isDynamicRoute(route)) { dynamicRoutes.push(pageToRoute(route)); } else if (!isReservedPage(route)) { staticRoutes.push(pageToRoute(route)); } } return { version: 3, pages404: true, caseSensitive: !!config.experimental.caseSensitiveRoutes, basePath: config.basePath, redirects: redirects.map((r)=>buildCustomRoute(r, "redirect")), headers: headers.map((r)=>buildCustomRoute(r, "header")), dynamicRoutes, staticRoutes, dataRoutes: [], i18n: config.i18n || undefined, rsc: { header: RSC, varyHeader: RSC_VARY_HEADER, contentTypeHeader: RSC_CONTENT_TYPE_HEADER }, skipMiddlewareUrlNormalize: config.skipMiddlewareUrlNormalize }; }); if (rewrites.beforeFiles.length === 0 && rewrites.fallback.length === 0) { routesManifest.rewrites = rewrites.afterFiles.map((r)=>buildCustomRoute(r, "rewrite")); } else { routesManifest.rewrites = { beforeFiles: rewrites.beforeFiles.map((r)=>buildCustomRoute(r, "rewrite")), afterFiles: rewrites.afterFiles.map((r)=>buildCustomRoute(r, "rewrite")), fallback: rewrites.fallback.map((r)=>buildCustomRoute(r, "rewrite")) }; } const combinedRewrites = [ ...rewrites.beforeFiles, ...rewrites.afterFiles, ...rewrites.fallback ]; if (config.experimental.clientRouterFilter) { const nonInternalRedirects = (config._originalRedirects || []).filter((r)=>!r.internal); const clientRouterFilters = createClientRouterFilter(appPageKeys, config.experimental.clientRouterFilterRedirects ? nonInternalRedirects : [], config.experimental.clientRouterFilterAllowedRate); NextBuildContext.clientRouterFilters = clientRouterFilters; } const distDirCreated = await nextBuildSpan.traceChild("create-dist-dir").traceAsyncFn(async ()=>{ try { await fs.mkdir(distDir, { recursive: true }); return true; } catch (err) { if (isError(err) && err.code === "EPERM") { return false; } throw err; } }); if (!distDirCreated || !await isWriteable(distDir)) { throw new Error("> Build directory is not writeable. https://nextjs.org/docs/messages/build-dir-not-writeable"); } if (config.cleanDistDir && !isGenerate) { await recursiveDelete(distDir, /^cache/); } // Ensure commonjs handling is used for files in the distDir (generally .next) // Files outside of the distDir can be "type": "module" await fs.writeFile(path.join(distDir, "package.json"), '{"type": "commonjs"}'); // We need to write the manifest with rewrites before build await nextBuildSpan.traceChild("write-routes-manifest").traceAsyncFn(()=>fs.writeFile(routesManifestPath, JSON.stringify(routesManifest), "utf8")); // We need to write a partial prerender manifest to make preview mode settings available in edge middleware const partialManifest = { preview: previewProps }; await fs.writeFile(path.join(distDir, PRERENDER_MANIFEST).replace(/\.json$/, ".js"), `self.__PRERENDER_MANIFEST=${JSON.stringify(JSON.stringify(partialManifest))}`, "utf8"); const outputFileTracingRoot = config.experimental.outputFileTracingRoot || dir; const manifestPath = path.join(distDir, SERVER_DIRECTORY, PAGES_MANIFEST); const { incrementalCacheHandlerPath } = config.experimental; const requiredServerFiles = nextBuildSpan.traceChild("generate-required-server-files").traceFn(()=>({ version: 1, config: { ...config, configFile: undefined, ...ciEnvironment.hasNextSupport ? { compress: false } : {}, experimental: { ...config.experimental, trustHostHeader: ciEnvironment.hasNextSupport, incrementalCacheHandlerPath: incrementalCacheHandlerPath ? path.relative(distDir, incrementalCacheHandlerPath) : undefined } }, appDir: dir, relativeAppDir: path.relative(outputFileTracingRoot, dir), files: [ ROUTES_MANIFEST, path.relative(distDir, manifestPath), BUILD_MANIFEST, PRERENDER_MANIFEST, PRERENDER_MANIFEST.replace(/\.json$/, ".js"), path.join(SERVER_DIRECTORY, MIDDLEWARE_MANIFEST), path.join(SERVER_DIRECTORY, MIDDLEWARE_BUILD_MANIFEST + ".js"), path.join(SERVER_DIRECTORY, MIDDLEWARE_REACT_LOADABLE_MANIFEST + ".js"), ...appDir ? [ ...config.experimental.sri ? [ path.join(SERVER_DIRECTORY, SUBRESOURCE_INTEGRITY_MANIFEST + ".js"), path.join(SERVER_DIRECTORY, SUBRESOURCE_INTEGRITY_MANIFEST + ".json") ] : [], path.join(SERVER_DIRECTORY, APP_PATHS_MANIFEST), path.join(APP_PATH_ROUTES_MANIFEST), APP_BUILD_MANIFEST, path.join(SERVER_DIRECTORY, SERVER_REFERENCE_MANIFEST + ".js"), path.join(SERVER_DIRECTORY, SERVER_REFERENCE_MANIFEST + ".json") ] : [], REACT_LOADABLE_MANIFEST, config.optimizeFonts ? path.join(SERVER_DIRECTORY, FONT_MANIFEST) : null, BUILD_ID_FILE, path.join(SERVER_DIRECTORY, NEXT_FONT_MANIFEST + ".js"), path.join(SERVER_DIRECTORY, NEXT_FONT_MANIFEST + ".json"), ...hasInstrumentationHook ? [ path.join(SERVER_DIRECTORY, `${INSTRUMENTATION_HOOK_FILENAME}.js`), path.join(SERVER_DIRECTORY, `edge-${INSTRUMENTATION_HOOK_FILENAME}.js`) ] : [] ].filter(nonNullable).map((file)=>path.join(config.distDir, file)), ignore: [] })); let binding = await loadBindings(); async function turbopackBuild() { const turboNextBuildStart = process.hrtime(); const turboJson = findUp.sync("turbo.json", { cwd: dir }); // eslint-disable-next-line no-shadow const packagePath = findUp.sync("package.json", { cwd: dir }); let root = turboNextBuildRoot ?? (turboJson ? path.dirname(turboJson) : packagePath ? path.dirname(packagePath) : undefined); await binding.turbo.nextBuild({ ...NextBuildContext, root }); const [duration] = process.hrtime(turboNextBuildStart); return { duration, turbotraceContext: null }; } let runTurbotrace = async (_staticPages)=>{}; let turboTasksForTrace; if (!isGenerate) { const { duration: webpackBuildDuration , turbotraceContext } = turboNextBuild ? await turbopackBuild() : await webpackBuild(); telemetry.record(eventBuildCompleted(pagesPaths, { durationInSeconds: webpackBuildDuration, totalAppPagesCount })); runTurbotrace = async function(staticPages) { if (!turbotraceContext) { return; } if (!(binding == null ? void 0 : binding.isWasm) && typeof binding.turbo.startTrace === "function") { var _config_experimental_turbotrace; let turbotraceOutputPath; let turbotraceFiles; turboTasksForTrace = binding.turbo.createTurboTasks((((_config_experimental_turbotrace = config.experimental.turbotrace) == null ? void 0 : _config_experimental_turbotrace.memoryLimit) ?? TURBO_TRACE_DEFAULT_MEMORY_LIMIT) * 1024 * 1024); const { entriesTrace , chunksTrace } = turbotraceContext; if (entriesTrace) { const { appDir: turbotraceContextAppDir , depModArray , entryNameMap , outputPath , action } = entriesTrace; const depModSet = new Set(depModArray); const filesTracedInEntries = await binding.turbo.startTrace(action, turboTasksForTrace); const { contextDirectory , input: entriesToTrace } = action; // only trace the assets under the appDir // exclude files from node_modules, entries and processed by webpack const filesTracedFromEntries = filesTracedInEntries.map((f)=>path.join(contextDirectory, f)).filter((f)=>!f.includes("/node_modules/") && f.startsWith(turbotraceContextAppDir) && !entriesToTrace.includes(f) && !depModSet.has(f)); if (filesTracedFromEntries.length) { // The turbo trace doesn't provide the traced file type and reason at present // let's write the traced files into the first [entry].nft.json const [[, entryName]] = Array.from(entryNameMap.entries()).filter(([k])=>k.startsWith(turbotraceContextAppDir)); const traceOutputPath = path.join(outputPath, `../${entryName}.js.nft.json`); const traceOutputDir = path.dirname(traceOutputPath); turbotraceOutputPath = traceOutputPath; turbotraceFiles = filesTracedFromEntries.map((file)=>path.relative(traceOutputDir, file)); } } if (chunksTrace) { const { action , outputPath } = chunksTrace; action.input = action.input.filter((f)=>{ const outputPagesPath = path.join(outputPath, "..", "pages"); return !f.startsWith(outputPagesPath) || !staticPages.has(// strip `outputPagesPath` and file ext from absolute f.substring(outputPagesPath.length, f.length - 3)); }); await binding.turbo.startTrace(action, turboTasksForTrace); if (turbotraceOutputPath && turbotraceFiles) { const existedNftFile = await fs.readFile(turbotraceOutputPath, "utf8").then((existedContent)=>JSON.parse(existedContent)).catch(()=>({ version: TRACE_OUTPUT_VERSION, files: [] })); existedNftFile.files.push(...turbotraceFiles); const filesSet = new Set(existedNftFile.files); existedNftFile.files = [ ...filesSet ]; await fs.writeFile(turbotraceOutputPath, JSON.stringify(existedNftFile), "utf8"); } } } }; } // For app directory, we run type checking after build. if (appDir && !(isCompile || isGenerate)) { await startTypeChecking(typeCheckingOptions); } const postCompileSpinner = createSpinner({ prefixText: `${Log.prefixes.info} Collecting page data` }); const buildManifestPath = path.join(distDir, BUILD_MANIFEST); const appBuildManifestPath = path.join(distDir, APP_BUILD_MANIFEST); let staticAppPagesCount = 0; let serverAppPagesCount = 0; let edgeRuntimeAppCount = 0; let edgeRuntimePagesCount = 0; const ssgPages = new Set(); const ssgStaticFallbackPages = new Set(); const ssgBlockingFallbackPages = new Set(); const staticPages = new Set(); 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 appNormalizedPaths = new Map(); const appDynamicParamPaths = new Set(); const appDefaultConfigs = new Map(); const pageInfos = new Map(); const pagesManifest = JSON.parse(await fs.readFile(manifestPath, "utf8")); const buildManifest = JSON.parse(await fs.readFile(buildManifestPath, "utf8")); const appBuildManifest = appDir ? JSON.parse(await fs.readFile(appBuildManifestPath, "utf8")) : undefined; const timeout = config.staticPageGenerationTimeout || 0; const sharedPool = config.experimental.sharedPool || false; const staticWorkerPath = sharedPool ? require.resolve("./worker") : require.resolve("./utils"); let appPathsManifest = {}; const appPathRoutes = {}; if (appDir) { appPathsManifest = JSON.parse(await fs.readFile(path.join(distDir, SERVER_DIRECTORY, APP_PATHS_MANIFEST), "utf8")); Object.keys(appPathsManifest).forEach((entry)=>{ appPathRoutes[entry] = normalizeAppPath(entry); }); await fs.writeFile(path.join(distDir, APP_PATH_ROUTES_MANIFEST), JSON.stringify(appPathRoutes, null, 2)); } process.env.NEXT_PHASE = PHASE_PRODUCTION_BUILD; const numWorkers = config.experimental.memoryBasedWorkersCount ? Math.max(config.experimental.cpus !== defaultConfig.experimental.cpus ? config.experimental.cpus : Math.min(config.experimental.cpus || 1, Math.floor(os.freemem() / 1e9)), // enforce a minimum of 4 workers 4) : config.experimental.cpus || 4; function createStaticWorker(type, ipcPort, ipcValidationKey) { let infoPrinted = false; return new Worker(staticWorkerPath, { timeout: timeout * 1000, onRestart: (method, [arg], attempts)=>{ if (method === "exportPage") { const { path: pagePath } = arg; if (attempts >= 3) { throw new Error(`Static page generation for ${pagePath} is still timing out after 3 attempts. See more info here https://nextjs.org/docs/messages/static-page-generation-timeout`); } Log.warn(`Restarted static page generation for ${pagePath} because it took more than ${timeout} seconds`); } else { const pagePath = arg; if (attempts >= 2) { throw new Error(`Collecting page data for ${pagePath} is still timing out after 2 attempts. See more info here https://nextjs.org/docs/messages/page-data-collection-timeout`); } Log.warn(`Restarted collecting page data for ${pagePath} because it took more than ${timeout} seconds`); } if (!infoPrinted) { Log.warn("See more info here https://nextjs.org/docs/messages/static-page-generation-timeout"); infoPrinted = true; } }, numWorkers, forkOptions: { env: { ...process.env, __NEXT_INCREMENTAL_CACHE_IPC_PORT: ipcPort + "", __NEXT_INCREMENTAL_CACHE_IPC_KEY: ipcValidationKey, __NEXT_PRIVATE_PREBUNDLED_REACT: type === "app" ? config.experimental.serverActions ? "experimental" : "next" : "" } }, enableWorkerThreads: config.experimental.workerThreads, exposedMethods: sharedPool ? [ "hasCustomGetInitialProps", "isPageStatic", "getDefinedNamedExports", "exportPage" ] : [ "hasCustomGetInitialProps", "isPageStatic", "getDefinedNamedExports" ] }); } let CacheHandler; if (incrementalCacheHandlerPath) { CacheHandler = require(path.isAbsolute(incrementalCacheHandlerPath) ? incrementalCacheHandlerPath : path.join(dir, incrementalCacheHandlerPath)); } const { ipcPort , ipcValidationKey } = await initialize({ fs: nodeFs, dev: false, appDir: isAppDirEnabled, fetchCache: isAppDirEnabled, flushToDisk: config.experimental.isrFlushToDisk, serverDistDir: path.join(distDir, "server"), fetchCacheKeyPrefix: config.experimental.fetchCacheKeyPrefix, maxMemoryCacheSize: config.experimental.isrMemoryCacheSize, getPrerenderManifest: ()=>({ version: -1, routes: {}, dynamicRoutes: {}, notFoundRoutes: [], preview: null }), requestHeaders: {}, CurCacheHandler: CacheHandler, minimalMode: ciEnvironment.hasNextSupport, allowedRevalidateHeaderKeys: config.experimental.allowedRevalidateHeaderKeys }); const pagesStaticWorkers = createStaticWorker("pages", ipcPort, ipcValidationKey); const appStaticWorkers = isAppDirEnabled ? createStaticWorker("app", ipcPort, ipcValidationKey) : undefined; const analysisBegin = process.hrtime(); const staticCheckSpan = nextBuildSpan.traceChild("static-check"); const functionsConfigManifest = {}; const { customAppGetInitialProps , namedExports , isNextImageImported , hasSsrAmpPages , hasNonStaticErrorPage } = await staticCheckSpan.traceAsyncFn(async ()=>{ if (isCompile) { return { customAppGetInitialProps: false, namedExports: [], isNextImageImported: true, hasSsrAmpPages: !!pagesDir, hasNonStaticErrorPage: true }; } const { configFileName , publicRuntimeConfig , serverRuntimeConfig } = config; const runtimeEnvConfig = { publicRuntimeConfig, serverRuntimeConfig }; const nonStaticErrorPageSpan = staticCheckSpan.traceChild("check-static-error-page"); const errorPageHasCustomGetInitialProps = nonStaticErrorPageSpan.traceAsyncFn(async ()=>hasCustomErrorPage && await pagesStaticWorkers.hasCustomGetInitialProps("/_error", distDir, runtimeEnvConfig, false)); const errorPageStaticResult = nonStaticErrorPageSpan.traceAsyncFn(async ()=>{ var _config_i18n, _config_i18n1; return hasCustomErrorPage && pagesStaticWorkers.isPageStatic({ page: "/_error", distDir, configFileName, runtimeEnvConfig, httpAgentOptions: config.httpAgentOptions, locales: (_config_i18n = config.i18n) == null ? void 0 : _config_i18n.locales, defaultLocale: (_config_i18n1 = config.i18n) == null ? void 0 : _config_i18n1.defaultLocale, nextConfigOutput: config.output }); }); const appPageToCheck = "/_app"; const customAppGetInitialPropsPromise = pagesStaticWorkers.hasCustomGetInitialProps(appPageToCheck, distDir, runtimeEnvConfig, true); const namedExportsPromise = pagesStaticWorkers.getDefinedNamedExports(appPageToCheck, distDir, runtimeEnvConfig); // eslint-disable-next-line @typescript-eslint/no-shadow let isNextImageImported; // eslint-disable-next-line @typescript-eslint/no-shadow let hasSsrAmpPages = false; const computedManifestData = await computeFromManifest({ build: buildManifest, app: appBuildManifest }, distDir, config.experimental.gzipSize); const middlewareManifest = require(path.join(distDir, SERVER_DIRECTORY, MIDDLEWARE_MANIFEST)); const actionManifest = appDir ? require(path.join(distDir, SERVER_DIRECTORY, SERVER_REFERENCE_MANIFEST + ".json")) : null; const entriesWithAction = actionManifest ? new Set() : null; if (actionManifest && entriesWithAction) { for(const id in actionManifest.node){ for(const entry in actionManifest.node[id].workers){ entriesWithAction.add(entry); } } for(const id in actionManifest.edge){ for(const entry in actionManifest.edge[id].workers){ entriesWithAction.add(entry); } } } for (const key of Object.keys(middlewareManifest == null ? void 0 : middlewareManifest.functions)){ if (key.startsWith("/api")) { edgeRuntimePagesCount++; } } await Promise.all(Object.entries(pageKeys).reduce((acc, [key, files])=>{ if (!files) { return acc; } const pageType = key; for (const page of files){ acc.push({ pageType, page }); } return acc; }, []).map(({ pageType , page })=>{ const checkPageSpan = staticCheckSpan.traceChild("check-page", { page }); return checkPageSpan.traceAsyncFn(async ()=>{ const actualPage = normalizePagePath(page); const [selfSize, allSize] = await getJsPageSizeInKb(pageType, actualPage, distDir, buildManifest, appBuildManifest, config.experimental.gzipSize, computedManifestData); let isSsg = false; let isStatic = false; let isServerComponent = false; let isHybridAmp = false; let ssgPageRoutes = null; let pagePath = ""; if (pageType === "pages") { pagePath = pagesPaths.find((p)=>{ p = normalizePathSep(p); return p.startsWith(actualPage + ".") || p.startsWith(actualPage + "/index."); }) || ""; } let originalAppPath; if (pageType === "app" && mappedAppPages) { for (const [originalPath, normalizedPath] of Object.entries(appPathRoutes)){ if (normalizedPath === page) { pagePath = mappedAppPages[originalPath].replace(/^private-next-app-dir/, ""); originalAppPath = originalPath; break; } } } const staticInfo = pagePath ? await getPageStaticInfo({ pageFilePath: path.join((pageType === "pages" ? pagesDir : appDir) || "", pagePath), nextConfig: config, pageType }) : undefined; if (staticInfo == null ? void 0 : staticInfo.extraConfig) { functionsConfigManifest[page] = staticInfo.extraConfig; } const pageRuntime = middlewareManifest.functions[originalAppPath || page] ? "edge" : staticInfo == null ? void 0 : staticInfo.runtime; if (!isCompile) { isServerComponent = pageType === "app" && (staticInfo == null ? void 0 : staticInfo.rsc) !== RSC_MODULE_TYPES.client; if (pageType === "app" || !isReservedPage(page)) { try { let edgeInfo; if (isEdgeRuntime(pageRuntime)) { if (pageType === "app") {