UNPKG

next

Version:

The React Framework

881 lines (880 loc) • 158 kB
import '../lib/setup-exception-listeners'; import { loadEnvConfig } from '@next/env'; import { bold, yellow } from '../lib/picocolors'; import { makeRe } from 'next/dist/compiled/picomatch'; import { existsSync, promises as fs } from 'fs'; import os from 'os'; import { Worker } from '../lib/worker'; import { defaultConfig, getNextConfigRuntime } 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 path from 'path'; import { STATIC_STATUS_PAGE_GET_INITIAL_PROPS_ERROR, PUBLIC_DIR_MIDDLEWARE_CONFLICT, MIDDLEWARE_FILENAME, PROXY_FILENAME, PAGES_DIR_ALIAS, INSTRUMENTATION_HOOK_FILENAME, RSC_SUFFIX, PRERENDER_REVALIDATE_HEADER, PRERENDER_REVALIDATE_ONLY_GENERATED_HEADER, NEXT_CACHE_REVALIDATE_TAG_TOKEN_HEADER, NEXT_CACHE_REVALIDATED_TAGS_HEADER, MATCHED_PATH_HEADER } 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 { nonNullable } from '../lib/non-nullable'; import { recursiveDeleteSyncWithAsyncRetries } 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, 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, RSC_MODULE_TYPES, NEXT_FONT_MANIFEST, SUBRESOURCE_INTEGRITY_MANIFEST, MIDDLEWARE_BUILD_MANIFEST, MIDDLEWARE_REACT_LOADABLE_MANIFEST, SERVER_REFERENCE_MANIFEST, FUNCTIONS_CONFIG_MANIFEST, DYNAMIC_CSS_MANIFEST, TURBOPACK_CLIENT_MIDDLEWARE_MANIFEST } from '../shared/lib/constants'; import { UNDERSCORE_NOT_FOUND_ROUTE, UNDERSCORE_NOT_FOUND_ROUTE_ENTRY, UNDERSCORE_GLOBAL_ERROR_ROUTE_ENTRY } from '../shared/lib/entry-constants'; import { isDynamicRoute } from '../shared/lib/router/utils'; import { Bundler, finalizeBundlerFromConfig } from '../lib/bundler'; import loadConfig from '../server/config'; import { normalizePagePath } from '../shared/lib/page-path/normalize-page-path'; import { getPagePath } from '../server/require'; import * as ciEnvironment from '../server/ci-info'; import { turborepoTraceAccess, TurborepoAccessTraceResult, writeTurborepoAccessTraceResult } from './turborepo-access-trace'; import { eventBuildOptimize, eventCliSession, eventBuildFeatureUsage, eventNextPlugins, EVENT_BUILD_FEATURE_USAGE, eventPackageUsedInGetServerSideProps, eventBuildCompleted, eventBuildFailed } from '../telemetry/events'; import { Telemetry } from '../telemetry/storage'; import { createPagesMapping, collectAppFiles, processPageRoutes, processAppRoutes, processLayoutRoutes, extractSlotsFromAppRoutes, extractSlotsFromDefaultFiles, combineSlots, collectPagesFiles } from './entries'; import { sortByPageExts } from './sort-by-page-exts'; import { getStaticInfoIncludingLayouts } from './get-static-info-including-layouts'; import { PAGE_TYPES } from '../lib/page-types'; 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, printCustomRoutes, printTreeView, copyTracedFiles, isReservedPage, isAppBuiltinPage, collectRoutesUsingEdgeRuntime, collectMeta, isProxyFile, pageToRoute } 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 { lockfilePatchPromise, teardownTraceSubscriber } from './swc'; import { installBindings } from './swc/install-bindings'; import { getNamedRouteRegex } from '../shared/lib/router/utils/route-regex'; import { getFilesInDir } from '../lib/get-files-in-dir'; import { eventSwcPlugins } from '../telemetry/events/swc-plugins'; import { normalizeAppPath } from '../shared/lib/router/utils/app-paths'; import { ACTION_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 { collectBuildTraces } from './collect-build-traces'; import { formatManifest } from './manifests/formatter/format-manifest'; import { recordFrameworkVersion, updateBuildDiagnostics, recordFetchMetrics } from '../diagnostics/build-diagnostics'; import { getStartServerInfo, logStartInfo } from '../server/lib/app-info-log'; import { hasCustomExportOutput } from '../export/utils'; import { traceMemoryUsage } from '../lib/memory/trace'; import { generateEncryptionKeyBase64 } from '../server/app-render/encryption-utils-server'; import uploadTrace from '../trace/upload-trace'; import { checkIsAppPPREnabled, checkIsRoutePPREnabled } from '../server/lib/experimental/ppr'; import { FallbackMode, fallbackModeToFallbackField } from '../lib/fallback'; import { RenderingMode } from './rendering-mode'; import { InvariantError } from '../shared/lib/invariant-error'; import { HTML_LIMITED_BOT_UA_RE_STRING } from '../shared/lib/router/utils/is-bot'; import { turbopackBuild } from './turbopack-build'; import { isFileSystemCacheEnabledForBuild } from '../shared/lib/turbopack/utils'; import { inlineStaticEnv } from '../lib/inline-static-env'; import { populateStaticEnv } from '../lib/static-env'; import { durationToString, hrtimeDurationToString } from './duration-to-string'; import { traceGlobals } from '../trace/shared'; import { extractNextErrorCode } from '../lib/error-telemetry-utils'; import { runAfterProductionCompile } from './after-production-compile'; import { generatePreviewKeys } from './preview-key-utils'; import { handleBuildComplete } from './adapter/build-complete'; import { sortPageObjects, sortPages, sortSortableRouteObjects } from '../shared/lib/router/utils/sortable-routes'; import { cp, mkdir, writeFile } from 'fs/promises'; import { createRouteTypesManifest, writeRouteTypesManifest, writeValidatorFile } from '../server/lib/router-utils/route-types-utils'; import { Lockfile } from './lockfile'; import { buildPrefetchSegmentDataRoute } from '../server/lib/router-utils/build-prefetch-segment-data-route'; import { generateRoutesManifest } from './generate-routes-manifest'; import { validateAppPaths } from './validate-app-paths'; /** * The headers that are allowed to be used when revalidating routes. Currently * this includes both headers used by the pages and app routers. */ const ALLOWED_HEADERS = [ 'host', MATCHED_PATH_HEADER, PRERENDER_REVALIDATE_HEADER, PRERENDER_REVALIDATE_ONLY_GENERATED_HEADER, NEXT_CACHE_REVALIDATED_TAGS_HEADER, NEXT_CACHE_REVALIDATE_TAG_TOKEN_HEADER ]; function getCacheDir(distDir) { const cacheDir = path.join(distDir, 'cache'); if (ciEnvironment.isCI && !ciEnvironment.hasNextSupport) { const hasCache = existsSync(cacheDir); if (!hasCache) { // Intentionally not piping to stderr which is what `Log.warn` does 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`); } } return cacheDir; } async function writeFileUtf8(filePath, content) { await fs.writeFile(filePath, content, 'utf-8'); } function readFileUtf8(filePath) { return fs.readFile(filePath, 'utf8'); } async function writeManifest(filePath, manifest) { await writeFileUtf8(filePath, formatManifest(manifest)); } async function readManifest(filePath) { return JSON.parse(await readFileUtf8(filePath)); } async function writePrerenderManifest(distDir, manifest) { await writeManifest(path.join(distDir, PRERENDER_MANIFEST), manifest); } async function writeClientSsgManifest(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 writeFileUtf8(path.join(distDir, CLIENT_STATIC_FILES_PATH, buildId, '_ssgManifest.js'), clientSsgManifestContent); } async function writeFunctionsConfigManifest(distDir, manifest) { let sortedManifest = { version: manifest.version, functions: Object.fromEntries(Object.entries(manifest.functions).sort(([a], [b])=>a.localeCompare(b))) }; await writeManifest(path.join(distDir, SERVER_DIRECTORY, FUNCTIONS_CONFIG_MANIFEST), sortedManifest); } async function writeRequiredServerFilesManifest(distDir, requiredServerFiles) { await writeManifest(path.join(distDir, SERVER_FILES_MANIFEST + '.json'), requiredServerFiles); await writeFileUtf8(path.join(distDir, SERVER_FILES_MANIFEST + '.js'), `self.__SERVER_FILES_MANIFEST=${formatManifest(requiredServerFiles)}`); } async function writeImagesManifest(distDir, config) { var _config_images, _config_images1; const images = { ...config.images }; const { deviceSizes, imageSizes } = images; images.sizes = [ ...deviceSizes, ...imageSizes ]; // By default, remotePatterns will allow no remote images ([]) images.remotePatterns = ((config == null ? void 0 : (_config_images = config.images) == null ? void 0 : _config_images.remotePatterns) || []).map((p)=>{ var _p_protocol; return { // Modifying the manifest should also modify matchRemotePattern() protocol: (_p_protocol = p.protocol) == null ? void 0 : _p_protocol.replace(/:$/, ''), hostname: makeRe(p.hostname).source, port: p.port, pathname: makeRe(p.pathname ?? '**', { dot: true }).source, search: p.search }; }); // By default, localPatterns will allow all local images (undefined) if (config == null ? void 0 : (_config_images1 = config.images) == null ? void 0 : _config_images1.localPatterns) { images.localPatterns = config.images.localPatterns.map((p)=>({ // Modifying the manifest should also modify matchLocalPattern() pathname: makeRe(p.pathname ?? '**', { dot: true }).source, search: p.search })); } await writeManifest(path.join(distDir, IMAGES_MANIFEST), { version: 1, images }); } const STANDALONE_DIRECTORY = 'standalone'; async function writeStandaloneDirectory(nextBuildSpan, distDir, pageKeys, denormalizedAppPages, outputFileTracingRoot, requiredServerFiles, middlewareManifest, hasNodeMiddleware, hasInstrumentationHook, staticPages, loadedEnvFiles, appDir) { await nextBuildSpan.traceChild('write-standalone-directory').traceAsyncFn(async ()=>{ await copyTracedFiles(// requiredServerFiles.appDir Refers to the application directory, not App Router. requiredServerFiles.appDir, distDir, pageKeys.pages, denormalizedAppPages, outputFileTracingRoot, requiredServerFiles.config, middlewareManifest, hasNodeMiddleware, hasInstrumentationHook, staticPages); for (const file of [ ...requiredServerFiles.files, path.join(requiredServerFiles.config.distDir, SERVER_FILES_MANIFEST + '.json'), ...loadedEnvFiles.reduce((acc, envFile)=>{ if ([ '.env', '.env.production' ].includes(envFile.path)) { acc.push(envFile.path); } return acc; }, []) ]){ // requiredServerFiles.appDir Refers to the application directory, not App Router. const filePath = path.join(requiredServerFiles.appDir, file); const outputPath = path.join(distDir, STANDALONE_DIRECTORY, path.relative(outputFileTracingRoot, filePath)); await fs.mkdir(path.dirname(outputPath), { recursive: true }); await fs.copyFile(filePath, outputPath); } if (hasNodeMiddleware) { const middlewareOutput = path.join(distDir, STANDALONE_DIRECTORY, path.relative(outputFileTracingRoot, distDir), SERVER_DIRECTORY, 'middleware.js'); await fs.mkdir(path.dirname(middlewareOutput), { recursive: true }); await fs.copyFile(path.join(distDir, SERVER_DIRECTORY, 'middleware.js'), middlewareOutput); } const originalPagesDir = path.join(distDir, SERVER_DIRECTORY, 'pages'); if (existsSync(originalPagesDir)) { await recursiveCopy(originalPagesDir, path.join(distDir, STANDALONE_DIRECTORY, path.relative(outputFileTracingRoot, distDir), SERVER_DIRECTORY, 'pages'), { overwrite: true }); } if (appDir) { const originalServerApp = path.join(distDir, SERVER_DIRECTORY, 'app'); if (existsSync(originalServerApp)) { await recursiveCopy(originalServerApp, path.join(distDir, STANDALONE_DIRECTORY, path.relative(outputFileTracingRoot, distDir), SERVER_DIRECTORY, 'app'), { overwrite: true }); } } }); } function getNumberOfWorkers(config) { if (config.experimental.cpus && config.experimental.cpus !== defaultConfig.experimental.cpus) { return config.experimental.cpus; } if (config.experimental.memoryBasedWorkersCount) { return Math.max(Math.min(config.experimental.cpus || 1, Math.floor(os.freemem() / 1e9)), // enforce a minimum of 4 workers 4); } if (config.experimental.cpus) { return config.experimental.cpus; } // Fall back to 4 workers if a count is not specified return 4; } const staticWorkerPath = require.resolve('./worker'); const staticWorkerExposedMethods = [ 'hasCustomGetInitialProps', 'isPageStatic', 'getDefinedNamedExports', 'exportPages' ]; export function createStaticWorker(config, options) { const { numberOfWorkers, debuggerPortOffset, progress } = options; return new Worker(staticWorkerPath, { logger: Log, numWorkers: numberOfWorkers, onActivity: ()=>{ progress == null ? void 0 : progress.run(); }, onActivityAbort: ()=>{ progress == null ? void 0 : progress.clear(); }, debuggerPortOffset, enableSourceMaps: config.enablePrerenderSourceMaps, // remove --max-old-space-size flag as it can cause memory issues. isolatedMemory: true, enableWorkerThreads: config.experimental.workerThreads, exposedMethods: staticWorkerExposedMethods }); } async function writeFullyStaticExport(config, dir, enabledDirectories, configOutDir, nextBuildSpan, appDirOnly) { const exportApp = require('../export').default; await exportApp(dir, { buildExport: false, nextConfig: config, enabledDirectories, silent: true, outdir: path.join(dir, configOutDir), numWorkers: getNumberOfWorkers(config), appDirOnly }, nextBuildSpan); } async function getBuildId(isGenerateMode, distDir, nextBuildSpan, config) { if (isGenerateMode) { return await fs.readFile(path.join(distDir, 'BUILD_ID'), 'utf8'); } return await nextBuildSpan.traceChild('generate-buildid').traceAsyncFn(()=>generateBuildId(config.generateBuildId, nanoid)); } export default async function build(dir, experimentalAnalyze = false, reactProductionProfiling = false, debugOutput = false, debugPrerender = false, noMangling = false, appDirOnly = false, bundler = Bundler.Turbopack, experimentalBuildMode, traceUploadUrl, debugBuildAppPaths, debugBuildPagePaths) { const isCompileMode = experimentalBuildMode === 'compile'; const isGenerateMode = experimentalBuildMode === 'generate'; NextBuildContext.isCompileMode = isCompileMode; NextBuildContext.analyze = experimentalAnalyze; const buildStartTime = Date.now(); let appType; let loadedConfig; let staticWorker; try { const nextBuildSpan = trace('next-build', undefined, { buildMode: experimentalBuildMode, version: "16.1.1" }); NextBuildContext.nextBuildSpan = nextBuildSpan; NextBuildContext.dir = dir; NextBuildContext.appDirOnly = appDirOnly; NextBuildContext.reactProductionProfiling = reactProductionProfiling; NextBuildContext.noMangling = noMangling; NextBuildContext.debugPrerender = debugPrerender; await nextBuildSpan.traceAsyncFn(async ()=>{ var _config_experimental, _mappedPages_404, _mappedPages__error; // 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 turborepoAccessTraceResult = new TurborepoAccessTraceResult(); const config = await nextBuildSpan.traceChild('load-next-config').traceAsyncFn(()=>turborepoTraceAccess(()=>loadConfig(PHASE_PRODUCTION_BUILD, dir, { // Log for next.config loading process silent: false, reactProductionProfiling, debugPrerender }), turborepoAccessTraceResult)); loadedConfig = config; // Reading the config can modify environment variables that influence the bundler selection. bundler = finalizeBundlerFromConfig(bundler); nextBuildSpan.setAttribute('bundler', getBundlerForTelemetry(bundler)); // Install the native bindings early so we can have synchronous access later. await installBindings((_config_experimental = config.experimental) == null ? void 0 : _config_experimental.useWasmBinary); process.env.NEXT_DEPLOYMENT_ID = config.deploymentId || ''; NextBuildContext.config = config; let configOutDir = 'out'; if (hasCustomExportOutput(config)) { configOutDir = config.distDir; config.distDir = '.next'; } const distDir = path.join(dir, config.distDir); NextBuildContext.distDir = distDir; setGlobal('phase', PHASE_PRODUCTION_BUILD); setGlobal('distDir', distDir); const buildId = await getBuildId(isGenerateMode, distDir, nextBuildSpan, config); NextBuildContext.buildId = buildId; if (experimentalBuildMode === 'generate-env') { if (bundler === Bundler.Turbopack) { Log.warn('generate-env is not needed with turbopack'); process.exit(0); } Log.info('Inlining static env ...'); await nextBuildSpan.traceChild('inline-static-env').traceAsyncFn(async ()=>{ await inlineStaticEnv({ distDir, config }); }); Log.info('Complete'); await flushAllTraces(); teardownTraceSubscriber(); process.exit(0); } // when using compile mode static env isn't inlined so we // need to populate in normal runtime env if (isCompileMode || isGenerateMode) { populateStaticEnv(config, config.deploymentId); } const customRoutes = await nextBuildSpan.traceChild('load-custom-routes').traceAsyncFn(()=>loadCustomRoutes(config)); const { headers, rewrites, redirects } = customRoutes; const combinedRewrites = [ ...rewrites.beforeFiles, ...rewrites.afterFiles, ...rewrites.fallback ]; const hasRewrites = combinedRewrites.length > 0; NextBuildContext.hasRewrites = hasRewrites; NextBuildContext.originalRewrites = config._originalRewrites; NextBuildContext.originalRedirects = config._originalRedirects; 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 Object.defineProperty(new Error('> Build directory is not writeable. https://nextjs.org/docs/messages/build-dir-not-writeable'), "__NEXT_ERROR_CODE", { value: "E202", enumerable: false, configurable: true }); } if (config.experimental.lockDistDir) { // This leaks the lock file descriptor. That's okay, it'll be cleaned up by the OS upon // process exit. await Lockfile.acquireWithRetriesOrExit(path.join(distDir, 'lock'), 'next build'); } if (config.cleanDistDir && !isGenerateMode) { await nextBuildSpan.traceChild('clean').traceAsyncFn(()=>recursiveDeleteSyncWithAsyncRetries(distDir, /^(cache|dev|lock)/)); } const cacheDir = getCacheDir(distDir); const telemetry = new Telemetry({ distDir }); setGlobal('telemetry', telemetry); const publicDir = path.join(dir, 'public'); const { pagesDir, appDir } = findPagesDir(dir); if (pagesDir && appDir) { appType = 'hybrid'; } else if (pagesDir) { appType = 'pages'; } else if (appDir) { appType = 'app'; } if (!appDirOnly && !pagesDir) { appDirOnly = true; } NextBuildContext.pagesDir = pagesDir; NextBuildContext.appDir = appDir; const enabledDirectories = { app: typeof appDir === 'string', pages: typeof pagesDir === 'string' }; // Generate a random encryption key for this build. // This key is used to encrypt cross boundary values and can be used to generate hashes. const encryptionKey = await generateEncryptionKeyBase64({ isBuild: true, distDir }); NextBuildContext.encryptionKey = encryptionKey; const isSrcDir = path.relative(dir, pagesDir || appDir || '').startsWith('src'); const hasPublicDir = existsSync(publicDir); telemetry.record(eventCliSession(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)); // Always log next version first then start rest jobs const { envInfo, experimentalFeatures, cacheComponents } = await getStartServerInfo({ dir, dev: false, debugPrerender }); logStartInfo({ networkUrl: null, appUrl: null, envInfo, experimentalFeatures, logBundler: true, cacheComponents }); const typeCheckingOptions = { dir, appDir, pagesDir, telemetry, nextBuildSpan, config, cacheDir, debugBuildPaths: debugBuildAppPaths !== undefined || debugBuildPagePaths !== undefined ? { app: debugBuildAppPaths, pages: debugBuildPagePaths } : undefined }; if (appDir && 'exportPathMap' in config) { const errorMessage = 'The "exportPathMap" configuration cannot be used with the "app" directory. Please use generateStaticParams() instead.'; Log.error(errorMessage); await telemetry.flush(); throw Object.defineProperty(new Error(errorMessage), "__NEXT_ERROR_CODE", { value: "E394", enumerable: false, configurable: true }); } const validFileMatcher = createValidFileMatcher(config.pageExtensions, appDir); const providedPagePaths = JSON.parse(process.env.NEXT_PRIVATE_PAGE_PATHS || '[]'); let pagesPaths = Boolean(process.env.NEXT_PRIVATE_PAGE_PATHS) ? providedPagePaths : !appDirOnly && pagesDir ? await nextBuildSpan.traceChild('collect-pages').traceAsyncFn(()=>collectPagesFiles(pagesDir, validFileMatcher)) : []; // Apply debug build paths filter if specified // If debugBuildPagePaths is defined (even if empty), only build specified pages if (debugBuildPagePaths !== undefined) { if (debugBuildPagePaths.length > 0) { const debugPathsSet = new Set(debugBuildPagePaths); pagesPaths = pagesPaths.filter((pagePath)=>debugPathsSet.has(pagePath)); } else { // Empty array means build no pages pagesPaths = []; } } const middlewareDetectionRegExp = new RegExp(`^${MIDDLEWARE_FILENAME}\\.(?:${config.pageExtensions.join('|')})$`); const proxyDetectionRegExp = new RegExp(`^${PROXY_FILENAME}\\.(?:${config.pageExtensions.join('|')})$`); const instrumentationHookDetectionRegExp = new RegExp(`^${INSTRUMENTATION_HOOK_FILENAME}\\.(?:${config.pageExtensions.join('|')})$`); const rootDir = path.join(pagesDir || appDir, '..'); const includes = [ middlewareDetectionRegExp, proxyDetectionRegExp, instrumentationHookDetectionRegExp ]; const rootPaths = Array.from(await getFilesInDir(rootDir)).filter((file)=>includes.some((include)=>include.test(file))).sort(sortByPageExts(config.pageExtensions)).map((file)=>path.join(rootDir, file).replace(dir, '')); let instrumentationHookFilePath; let proxyFilePath; let middlewareFilePath; for (const rootPath of rootPaths){ const { name: fileBaseName, dir: fileDir } = path.parse(rootPath); const normalizedFileDir = normalizePathSep(fileDir); const isAtConventionLevel = normalizedFileDir === '/' || normalizedFileDir === '/src'; if (isAtConventionLevel && fileBaseName === MIDDLEWARE_FILENAME) { middlewareFilePath = rootPath; } if (isAtConventionLevel && fileBaseName === PROXY_FILENAME) { proxyFilePath = rootPath; } if (isAtConventionLevel && fileBaseName === INSTRUMENTATION_HOOK_FILENAME) { instrumentationHookFilePath = rootPath; } } if (middlewareFilePath) { if (proxyFilePath) { const cwd = process.cwd(); const absoluteProxyPath = path.join(rootDir, proxyFilePath); const absoluteMiddlewarePath = path.join(rootDir, middlewareFilePath); throw Object.defineProperty(new Error(`Both ${MIDDLEWARE_FILENAME} file "./${path.relative(cwd, absoluteMiddlewarePath)}" and ${PROXY_FILENAME} file "./${path.relative(cwd, absoluteProxyPath)}" are detected. Please use "./${path.relative(cwd, absoluteProxyPath)}" only. Learn more: https://nextjs.org/docs/messages/middleware-to-proxy`), "__NEXT_ERROR_CODE", { value: "E900", enumerable: false, configurable: true }); } Log.warnOnce(`The "${MIDDLEWARE_FILENAME}" file convention is deprecated. Please use "${PROXY_FILENAME}" instead. Learn more: https://nextjs.org/docs/messages/middleware-to-proxy`); } const hasInstrumentationHook = Boolean(instrumentationHookFilePath); const hasMiddlewareFile = Boolean(middlewareFilePath); const hasProxyFile = Boolean(proxyFilePath); NextBuildContext.hasInstrumentationHook = hasInstrumentationHook; const previewProps = await generatePreviewKeys({ isBuild: true, distDir }); NextBuildContext.previewProps = previewProps; const mappedPages = await nextBuildSpan.traceChild('create-pages-mapping').traceAsyncFn(()=>createPagesMapping({ isDev: false, pageExtensions: config.pageExtensions, pagesType: PAGE_TYPES.PAGES, pagePaths: pagesPaths, pagesDir, appDir, appDirOnly })); NextBuildContext.mappedPages = mappedPages; // Update appDirOnly if no user pages routes are found if (Object.keys(mappedPages).length === 0 && !appDirOnly) { NextBuildContext.appDirOnly = appDirOnly = true; } let mappedAppPages; let mappedAppLayouts; let denormalizedAppPages; if (appDir) { const providedAppPaths = JSON.parse(process.env.NEXT_PRIVATE_APP_PATHS || '[]'); let appPaths; let layoutPaths; if (Boolean(process.env.NEXT_PRIVATE_APP_PATHS)) { // used for testing appPaths = providedAppPaths; layoutPaths = []; } else { // Collect app pages, layouts, and default files in a single directory traversal const result = await nextBuildSpan.traceChild('collect-app-files').traceAsyncFn(()=>collectAppFiles(appDir, validFileMatcher)); appPaths = result.appPaths; layoutPaths = result.layoutPaths; // Apply debug build paths filter if specified // If debugBuildAppPaths is defined (even if empty), only build specified app paths if (debugBuildAppPaths !== undefined) { if (debugBuildAppPaths.length > 0) { const debugPathsSet = new Set(debugBuildAppPaths); appPaths = appPaths.filter((appPath)=>debugPathsSet.has(appPath)); } else { // Empty array means build no app paths appPaths = []; } } // Note: defaultPaths are not used in the build process, only for slot detection in generating route types } mappedAppPages = await nextBuildSpan.traceChild('create-app-mapping').traceAsyncFn(()=>createPagesMapping({ pagePaths: appPaths, isDev: false, pagesType: PAGE_TYPES.APP, pageExtensions: config.pageExtensions, pagesDir, appDir, appDirOnly })); mappedAppLayouts = await nextBuildSpan.traceChild('create-app-layouts').traceAsyncFn(()=>createPagesMapping({ pagePaths: layoutPaths, isDev: false, pagesType: PAGE_TYPES.APP, pageExtensions: config.pageExtensions, pagesDir, appDir, appDirOnly })); NextBuildContext.mappedAppPages = mappedAppPages; } const mappedRootPaths = await createPagesMapping({ isDev: false, pageExtensions: config.pageExtensions, pagePaths: rootPaths, pagesType: PAGE_TYPES.ROOT, pagesDir: pagesDir, appDir, appDirOnly }); NextBuildContext.mappedRootPaths = mappedRootPaths; const pagesPageKeys = Object.keys(mappedPages); const conflictingAppPagePaths = []; const appPageKeys = new Set(); 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.add(normalizedAppPageKey); } } const appPaths = Array.from(appPageKeys); // Validate that the app paths are valid. This is currently duplicating // the logic from packages/next/src/shared/lib/router/utils/sorted-routes.ts // but is instead specifically focused on code that can be shared // eventually with the development code. validateAppPaths(appPaths); // Interception routes are modelled as beforeFiles rewrites rewrites.beforeFiles.push(...generateInterceptionRoutesRewrites(appPaths, config.basePath)); NextBuildContext.rewrites = rewrites; const totalAppPagesCount = appPaths.length; const pageKeys = { pages: pagesPageKeys, app: appPaths.length > 0 ? appPaths : undefined }; await nextBuildSpan.traceChild('generate-route-types').traceAsyncFn(async ()=>{ const routeTypesFilePath = path.join(distDir, 'types', 'routes.d.ts'); const validatorFilePath = path.join(distDir, 'types', 'validator.ts'); await mkdir(path.dirname(routeTypesFilePath), { recursive: true }); let appRoutes = []; let appRouteHandlers = []; let layoutRoutes = []; let slots = []; const { pageRoutes, pageApiRoutes } = processPageRoutes(mappedPages, dir, isSrcDir); // Build app routes if (appDir && mappedAppPages) { // Extract slots from both pages and default files const slotsFromPages = extractSlotsFromAppRoutes(mappedAppPages); let slotsFromDefaults = []; // Collect and map default files for slot extraction const { defaultPaths } = await nextBuildSpan.traceChild('collect-default-files').traceAsyncFn(()=>collectAppFiles(appDir, validFileMatcher)); if (defaultPaths.length > 0) { const mappedDefaultFiles = await nextBuildSpan.traceChild('create-default-mapping').traceAsyncFn(()=>createPagesMapping({ pagePaths: defaultPaths, isDev: false, pagesType: PAGE_TYPES.APP, pageExtensions: config.pageExtensions, pagesDir, appDir, appDirOnly })); slotsFromDefaults = extractSlotsFromDefaultFiles(mappedDefaultFiles); } // Combine slots and deduplicate using Set slots = combineSlots(slotsFromPages, slotsFromDefaults); const result = processAppRoutes(mappedAppPages, validFileMatcher, dir, isSrcDir); appRoutes = result.appRoutes; appRouteHandlers = result.appRouteHandlers; } // Build app layouts if (appDir && mappedAppLayouts) { layoutRoutes = processLayoutRoutes(mappedAppLayouts, dir, isSrcDir); } const routeTypesManifest = await createRouteTypesManifest({ dir, pageRoutes, appRoutes, appRouteHandlers, pageApiRoutes, layoutRoutes, slots, redirects: config.redirects, rewrites: config.rewrites, validatorFilePath }); await writeRouteTypesManifest(routeTypesManifest, routeTypesFilePath, config); await writeValidatorFile(routeTypesManifest, validatorFilePath); }); // Turbopack already handles conflicting app and page routes. if (bundler !== Bundler.Turbopack) { const numConflictingAppPaths = conflictingAppPagePaths.length; if (mappedAppPages && numConflictingAppPaths > 0) { const errorMessage = `Conflicting app and page file${numConflictingAppPaths === 1 ? ' was' : 's were'} found, please remove the conflicting files to continue:`; Log.error(errorMessage); for (const [pagePath, appPath] of conflictingAppPagePaths){ Log.error(` "${pagePath}" - "${appPath}"`); } await telemetry.flush(); throw Object.defineProperty(new Error(errorMessage), "__NEXT_ERROR_CODE", { value: "E394", enumerable: false, configurable: true }); } } const conflictingPublicFiles = []; const hasPages404 = (_mappedPages_404 = mappedPages['/404']) == null ? void 0 : _mappedPages_404.startsWith(PAGES_DIR_ALIAS); const hasApp404 = !!(mappedAppPages == null ? void 0 : mappedAppPages[UNDERSCORE_NOT_FOUND_ROUTE_ENTRY]); const hasAppGlobalError = !!(mappedAppPages == null ? void 0 : mappedAppPages[UNDERSCORE_GLOBAL_ERROR_ROUTE_ENTRY]); const hasCustomErrorPage = (_mappedPages__error = mappedPages['/_error']) == null ? void 0 : _mappedPages__error.startsWith(PAGES_DIR_ALIAS); // Check if there are any user pages (non-reserved pages) in the pages router const hasUserPagesRoutes = Object.keys(mappedPages).some((route)=>!isReservedPage(route)); if (hasPublicDir) { const hasPublicUnderScoreNextDir = existsSync(path.join(publicDir, '_next')); if (hasPublicUnderScoreNextDir) { throw Object.defineProperty(new Error(PUBLIC_DIR_MIDDLEWARE_CONFLICT), "__NEXT_ERROR_CODE", { value: "E394", enumerable: false, configurable: true }); } } 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 Object.defineProperty(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')}`), "__NEXT_ERROR_CODE", { value: "E270", enumerable: false, configurable: true }); } }); 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 isAppCacheComponentsEnabled = Boolean(config.cacheComponents); const isAuthInterruptsEnabled = Boolean(config.experimental.authInterrupts); const isAppPPREnabled = checkIsAppPPREnabled(config.experimental.ppr); const routesManifestPath = path.join(distDir, ROUTES_MANIFEST); // Generate the routes manifest using the extracted helper const { routesManifest, dynamicRoutes, sourcePages } = nextBuildSpan.traceChild('generate-routes-manifest').traceFn(()=>generateRoutesManifest({ appType, pageKeys, config, redirects, headers, rewrites, restrictedRedirectPaths, isAppPPREnabled })); // For pages directory, we run type checking after route collection but before build. if (!appDir && !isCompileMode) { await startTypeChecking(typeCheckingOptions); } let clientRouterFilters; if (config.experimental.clientRouterFilter) { const nonInternalRedirects = (config._originalRedirects || []).filter((r)=>!r.internal); clientRouterFilters = createClientRouterFilter([ ...appPaths ], config.experimental.clientRouterFilterRedirects ? nonInternalRedirects : [], config.experimental.clientRouterFilterAllowedRate); NextBuildContext.clientRouterFilters = clientRouterFilters; } // Ensure commonjs handling is used for files in the distDir (generally .next) // Files outside of the distDir can be "type": "module" await writeFileUtf8(path.join(distDir, 'package.json'), '{"type": "commonjs"}'); // These are written to distDir, so they need to come after creating and cleaning distDr. await recordFrameworkVersion("16.1.1"); await updateBuildDiagnostics({ buildStage: 'start' }); const outputFileTracingRoot = config.outputFileTracingRoot || dir; const pagesManifestPath = path.join(distDir, SERVER_DIRECTORY, PAGES_MANIFEST); let buildTraceContext; let buildTracesPromise = undefined; // If there's has a custom webpack config and disable the build worker. // Otherwise respect the option if it's set. const useBuildWorker = config.experimental.webpackBuildWorker || config.experimental.webpackBuildWorker === undefined && !config.webpack; const runServerAndEdgeInParallel = config.experimental.parallelServerCompiles; const collectServerBuildTracesInParallel = config.experimental.parallelServerBuildTraces || config.experimental.parallelServerBuildTraces === undefined && isCompileMode; nextBuildSpan.setAttribute('has-custom-webpack-config', String(!!config.webpack)); nextBuildSpan.setAttribute('use-build-worker', String(useBuildWorker)); if (!useBuildWorker && (runServerAndEdgeInParallel || collectServerBuildTracesInParallel)) { throw Object.defineProperty(new Error('The "parallelServerBuildTraces" and "parallelServerCompiles" options may only be used when build workers can be used. Read more: https://nextjs.org/docs/messages/parallel-build-without-worker'), "__NEXT_ERROR_CODE", { value: "E101", enumerable: false, configurable: true }); } Log.info('Creating an optimized production build ...'); traceMemoryUsage('Starting build', nextBuildSpan); await updateBuildDiagnostics({ buildStage: 'compile', buildOptions: { useBuildWorker: String(useBuildWorker) } }); let shutdownPromise = Promise.resolve(); if (!isGenerateMode) { if (bundler === Bundler.Turbopack) { const { duration: compilerDuration, shutdownPromise: p, ...rest } = await turbopackBuild(process.env.NEXT_TURBOPACK_USE_WORKER === undefined || process.env.NEXT_TURBOPACK_USE_WORKER !== '0'); shutdownPromise = p; traceMemoryUsage('Finished build', nextBuildSpan); buildTraceContext = rest.buildTraceContext; const durationString = durationToString(compilerDuration); Log.event(`Compiled successfully in ${durationString}`); telemetry.record(eventBuildCompleted(pagesPaths, { bundler: 'turbopack', durationInSeconds: Math.round(compilerDuration), totalAppPagesCount })); } else { if (runServerAndEdgeInParallel || collectServerBuildTracesInParallel) { let durationInSeconds = 0; await updateBuildDiagnostics({ buildStage: 'compile-server' }); const serverBuildPromise = webpackBuild(useBuildWorker, [ 'server' ]).then((res)=>{ traceMemoryUsage('Finished server compilation', nextBuildSpan); buildTraceContext = res.buildTraceContext; durationInSeconds += res.duration; if (collectServerBuildTracesInParallel) { const buildTraceWorker = new Worker(require.resolve('./collect-build-traces'), { debuggerPortOffset: -1, isolatedMemory: false, numWorkers: 1, exposedMethods: [ 'collectBuildTraces' ] }); buildTracesPromise = nextBuildSpan.traceChild('collect-build-traces').traceAsyncFn(()=>{ return buildTraceWorker.collectBuildTraces({ dir, config, distDir, // Serialize Map as this is sent to the worker. edgeRuntimeRoutes: collectRoutesUsingEdgeRuntime(new Map()), staticPages: [], buildTraceContext, outputFileTracingRoot