2022-08-16 11:55:37 +02:00
import type { webpack } from 'next/dist/compiled/webpack/webpack'
2020-12-21 20:26:00 +01:00
import { loadEnvConfig } from '@next/env'
2021-12-21 16:13:45 +01:00
import chalk from 'next/dist/compiled/chalk'
2020-02-12 02:16:42 +01:00
import crypto from 'crypto'
2022-05-25 22:25:06 +02:00
import { isMatch , makeRe } from 'next/dist/compiled/micromatch'
2020-05-02 06:10:19 +02:00
import { promises , writeFileSync } from 'fs'
2022-05-23 11:25:09 +02:00
import { Worker as JestWorker } from 'next/dist/compiled/jest-worker'
2021-07-16 11:21:44 +02:00
import { Worker } from '../lib/worker'
2020-03-29 01:18:22 +01:00
import devalue from 'next/dist/compiled/devalue'
2022-01-03 18:41:50 +01:00
import { escapeStringRegexp } from '../shared/lib/escape-regexp'
2020-03-29 18:21:53 +02:00
import findUp from 'next/dist/compiled/find-up'
2020-12-29 21:10:08 +01:00
import { nanoid } from 'next/dist/compiled/nanoid/index.cjs'
2020-03-29 19:17:06 +02:00
import { pathToRegexp } from 'next/dist/compiled/path-to-regexp'
2022-01-27 23:22:20 +01:00
import path , { join } from 'path'
2019-10-01 04:08:01 +02:00
import formatWebpackMessages from '../client/dev/error-overlay/format-webpack-messages'
2020-02-01 15:47:42 +01:00
import {
2021-02-22 17:29:50 +01:00
STATIC_STATUS_PAGE_GET_INITIAL_PROPS_ERROR ,
2020-02-04 19:55:43 +01:00
PUBLIC_DIR_MIDDLEWARE_CONFLICT ,
2022-05-19 17:46:21 +02:00
MIDDLEWARE_FILENAME ,
2022-04-27 11:50:29 +02:00
PAGES_DIR_ALIAS ,
2022-06-27 03:02:24 +02:00
SERVER_RUNTIME ,
2020-02-01 15:47:42 +01:00
} from '../lib/constants'
2020-06-10 22:35:34 +02:00
import { fileExists } from '../lib/file-exists'
2019-10-01 04:08:01 +02:00
import { findPagesDir } from '../lib/find-pages-dir'
2020-06-10 22:35:34 +02:00
import loadCustomRoutes , {
2021-03-26 16:19:48 +01:00
CustomRoutes ,
2020-07-08 20:45:53 +02:00
normalizeRouteRegex ,
2020-06-10 22:35:34 +02:00
Redirect ,
2021-03-26 16:19:48 +01:00
Rewrite ,
2020-06-10 22:35:34 +02:00
RouteType ,
} from '../lib/load-custom-routes'
2022-08-09 21:34:25 +02:00
import { getRedirectStatus , modifyRouteRegex } from '../lib/redirect-status'
2020-12-21 20:26:00 +01:00
import { nonNullable } from '../lib/non-nullable'
2019-10-01 04:08:01 +02:00
import { recursiveDelete } from '../lib/recursive-delete'
2021-04-30 13:09:07 +02:00
import { verifyAndLint } from '../lib/verifyAndLint'
2022-03-11 23:26:46 +01:00
import { verifyPartytownSetup } from '../lib/verify-partytown-setup'
2019-04-24 11:04:36 +02:00
import {
2020-12-16 21:46:55 +01:00
BUILD_ID_FILE ,
2019-09-19 18:16:51 +02:00
BUILD_MANIFEST ,
2020-03-02 18:14:40 +01:00
CLIENT_STATIC_FILES_PATH ,
2019-12-13 20:30:22 +01:00
EXPORT_DETAIL ,
EXPORT_MARKER ,
2020-12-16 21:46:55 +01:00
FONT_MANIFEST ,
2020-10-16 13:10:01 +02:00
IMAGES_MANIFEST ,
2019-10-01 04:08:01 +02:00
PAGES_MANIFEST ,
2019-04-24 11:04:36 +02:00
PHASE_PRODUCTION_BUILD ,
2019-08-06 22:26:01 +02:00
PRERENDER_MANIFEST ,
2022-06-26 23:01:26 +02:00
FLIGHT_MANIFEST ,
2020-12-16 21:46:55 +01:00
REACT_LOADABLE_MANIFEST ,
2019-11-15 08:19:41 +01:00
ROUTES_MANIFEST ,
2019-12-12 17:20:24 +01:00
SERVER_DIRECTORY ,
2020-12-16 21:46:55 +01:00
SERVER_FILES_MANIFEST ,
2021-02-22 17:29:50 +01:00
STATIC_STATUS_PAGES ,
2021-10-20 19:52:11 +02:00
MIDDLEWARE_MANIFEST ,
2022-07-07 22:42:44 +02:00
APP_PATHS_MANIFEST ,
APP_PATH_ROUTES_MANIFEST ,
2022-08-12 15:01:19 +02:00
COMPILER_NAMES ,
2022-08-10 21:31:01 +02:00
APP_BUILD_MANIFEST ,
2022-08-13 22:39:31 +02:00
FLIGHT_SERVER_CSS_MANIFEST ,
2022-09-18 02:00:16 +02:00
RSC_MODULE_TYPES ,
2022-09-22 07:12:59 +02:00
FONT_LOADER_MANIFEST ,
2022-10-28 01:50:46 +02:00
CLIENT_STATIC_FILES_RUNTIME_MAIN_APP ,
APP_CLIENT_INTERNALS ,
2021-06-30 11:43:31 +02:00
} from '../shared/lib/constants'
2022-05-19 17:46:21 +02:00
import { getSortedRoutes , isDynamicRoute } from '../shared/lib/router/utils'
2021-06-30 13:44:40 +02:00
import { __ApiPreviewProps } from '../server/api-utils'
2022-01-20 22:25:44 +01:00
import loadConfig from '../server/config'
2021-06-30 13:44:40 +02:00
import { BuildManifest } from '../server/get-page-files'
2022-04-30 13:19:27 +02:00
import { normalizePagePath } from '../shared/lib/page-path/normalize-page-path'
2021-06-30 13:44:40 +02:00
import { getPagePath } from '../server/require'
2020-04-08 22:20:28 +02:00
import * as ciEnvironment from '../telemetry/ci-info'
2019-08-29 18:43:06 +02:00
import {
2020-02-04 19:55:43 +01:00
eventBuildCompleted ,
2019-10-10 19:18:07 +02:00
eventBuildOptimize ,
2020-02-14 21:42:44 +01:00
eventCliSession ,
2021-10-05 21:31:48 +02:00
eventBuildFeatureUsage ,
2019-10-10 19:18:07 +02:00
eventNextPlugins ,
2021-05-04 10:41:01 +02:00
eventTypeCheckCompleted ,
2021-10-11 23:15:18 +02:00
EVENT_BUILD_FEATURE_USAGE ,
EventBuildFeatureUsage ,
2022-04-01 17:08:44 +02:00
eventPackageUsedInGetServerSideProps ,
2019-08-29 18:43:06 +02:00
} from '../telemetry/events'
2019-10-10 19:18:07 +02:00
import { Telemetry } from '../telemetry/storage'
2022-05-02 17:21:40 +02:00
import { runCompiler } from './compiler'
2022-05-20 14:24:00 +02:00
import { getPageStaticInfo } from './analysis/get-page-static-info'
import { createEntrypoints , createPagesMapping } from './entries'
2019-02-17 12:56:48 +01:00
import { generateBuildId } from './generate-build-id'
import { isWriteable } from './is-writeable'
2020-12-21 20:26:00 +01:00
import * as Log from './output/log'
2019-10-01 04:08:01 +02:00
import createSpinner from './spinner'
2021-09-13 15:49:29 +02:00
import { trace , flushAllTraces , setGlobal } from '../trace'
2019-04-10 18:41:59 +02:00
import {
2021-01-11 21:50:17 +01:00
detectConflictingPaths ,
2021-05-14 16:29:49 +02:00
computeFromManifest ,
2020-04-01 11:39:25 +02:00
getJsPageSizeInKb ,
2019-08-06 00:26:20 +02:00
PageInfo ,
2019-11-26 10:33:47 +01:00
printCustomRoutes ,
2019-11-27 22:48:28 +01:00
printTreeView ,
2021-11-09 18:03:20 +01:00
copyTracedFiles ,
2021-11-06 12:27:40 +01:00
isReservedPage ,
2022-09-19 20:05:28 +02:00
AppConfig ,
2019-04-10 18:41:59 +02:00
} from './utils'
2019-04-10 18:37:13 +02:00
import getBaseWebpackConfig from './webpack-config'
2020-05-25 23:15:56 +02:00
import { PagesManifest } from './webpack/plugins/pages-manifest-plugin'
2020-05-29 10:16:22 +02:00
import { writeBuildId } from './write-build-id'
2021-06-30 11:43:31 +02:00
import { normalizeLocalePath } from '../shared/lib/i18n/normalize-locale-path'
2021-07-12 23:38:57 +02:00
import { NextConfigComplete } from '../server/config-shared'
2021-10-21 01:23:44 +02:00
import isError , { NextError } from '../lib/is-error'
2021-10-05 21:31:48 +02:00
import { TelemetryPlugin } from './webpack/plugins/telemetry-plugin'
2021-10-20 19:52:11 +02:00
import { MiddlewareManifest } from './webpack/plugins/middleware-plugin'
2021-11-09 18:03:20 +01:00
import { recursiveCopy } from '../lib/recursive-copy'
2022-04-27 11:50:29 +02:00
import { recursiveReadDir } from '../lib/recursive-readdir'
2022-07-07 19:37:50 +02:00
import {
lockfilePatchPromise ,
teardownTraceSubscriber ,
teardownCrashReporter ,
2022-10-25 11:56:26 +02:00
loadBindings ,
2022-07-07 19:37:50 +02:00
} from './swc'
2022-07-29 00:35:52 +02:00
import { injectedClientEntries } from './webpack/plugins/flight-client-entry-plugin'
2022-05-19 17:46:21 +02:00
import { getNamedRouteRegex } from '../shared/lib/router/utils/route-regex'
import { flatReaddir } from '../lib/flat-readdir'
2022-05-25 22:25:06 +02:00
import { RemotePattern } from '../shared/lib/image-config'
2022-06-30 21:05:35 +02:00
import { eventSwcPlugins } from '../telemetry/events/swc-plugins'
2022-07-07 22:42:44 +02:00
import { normalizeAppPath } from '../shared/lib/router/utils/app-paths'
2022-08-10 21:31:01 +02:00
import { AppBuildManifest } from './webpack/plugins/app-build-manifest-plugin'
2022-11-08 01:35:32 +01:00
import { RSC , RSC_VARY_HEADER } from '../client/components/app-router-headers'
2021-10-20 19:52:11 +02:00
2020-01-15 21:57:07 +01:00
export type SsgRoute = {
2019-09-24 10:50:04 +02:00
initialRevalidateSeconds : number | false
2019-10-02 15:28:38 +02:00
srcRoute : string | null
2019-10-01 04:08:01 +02:00
dataRoute : string
}
2020-01-15 21:57:07 +01:00
export type DynamicSsgRoute = {
2019-10-01 04:08:01 +02:00
routeRegex : string
2020-08-04 17:10:31 +02:00
fallback : string | null | false
2019-10-01 04:08:01 +02:00
dataRoute : string
dataRouteRegex : string
2019-09-24 10:50:04 +02:00
}
export type PrerenderManifest = {
2021-01-27 12:24:00 +01:00
version : 3
2020-01-15 21:57:07 +01:00
routes : { [ route : string ] : SsgRoute }
dynamicRoutes : { [ route : string ] : DynamicSsgRoute }
2020-10-15 23:55:38 +02:00
notFoundRoutes : string [ ]
2020-02-12 02:16:42 +01:00
preview : __ApiPreviewProps
2019-08-06 22:26:01 +02:00
}
2022-05-02 17:21:40 +02:00
type CompilerResult = {
errors : webpack.StatsError [ ]
warnings : webpack.StatsError [ ]
2022-05-13 19:48:53 +02:00
stats : ( webpack . Stats | undefined ) [ ]
}
type SingleCompilerResult = {
errors : webpack.StatsError [ ]
warnings : webpack.StatsError [ ]
stats : webpack.Stats | undefined
2022-05-02 17:21:40 +02:00
}
2022-08-15 16:29:51 +02:00
/ * *
* typescript will be loaded in "next/lib/verifyTypeScriptSetup" and
* then passed to "next/lib/typescript/runTypeCheck" as a parameter .
*
* Since it is impossible to pass a function from main thread to a worker ,
* instead of running "next/lib/typescript/runTypeCheck" in a worker ,
* we will run entire "next/lib/verifyTypeScriptSetup" in a worker instead .
* /
function verifyTypeScriptSetup (
dir : string ,
intentDirs : string [ ] ,
typeCheckPreflight : boolean ,
tsconfigPath : string ,
disableStaticImages : boolean ,
cacheDir : string | undefined ,
numWorkers : number | undefined ,
2022-10-19 18:28:36 +02:00
enableWorkerThreads : boolean | undefined ,
isAppDirEnabled : boolean
2022-08-15 16:29:51 +02:00
) {
const typeCheckWorker = new JestWorker (
require . resolve ( '../lib/verifyTypeScriptSetup' ) ,
{
numWorkers ,
enableWorkerThreads ,
maxRetries : 0 ,
}
) as JestWorker & {
verifyTypeScriptSetup : typeof import ( '../lib/verifyTypeScriptSetup' ) . verifyTypeScriptSetup
}
typeCheckWorker . getStdout ( ) . pipe ( process . stdout )
typeCheckWorker . getStderr ( ) . pipe ( process . stderr )
return typeCheckWorker
2022-08-23 20:16:47 +02:00
. verifyTypeScriptSetup ( {
2022-08-15 16:29:51 +02:00
dir ,
intentDirs ,
typeCheckPreflight ,
tsconfigPath ,
disableStaticImages ,
2022-08-23 20:16:47 +02:00
cacheDir ,
2022-10-19 18:28:36 +02:00
isAppDirEnabled ,
2022-08-23 20:16:47 +02:00
} )
2022-08-15 16:29:51 +02:00
. then ( ( result ) = > {
typeCheckWorker . end ( )
return result
} )
}
function generateClientSsgManifest (
prerenderManifest : PrerenderManifest ,
{
buildId ,
distDir ,
locales ,
} : { buildId : string ; distDir : string ; locales : string [ ] }
) {
const ssgPages = new Set < string > (
[
. . . 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 ( ) `
writeFileSync (
path . join ( distDir , CLIENT_STATIC_FILES_PATH , buildId , '_ssgManifest.js' ) ,
clientSsgManifestContent
)
}
function isTelemetryPlugin ( plugin : unknown ) : plugin is TelemetryPlugin {
return plugin instanceof TelemetryPlugin
}
function pageToRoute ( page : string ) {
const routeRegex = getNamedRouteRegex ( page )
return {
page ,
regex : normalizeRouteRegex ( routeRegex . re . source ) ,
routeKeys : routeRegex.routeKeys ,
namedRegex : routeRegex.namedRegex ,
}
}
2020-07-09 13:39:12 +02:00
export default async function build (
dir : string ,
conf = null ,
2020-08-20 17:43:38 +02:00
reactProductionProfiling = false ,
2021-06-03 14:01:24 +02:00
debugOutput = false ,
runLint = true
2020-07-09 13:39:12 +02:00
) : Promise < void > {
2022-04-28 18:40:37 +02:00
try {
const nextBuildSpan = trace ( 'next-build' , undefined , {
version : process.env.__NEXT_VERSION as string ,
2021-05-04 10:41:01 +02:00
} )
2022-04-28 18:40:37 +02:00
const buildResult = await nextBuildSpan . traceAsyncFn ( async ( ) = > {
// 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 ) )
const config : NextConfigComplete = await nextBuildSpan
. traceChild ( 'load-next-config' )
. traceAsyncFn ( ( ) = > loadConfig ( PHASE_PRODUCTION_BUILD , dir , conf ) )
const distDir = path . join ( dir , config . distDir )
setGlobal ( 'phase' , PHASE_PRODUCTION_BUILD )
setGlobal ( 'distDir' , distDir )
const { target } = config
const buildId : string = await nextBuildSpan
. traceChild ( 'generate-buildid' )
. traceAsyncFn ( ( ) = > generateBuildId ( config . generateBuildId , nanoid ) )
const customRoutes : CustomRoutes = await nextBuildSpan
. traceChild ( 'load-custom-routes' )
. traceAsyncFn ( ( ) = > loadCustomRoutes ( config ) )
const { headers , rewrites , redirects } = customRoutes
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 )
2019-08-18 21:45:39 +02:00
2022-04-28 18:40:37 +02:00
const publicDir = path . join ( dir , 'public' )
2022-10-07 00:16:42 +02:00
const isAppDirEnabled = ! ! config . experimental . appDir
2022-10-24 05:04:23 +02:00
const initialRequireHookFilePath = require . resolve (
'next/dist/server/initialize-require-hook'
)
const content = await promises . readFile (
initialRequireHookFilePath ,
'utf8'
)
2022-10-19 01:32:23 +02:00
if ( isAppDirEnabled ) {
2022-10-22 08:55:19 +02:00
process . env . NEXT_PREBUNDLED_REACT = '1'
2022-10-19 01:32:23 +02:00
}
2022-10-24 05:04:23 +02:00
await promises
. writeFile (
initialRequireHookFilePath ,
content . replace (
/isPrebundled = (true|false)/ ,
` isPrebundled = ${ isAppDirEnabled } `
)
)
. catch ( ( err ) = > {
if ( isAppDirEnabled ) {
throw err
}
} )
2022-10-07 00:16:42 +02:00
const { pagesDir , appDir } = findPagesDir ( dir , isAppDirEnabled )
2022-04-28 18:40:37 +02:00
const hasPublicDir = await fileExists ( publicDir )
2021-05-04 10:41:01 +02:00
2021-05-07 16:34:15 +02:00
telemetry . record (
2022-04-28 18:40:37 +02:00
eventCliSession ( dir , config , {
webpackVersion : 5 ,
cliCommand : 'build' ,
2022-09-03 02:13:47 +02:00
isSrcDir :
( ! ! pagesDir && path . relative ( dir , pagesDir ) . startsWith ( 'src' ) ) ||
( ! ! appDir && path . relative ( dir , appDir ) . startsWith ( 'src' ) ) ,
2022-04-28 18:40:37 +02:00
hasNowJson : ! ! ( await findUp ( 'now.json' , { cwd : dir } ) ) ,
isCustomServer : null ,
2022-10-31 04:06:11 +01:00
turboFlag : false ,
2022-11-02 19:09:56 +01:00
pagesDir : ! ! pagesDir ,
appDir : ! ! appDir ,
2021-05-07 16:34:15 +02:00
} )
)
2021-05-04 10:41:01 +02:00
2022-04-28 18:40:37 +02:00
eventNextPlugins ( path . resolve ( dir ) ) . then ( ( events ) = >
telemetry . record ( events )
)
2022-06-30 21:05:35 +02:00
eventSwcPlugins ( path . resolve ( dir ) , config ) . then ( ( events ) = >
telemetry . record ( events )
)
2022-05-23 11:25:09 +02:00
const ignoreESLint = Boolean ( config . eslint . ignoreDuringBuilds )
const shouldLint = ! ignoreESLint && runLint
2022-10-19 18:28:36 +02:00
const startTypeChecking = async ( ) = > {
const ignoreTypeScriptErrors = Boolean (
config . typescript . ignoreBuildErrors
)
2022-05-23 11:25:09 +02:00
2022-10-19 18:28:36 +02:00
const eslintCacheDir = path . join ( cacheDir , 'eslint/' )
2022-05-23 11:25:09 +02:00
2022-10-19 18:28:36 +02:00
if ( ignoreTypeScriptErrors ) {
Log . info ( 'Skipping validation of types' )
}
if ( runLint && ignoreESLint ) {
2022-10-24 05:04:23 +02:00
// only print log when build require lint while ignoreESLint is enabled
2022-10-19 18:28:36 +02:00
Log . info ( 'Skipping linting' )
}
2022-05-23 11:25:09 +02:00
2022-10-19 18:28:36 +02:00
let typeCheckingAndLintingSpinnerPrefixText : string | undefined
let typeCheckingAndLintingSpinner :
| ReturnType < typeof createSpinner >
| undefined
if ( ! ignoreTypeScriptErrors && shouldLint ) {
typeCheckingAndLintingSpinnerPrefixText =
'Linting and checking validity of types'
} else if ( ! ignoreTypeScriptErrors ) {
typeCheckingAndLintingSpinnerPrefixText = 'Checking validity of types'
} else if ( shouldLint ) {
typeCheckingAndLintingSpinnerPrefixText = 'Linting'
}
// we will not create a spinner if both ignoreTypeScriptErrors and ignoreESLint are
// enabled, but we will still verifying project's tsconfig and dependencies.
if ( typeCheckingAndLintingSpinnerPrefixText ) {
typeCheckingAndLintingSpinner = createSpinner ( {
prefixText : ` ${ Log . prefixes . info } ${ typeCheckingAndLintingSpinnerPrefixText } ` ,
} )
}
const typeCheckStart = process . hrtime ( )
try {
const [ [ verifyResult , typeCheckEnd ] ] = await Promise . all ( [
2022-08-24 16:29:27 +02:00
nextBuildSpan
2022-10-19 18:28:36 +02:00
. traceChild ( 'verify-typescript-setup' )
. traceAsyncFn ( ( ) = >
verifyTypeScriptSetup (
2022-08-24 16:29:27 +02:00
dir ,
2022-10-19 18:28:36 +02:00
[ pagesDir , appDir ] . filter ( Boolean ) as string [ ] ,
! ignoreTypeScriptErrors ,
config . typescript . tsconfigPath ,
config . images . disableStaticImages ,
cacheDir ,
2022-08-24 16:29:27 +02:00
config . experimental . cpus ,
config . experimental . workerThreads ,
2022-10-19 18:28:36 +02:00
isAppDirEnabled
) . then ( ( resolved ) = > {
const checkEnd = process . hrtime ( typeCheckStart )
return [ resolved , checkEnd ] as const
} )
) ,
shouldLint &&
nextBuildSpan
. traceChild ( 'verify-and-lint' )
. traceAsyncFn ( async ( ) = > {
await verifyAndLint (
dir ,
eslintCacheDir ,
config . eslint ? . dirs ,
config . experimental . cpus ,
config . experimental . workerThreads ,
telemetry ,
isAppDirEnabled && ! ! appDir
)
} ) ,
] )
typeCheckingAndLintingSpinner ? . stopAndPersist ( )
if ( ! ignoreTypeScriptErrors && verifyResult ) {
telemetry . record (
eventTypeCheckCompleted ( {
durationInSeconds : typeCheckEnd [ 0 ] ,
typescriptVersion : verifyResult.version ,
inputFilesCount : verifyResult.result?.inputFilesCount ,
totalFilesCount : verifyResult.result?.totalFilesCount ,
incremental : verifyResult.result?.incremental ,
} )
)
}
} catch ( err ) {
// prevent showing jest-worker internal error as it
// isn't helpful for users and clutters output
if ( isError ( err ) && err . message === 'Call retries were exceeded' ) {
process . exit ( 1 )
}
throw err
2022-08-24 16:29:27 +02:00
}
2022-04-28 18:40:37 +02:00
}
2021-10-26 18:50:56 +02:00
2022-10-19 18:28:36 +02:00
// 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 ) await startTypeChecking ( )
2022-04-28 18:40:37 +02:00
const buildLintEvent : EventBuildFeatureUsage = {
featureName : 'build-lint' ,
invocationCount : shouldLint ? 1 : 0 ,
}
telemetry . record ( {
eventName : EVENT_BUILD_FEATURE_USAGE ,
payload : buildLintEvent ,
} )
2022-04-27 11:50:29 +02:00
2022-04-28 18:40:37 +02:00
const buildSpinner = createSpinner ( {
prefixText : ` ${ Log . prefixes . info } Creating an optimized production build ` ,
} )
2020-12-21 17:02:41 +01:00
2022-09-03 02:13:47 +02:00
const pagesPaths = pagesDir
? await nextBuildSpan
. traceChild ( 'collect-pages' )
. traceAsyncFn ( ( ) = >
recursiveReadDir (
pagesDir ,
new RegExp ( ` \\ .(?: ${ config . pageExtensions . join ( '|' ) } ) $ ` )
)
)
: [ ]
2022-05-03 12:37:23 +02:00
2022-05-25 11:46:26 +02:00
let appPaths : string [ ] | undefined
2022-05-03 12:37:23 +02:00
2022-05-25 11:46:26 +02:00
if ( appDir ) {
appPaths = await nextBuildSpan
. traceChild ( 'collect-app-paths' )
2022-05-03 12:37:23 +02:00
. traceAsyncFn ( ( ) = >
recursiveReadDir (
2022-05-25 11:46:26 +02:00
appDir ,
2022-05-06 18:11:11 +02:00
new RegExp ( ` page \\ .(?: ${ config . pageExtensions . join ( '|' ) } ) $ ` )
2022-05-03 12:37:23 +02:00
)
)
}
2022-05-06 18:11:11 +02:00
2022-06-08 16:10:05 +02:00
const middlewareDetectionRegExp = new RegExp (
` ^ ${ MIDDLEWARE_FILENAME } \\ .(?: ${ config . pageExtensions . join ( '|' ) } ) $ `
2022-05-19 17:46:21 +02:00
)
2022-09-03 02:13:47 +02:00
const rootPaths = pagesDir
? (
await flatReaddir ( join ( pagesDir , '..' ) , middlewareDetectionRegExp )
) . map ( ( absoluteFile ) = > absoluteFile . replace ( dir , '' ) )
: [ ]
2022-06-08 16:10:05 +02:00
2022-04-28 18:40:37 +02:00
// needed for static exporting since we want to replace with HTML
// files
2019-09-16 23:06:30 +02:00
2022-04-28 18:40:37 +02:00
const allStaticPages = new Set < string > ( )
let allPageInfos = new Map < string , PageInfo > ( )
2019-09-16 23:06:30 +02:00
2022-04-28 18:40:37 +02:00
const previewProps : __ApiPreviewProps = {
previewModeId : crypto.randomBytes ( 16 ) . toString ( 'hex' ) ,
previewModeSigningKey : crypto.randomBytes ( 32 ) . toString ( 'hex' ) ,
previewModeEncryptionKey : crypto.randomBytes ( 32 ) . toString ( 'hex' ) ,
}
2020-05-27 18:45:53 +02:00
2022-04-28 18:40:37 +02:00
const mappedPages = nextBuildSpan
. traceChild ( 'create-pages-mapping' )
. traceFn ( ( ) = >
createPagesMapping ( {
isDev : false ,
pageExtensions : config.pageExtensions ,
2022-05-19 17:46:21 +02:00
pagesType : 'pages' ,
2022-08-10 21:31:01 +02:00
pagePaths : pagesPaths ,
2022-09-03 02:13:47 +02:00
pagesDir ,
2022-04-28 18:40:37 +02:00
} )
)
2019-12-23 22:20:17 +01:00
2022-08-10 21:31:01 +02:00
let mappedAppPages : { [ page : string ] : string } | undefined
2022-05-03 12:37:23 +02:00
2022-05-25 11:46:26 +02:00
if ( appPaths && appDir ) {
2022-08-10 21:31:01 +02:00
mappedAppPages = nextBuildSpan
2022-05-25 11:46:26 +02:00
. traceChild ( 'create-app-mapping' )
2022-05-03 12:37:23 +02:00
. traceFn ( ( ) = >
createPagesMapping ( {
2022-05-25 11:46:26 +02:00
pagePaths : appPaths ! ,
2022-05-03 12:37:23 +02:00
isDev : false ,
2022-05-25 11:46:26 +02:00
pagesType : 'app' ,
2022-05-03 12:37:23 +02:00
pageExtensions : config.pageExtensions ,
2022-09-03 02:13:47 +02:00
pagesDir : pagesDir ,
2022-05-03 12:37:23 +02:00
} )
)
}
2022-05-19 17:46:21 +02:00
let mappedRootPaths : { [ page : string ] : string } = { }
if ( rootPaths . length > 0 ) {
mappedRootPaths = createPagesMapping ( {
isDev : false ,
pageExtensions : config.pageExtensions ,
pagePaths : rootPaths ,
pagesType : 'root' ,
2022-09-03 02:13:47 +02:00
pagesDir : pagesDir ,
2022-05-19 17:46:21 +02:00
} )
}
2022-04-28 18:40:37 +02:00
const entrypoints = await nextBuildSpan
. traceChild ( 'create-entrypoints' )
. traceAsyncFn ( ( ) = >
createEntrypoints ( {
buildId ,
config ,
envFiles : loadedEnvFiles ,
isDev : false ,
pages : mappedPages ,
pagesDir ,
previewMode : previewProps ,
2022-05-19 17:46:21 +02:00
rootDir : dir ,
rootPaths : mappedRootPaths ,
2022-05-25 11:46:26 +02:00
appDir ,
2022-08-10 21:31:01 +02:00
appPaths : mappedAppPages ,
2022-05-07 15:37:14 +02:00
pageExtensions : config.pageExtensions ,
2022-04-28 18:40:37 +02:00
} )
)
2022-11-03 20:50:39 +01:00
const pagesPageKeys = Object . keys ( mappedPages )
const conflictingAppPagePaths : [ pagePath : string , appPath : string ] [ ] = [ ]
const appPageKeys : string [ ] = [ ]
if ( mappedAppPages ) {
for ( const appKey in mappedAppPages ) {
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' ) ,
] )
2022-10-02 20:57:21 +02:00
}
2022-11-03 20:50:39 +01:00
appPageKeys . push ( normalizedAppPageKey )
2022-10-02 20:57:21 +02:00
}
2022-11-03 20:50:39 +01:00
}
2022-10-02 20:57:21 +02:00
2022-11-03 20:50:39 +01:00
const pageKeys = {
pages : pagesPageKeys ,
app : appPageKeys.length > 0 ? appPageKeys : undefined ,
}
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 } " ` )
2022-10-02 20:57:21 +02:00
}
2022-11-03 20:50:39 +01:00
process . exit ( 1 )
2022-10-02 20:57:21 +02:00
}
2022-04-28 18:40:37 +02:00
const conflictingPublicFiles : string [ ] = [ ]
const hasPages404 = mappedPages [ '/404' ] ? . startsWith ( PAGES_DIR_ALIAS )
const hasCustomErrorPage =
mappedPages [ '/_error' ] . startsWith ( PAGES_DIR_ALIAS )
2021-07-13 21:38:14 +02:00
2022-04-28 18:40:37 +02:00
if ( hasPublicDir ) {
const hasPublicUnderScoreNextDir = await fileExists (
path . join ( publicDir , '_next' )
2021-10-20 19:52:11 +02:00
)
2022-04-28 18:40:37 +02:00
if ( hasPublicUnderScoreNextDir ) {
throw new Error ( PUBLIC_DIR_MIDDLEWARE_CONFLICT )
}
2021-03-26 16:19:48 +01:00
}
2022-04-28 18:40:37 +02:00
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 ) ,
'file'
)
if ( hasPublicPageFile ) {
conflictingPublicFiles . push ( page )
}
2021-01-25 18:09:22 +01:00
}
2022-04-28 18:40:37 +02:00
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'
) } `
)
}
} )
2022-08-10 21:31:01 +02:00
const nestedReservedPages = pageKeys . pages . filter ( ( page ) = > {
2022-04-28 18:40:37 +02:00
return (
page . match ( /\/(_app|_document|_error)$/ ) && path . dirname ( page ) !== '/'
)
Telemetry-compatible tracing (#22713)
A number of changes here. I recommend viewing the diff with the <a href="?w=1">whitespace flag enabled</a>.
- OpenTelemetry is replaced with a custom and lightweight tracing solution.
- Three trace targets are currently supported: console, Zipkin, and NextJS.
- Tracing is now governed by environment variables rather than `--require instrument.js`.
+ `TRACE_TARGET`: one of `CONSOLE`, `ZIPKIN`, or `TELEMETRY`; defaults to `TELEMETRY` if unset or invalid.
+ `TRACE_ID`: an 8-byte hex-encoded value used as the Zipkin trace ID; if not provided, this value will be randomly generated and passed down to subprocesses.
Other sundry:
- I'm missing something, probably a setup step, with the Zipkin target. Traces are captured successfully, but you have to manually enter the Trace ID in order to view the trace - it doesn't show up in queries.
- I'm generally unhappy with [this commit](https://github.com/vercel/next.js/pull/22713/commits/235cedcb3ead76b630b4c8aa695f904489da2831). It is... untidy to provide a telemetry object via `setGlobal`, but I don't have a ready alternative. Is `distDir` strictly required when creating a new Telemetry object? I didn't dig too deep here.
As noted, there are a lot of changes, so it'd be great if a reviewer could:
- [ ] pull down the branch and try to break it
- [ ] check the Zipkin traces and identify possible regressions in the functionality
Closes #22570
Fixes #22574
2021-03-10 22:00:20 +01:00
} )
2021-01-25 18:09:22 +01:00
2022-04-28 18:40:37 +02:00
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 `
Telemetry-compatible tracing (#22713)
A number of changes here. I recommend viewing the diff with the <a href="?w=1">whitespace flag enabled</a>.
- OpenTelemetry is replaced with a custom and lightweight tracing solution.
- Three trace targets are currently supported: console, Zipkin, and NextJS.
- Tracing is now governed by environment variables rather than `--require instrument.js`.
+ `TRACE_TARGET`: one of `CONSOLE`, `ZIPKIN`, or `TELEMETRY`; defaults to `TELEMETRY` if unset or invalid.
+ `TRACE_ID`: an 8-byte hex-encoded value used as the Zipkin trace ID; if not provided, this value will be randomly generated and passed down to subprocesses.
Other sundry:
- I'm missing something, probably a setup step, with the Zipkin target. Traces are captured successfully, but you have to manually enter the Trace ID in order to view the trace - it doesn't show up in queries.
- I'm generally unhappy with [this commit](https://github.com/vercel/next.js/pull/22713/commits/235cedcb3ead76b630b4c8aa695f904489da2831). It is... untidy to provide a telemetry object via `setGlobal`, but I don't have a ready alternative. Is `distDir` strictly required when creating a new Telemetry object? I didn't dig too deep here.
As noted, there are a lot of changes, so it'd be great if a reviewer could:
- [ ] pull down the branch and try to break it
- [ ] check the Zipkin traces and identify possible regressions in the functionality
Closes #22570
Fixes #22574
2021-03-10 22:00:20 +01:00
)
2022-04-28 18:40:37 +02:00
}
const restrictedRedirectPaths = [ '/_next' ] . map ( ( p ) = >
config . basePath ? ` ${ config . basePath } ${ p } ` : p
2021-01-10 02:12:13 +01:00
)
2019-07-15 17:16:35 +02:00
2022-04-28 18:40:37 +02:00
const buildCustomRoute = (
r : {
source : string
locale? : false
basePath? : false
statusCode? : number
destination? : string
2022-02-08 04:50:23 +01:00
} ,
2022-04-28 18:40:37 +02:00
type : RouteType
) = > {
const keys : any [ ] = [ ]
const routeRegex = pathToRegexp ( r . source , keys , {
strict : true ,
sensitive : false ,
delimiter : '/' , // default is `/#?`, but Next does not pass query info
} )
let regexSource = routeRegex . source
if ( ! ( r as any ) . internal ) {
regexSource = modifyRouteRegex (
routeRegex . source ,
type === 'redirect' ? restrictedRedirectPaths : undefined
)
}
return {
. . . r ,
. . . ( type === 'redirect'
? {
statusCode : getRedirectStatus ( r as Redirect ) ,
permanent : undefined ,
}
: { } ) ,
regex : normalizeRouteRegex ( regexSource ) ,
}
}
const routesManifestPath = path . join ( distDir , ROUTES_MANIFEST )
const routesManifest : {
version : number
pages404 : boolean
basePath : string
redirects : Array < ReturnType < typeof buildCustomRoute > >
rewrites ? :
| Array < ReturnType < typeof buildCustomRoute > >
| {
beforeFiles : Array < ReturnType < typeof buildCustomRoute > >
afterFiles : Array < ReturnType < typeof buildCustomRoute > >
fallback : Array < ReturnType < typeof buildCustomRoute > >
}
headers : Array < ReturnType < typeof buildCustomRoute > >
staticRoutes : Array < {
page : string
regex : string
namedRegex? : string
routeKeys ? : { [ key : string ] : string }
} >
dynamicRoutes : Array < {
page : string
regex : string
namedRegex? : string
routeKeys ? : { [ key : string ] : string }
} >
dataRoutes : Array < {
page : string
routeKeys ? : { [ key : string ] : string }
dataRouteRegex : string
namedDataRouteRegex? : string
} >
i18n ? : {
domains? : Array < {
http? : true
domain : string
locales? : string [ ]
defaultLocale : string
} >
locales : string [ ]
defaultLocale : string
localeDetection? : false
}
2022-11-08 01:35:32 +01:00
rsc : {
header : typeof RSC
varyHeader : typeof RSC_VARY_HEADER
}
2022-07-07 22:42:44 +02:00
} = nextBuildSpan . traceChild ( 'generate-routes-manifest' ) . traceFn ( ( ) = > {
const sortedRoutes = getSortedRoutes ( [
2022-08-10 21:31:01 +02:00
. . . pageKeys . pages ,
. . . ( pageKeys . app ? ? [ ] ) ,
2022-07-07 22:42:44 +02:00
] )
const dynamicRoutes : Array < ReturnType < typeof pageToRoute > > = [ ]
const staticRoutes : typeof dynamicRoutes = [ ]
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 ,
basePath : config.basePath ,
redirects : redirects.map ( ( r : any ) = > buildCustomRoute ( r , 'redirect' ) ) ,
headers : headers.map ( ( r : any ) = > buildCustomRoute ( r , 'header' ) ) ,
dynamicRoutes ,
staticRoutes ,
dataRoutes : [ ] ,
i18n : config.i18n || undefined ,
2022-11-08 01:35:32 +01:00
rsc : {
header : RSC ,
varyHeader : RSC_VARY_HEADER ,
} ,
2022-07-07 22:42:44 +02:00
}
} )
2019-07-15 17:16:35 +02:00
2022-04-28 18:40:37 +02:00
if ( rewrites . beforeFiles . length === 0 && rewrites . fallback . length === 0 ) {
routesManifest . rewrites = rewrites . afterFiles . map ( ( r : any ) = >
buildCustomRoute ( r , 'rewrite' )
)
} else {
routesManifest . rewrites = {
beforeFiles : rewrites.beforeFiles.map ( ( r : any ) = >
buildCustomRoute ( r , 'rewrite' )
) ,
afterFiles : rewrites.afterFiles.map ( ( r : any ) = >
buildCustomRoute ( r , 'rewrite' )
) ,
fallback : rewrites.fallback.map ( ( r : any ) = >
buildCustomRoute ( r , 'rewrite' )
) ,
}
2022-04-27 11:50:29 +02:00
}
2022-04-28 18:40:37 +02:00
const combinedRewrites : Rewrite [ ] = [
. . . rewrites . beforeFiles ,
. . . rewrites . afterFiles ,
. . . rewrites . fallback ,
]
const distDirCreated = await nextBuildSpan
. traceChild ( 'create-dist-dir' )
. traceAsyncFn ( async ( ) = > {
try {
await promises . mkdir ( distDir , { recursive : true } )
return true
} catch ( err ) {
if ( isError ( err ) && err . code === 'EPERM' ) {
return false
}
throw err
}
} )
2022-04-27 11:50:29 +02:00
2022-04-28 18:40:37 +02:00
if ( ! distDirCreated || ! ( await isWriteable ( distDir ) ) ) {
throw new Error (
'> Build directory is not writeable. https://nextjs.org/docs/messages/build-dir-not-writeable'
2021-12-03 01:47:16 +01:00
)
2022-04-28 18:40:37 +02:00
}
2020-12-21 17:02:41 +01:00
2022-04-28 18:40:37 +02:00
if ( config . cleanDistDir ) {
await recursiveDelete ( distDir , /^cache/ )
}
2019-03-17 13:13:29 +01:00
2022-04-28 18:40:37 +02:00
// Ensure commonjs handling is used for files in the distDir (generally .next)
// Files outside of the distDir can be "type": "module"
await promises . writeFile (
path . join ( distDir , 'package.json' ) ,
'{"type": "commonjs"}'
)
// We need to write the manifest with rewrites before build
// so serverless can import the manifest
await nextBuildSpan
. traceChild ( 'write-routes-manifest' )
. traceAsyncFn ( ( ) = >
promises . writeFile (
routesManifestPath ,
JSON . stringify ( routesManifest ) ,
'utf8'
)
2021-12-03 01:47:16 +01:00
)
2020-02-11 23:09:00 +01:00
2022-10-18 18:47:13 +02:00
const manifestPath = path . join ( distDir , SERVER_DIRECTORY , PAGES_MANIFEST )
2022-04-28 18:40:37 +02:00
const requiredServerFiles = nextBuildSpan
. traceChild ( 'generate-required-server-files' )
. traceFn ( ( ) = > ( {
version : 1 ,
config : {
. . . config ,
configFile : undefined ,
experimental : {
. . . config . experimental ,
trustHostHeader : ciEnvironment.hasNextSupport ,
} ,
} ,
appDir : dir ,
files : [
ROUTES_MANIFEST ,
path . relative ( distDir , manifestPath ) ,
BUILD_MANIFEST ,
PRERENDER_MANIFEST ,
path . join ( SERVER_DIRECTORY , MIDDLEWARE_MANIFEST ) ,
2022-09-21 21:30:46 +02:00
. . . ( appDir
2022-04-28 18:40:37 +02:00
? [
2022-06-26 23:01:26 +02:00
path . join ( SERVER_DIRECTORY , FLIGHT_MANIFEST + '.js' ) ,
path . join ( SERVER_DIRECTORY , FLIGHT_MANIFEST + '.json' ) ,
2022-08-24 21:49:47 +02:00
path . join (
SERVER_DIRECTORY ,
FLIGHT_SERVER_CSS_MANIFEST + '.js'
) ,
2022-08-13 22:39:31 +02:00
path . join (
SERVER_DIRECTORY ,
FLIGHT_SERVER_CSS_MANIFEST + '.json'
) ,
2022-04-28 18:40:37 +02:00
]
: [ ] ) ,
REACT_LOADABLE_MANIFEST ,
2022-10-18 18:47:13 +02:00
config . optimizeFonts
? path . join ( SERVER_DIRECTORY , FONT_MANIFEST )
: null ,
2022-04-28 18:40:37 +02:00
BUILD_ID_FILE ,
2022-10-18 18:47:13 +02:00
appDir ? path . join ( SERVER_DIRECTORY , APP_PATHS_MANIFEST ) : null ,
2022-09-22 18:10:36 +02:00
. . . ( config . experimental . fontLoaders
? [
path . join ( SERVER_DIRECTORY , FONT_LOADER_MANIFEST + '.js' ) ,
path . join ( SERVER_DIRECTORY , FONT_LOADER_MANIFEST + '.json' ) ,
]
: [ ] ) ,
2022-04-28 18:40:37 +02:00
]
. filter ( nonNullable )
. map ( ( file ) = > path . join ( config . distDir , file ) ) ,
ignore : [ ] as string [ ] ,
} ) )
2022-05-02 17:21:40 +02:00
let result : CompilerResult = {
warnings : [ ] ,
errors : [ ] ,
2022-05-13 19:48:53 +02:00
stats : [ ] ,
2022-05-02 17:21:40 +02:00
}
2022-04-28 18:40:37 +02:00
let webpackBuildStart
let telemetryPlugin
await ( async ( ) = > {
// IIFE to isolate locals and avoid retaining memory too long
const runWebpackSpan = nextBuildSpan . traceChild ( 'run-webpack-compiler' )
const commonWebpackOptions = {
buildId ,
config ,
pagesDir ,
reactProductionProfiling ,
rewrites ,
runWebpackSpan ,
target ,
2022-05-25 11:46:26 +02:00
appDir ,
feat(next): Support has match and locale option on middleware config (#39257)
## Feature
As the title, support `has` match, `local` that works the same with the `rewrites` and `redirects` of next.config.js on middleware config. With this PR, you can write the config like the following:
```js
export const config = {
matcher: [
"/foo",
{ source: "/bar" },
{
source: "/baz",
has: [
{
type: 'header',
key: 'x-my-header',
value: 'my-value',
}
]
},
{
source: "/en/asdf",
locale: false,
},
]
}
```
Also, fixes https://github.com/vercel/next.js/issues/39428
related https://github.com/vercel/edge-functions/issues/178, https://github.com/vercel/edge-functions/issues/179
- [x] Implements an existing feature request or RFC. Make sure the feature request has been accepted for implementation before opening a PR.
- [ ] Related issues linked using `fixes #number`
- [x] Integration tests added
- [ ] Documentation added
- [ ] Telemetry added. In case of a feature if it's used or not.
- [ ] Errors have helpful link attached, see `contributing.md`
Co-authored-by: JJ Kasper <jj@jjsweb.site>
2022-08-31 18:23:30 +02:00
middlewareMatchers : entrypoints.middlewareMatchers ,
2022-04-28 18:40:37 +02:00
}
const configs = await runWebpackSpan
. traceChild ( 'generate-webpack-config' )
. traceAsyncFn ( ( ) = >
Promise . all ( [
getBaseWebpackConfig ( dir , {
. . . commonWebpackOptions ,
2022-08-12 15:01:19 +02:00
compilerType : COMPILER_NAMES.client ,
2022-04-28 18:40:37 +02:00
entrypoints : entrypoints.client ,
} ) ,
getBaseWebpackConfig ( dir , {
. . . commonWebpackOptions ,
2022-08-12 15:01:19 +02:00
compilerType : COMPILER_NAMES.server ,
2022-04-28 18:40:37 +02:00
entrypoints : entrypoints.server ,
} ) ,
getBaseWebpackConfig ( dir , {
. . . commonWebpackOptions ,
2022-08-12 15:01:19 +02:00
compilerType : COMPILER_NAMES.edgeServer ,
2022-04-28 18:40:37 +02:00
entrypoints : entrypoints.edgeServer ,
} ) ,
] )
)
2020-12-21 17:02:41 +01:00
2022-04-28 18:40:37 +02:00
const clientConfig = configs [ 0 ]
if (
clientConfig . optimization &&
( clientConfig . optimization . minimize !== true ||
( clientConfig . optimization . minimizer &&
clientConfig . optimization . minimizer . length === 0 ) )
) {
Log . warn (
` Production code optimization has been disabled in your project. Read more: https://nextjs.org/docs/messages/minification-disabled `
)
2021-08-17 09:18:47 +02:00
}
2020-08-04 09:58:23 +02:00
2022-04-28 18:40:37 +02:00
webpackBuildStart = process . hrtime ( )
2020-12-21 17:02:41 +01:00
2022-04-28 18:40:37 +02:00
// We run client and server compilation separately to optimize for memory usage
await runWebpackSpan . traceAsyncFn ( async ( ) = > {
2022-10-18 18:47:13 +02:00
// Run the server compilers first and then the client
2022-05-13 19:48:53 +02:00
// compiler to track the boundary of server/client components.
let clientResult : SingleCompilerResult | null = null
2022-10-18 18:47:13 +02:00
// During the server compilations, entries of client components will be
// injected to this set and then will be consumed by the client compiler.
injectedClientEntries . clear ( )
2022-05-13 19:48:53 +02:00
2022-10-18 18:47:13 +02:00
const serverResult = await runCompiler ( configs [ 1 ] , {
runWebpackSpan ,
} )
const edgeServerResult = configs [ 2 ]
? await runCompiler ( configs [ 2 ] , { runWebpackSpan } )
: null
// Only continue if there were no errors
if ( ! serverResult . errors . length && ! edgeServerResult ? . errors . length ) {
injectedClientEntries . forEach ( ( value , key ) = > {
2022-10-28 01:50:46 +02:00
const clientEntry = clientConfig . entry as webpack . EntryObject
if ( key === APP_CLIENT_INTERNALS ) {
clientEntry [ CLIENT_STATIC_FILES_RUNTIME_MAIN_APP ] = [
// TODO-APP: cast clientEntry[CLIENT_STATIC_FILES_RUNTIME_MAIN_APP] to type EntryDescription once it's available from webpack
// @ts-expect-error clientEntry['main-app'] is type EntryDescription { import: ... }
. . . clientEntry [ CLIENT_STATIC_FILES_RUNTIME_MAIN_APP ] . import ,
value ,
]
} else {
2022-10-31 18:50:35 +01:00
clientEntry [ key ] = {
dependOn : [ CLIENT_STATIC_FILES_RUNTIME_MAIN_APP ] ,
import : value ,
}
2022-10-28 01:50:46 +02:00
}
2022-05-13 19:48:53 +02:00
} )
2022-10-18 18:47:13 +02:00
clientResult = await runCompiler ( clientConfig , {
2022-04-28 18:40:37 +02:00
runWebpackSpan ,
} )
}
2022-05-13 19:48:53 +02:00
result = {
warnings : ( [ ] as any [ ] )
. concat (
clientResult ? . warnings ,
serverResult ? . warnings ,
edgeServerResult ? . warnings
)
. filter ( nonNullable ) ,
errors : ( [ ] as any [ ] )
. concat (
clientResult ? . errors ,
serverResult ? . errors ,
edgeServerResult ? . errors
)
. filter ( nonNullable ) ,
stats : [
clientResult ? . stats ,
serverResult ? . stats ,
edgeServerResult ? . stats ,
] ,
}
2022-04-28 18:40:37 +02:00
} )
result = nextBuildSpan
. traceChild ( 'format-webpack-messages' )
. traceFn ( ( ) = > formatWebpackMessages ( result , true ) )
2020-12-21 17:02:41 +01:00
2022-04-28 18:40:37 +02:00
telemetryPlugin = ( clientConfig as webpack . Configuration ) . plugins ? . find (
isTelemetryPlugin
2020-12-21 17:02:41 +01:00
)
2022-04-28 18:40:37 +02:00
} ) ( )
const webpackBuildEnd = process . hrtime ( webpackBuildStart )
if ( buildSpinner ) {
buildSpinner . stopAndPersist ( )
2020-12-21 17:02:41 +01:00
}
2022-04-28 18:40:37 +02:00
if ( result . errors . length > 0 ) {
// Only keep the first few errors. Others are often indicative
// of the same problem, but confuse the reader with noise.
if ( result . errors . length > 5 ) {
result . errors . length = 5
}
2022-05-22 08:56:18 +02:00
let error = result . errors . filter ( Boolean ) . join ( '\n\n' )
2019-05-22 18:36:53 +02:00
2022-04-28 18:40:37 +02:00
console . error ( chalk . red ( 'Failed to compile.\n' ) )
if (
error . indexOf ( 'private-next-pages' ) > - 1 &&
error . indexOf ( 'does not contain a default export' ) > - 1
) {
const page_name_regex = /'private-next-pages\/(?<page_name>[^']*)'/
const parsed = page_name_regex . exec ( error )
const page_name = parsed && parsed . groups && parsed . groups . page_name
throw new Error (
` webpack build failed: found page without a React Component as default export in pages/ ${ page_name } \ n \ nSee https://nextjs.org/docs/messages/page-without-valid-component for more info. `
)
}
2021-11-01 12:40:21 +01:00
2022-04-28 18:40:37 +02:00
console . error ( error )
console . error ( )
if (
error . indexOf ( 'private-next-pages' ) > - 1 ||
error . indexOf ( '__next_polyfill__' ) > - 1
) {
const err = new Error (
'webpack config.resolve.alias was incorrectly overridden. https://nextjs.org/docs/messages/invalid-resolve-alias'
) as NextError
err . code = 'INVALID_RESOLVE_ALIAS'
throw err
}
2021-10-21 01:23:44 +02:00
const err = new Error (
2022-04-28 18:40:37 +02:00
'Build failed because of webpack errors'
2021-10-21 01:23:44 +02:00
) as NextError
2022-04-28 18:40:37 +02:00
err . code = 'WEBPACK_ERRORS'
2021-10-21 01:23:44 +02:00
throw err
2020-12-21 17:02:41 +01:00
} else {
2022-04-28 18:40:37 +02:00
telemetry . record (
2022-08-10 21:31:01 +02:00
eventBuildCompleted ( pagesPaths , {
2022-04-28 18:40:37 +02:00
durationInSeconds : webpackBuildEnd [ 0 ] ,
} )
)
if ( result . warnings . length > 0 ) {
Log . warn ( 'Compiled with warnings\n' )
2022-05-22 08:56:18 +02:00
console . warn ( result . warnings . filter ( Boolean ) . join ( '\n\n' ) )
2022-04-28 18:40:37 +02:00
console . warn ( )
} else {
Log . info ( 'Compiled successfully' )
}
2020-12-21 17:02:41 +01:00
}
2020-06-15 16:41:17 +02:00
2022-10-19 18:28:36 +02:00
// For app directory, we run type checking after build.
if ( appDir ) {
await startTypeChecking ( )
}
2022-04-28 18:40:37 +02:00
const postCompileSpinner = createSpinner ( {
prefixText : ` ${ Log . prefixes . info } Collecting page data ` ,
} )
2020-06-15 16:41:17 +02:00
2022-04-28 18:40:37 +02:00
const buildManifestPath = path . join ( distDir , BUILD_MANIFEST )
2022-08-10 21:31:01 +02:00
const appBuildManifestPath = path . join ( distDir , APP_BUILD_MANIFEST )
2022-04-28 18:40:37 +02:00
const ssgPages = new Set < string > ( )
const ssgStaticFallbackPages = new Set < string > ( )
const ssgBlockingFallbackPages = new Set < string > ( )
const staticPages = new Set < string > ( )
const invalidPages = new Set < string > ( )
const hybridAmpPages = new Set < string > ( )
const serverPropsPages = new Set < string > ( )
const additionalSsgPaths = new Map < string , Array < string > > ( )
const additionalSsgPathsEncoded = new Map < string , Array < string > > ( )
2022-09-19 20:05:28 +02:00
const appStaticPaths = new Map < string , Array < string > > ( )
const appStaticPathsEncoded = new Map < string , Array < string > > ( )
const appNormalizedPaths = new Map < string , string > ( )
const appDynamicParamPaths = new Set < string > ( )
const appDefaultConfigs = new Map < string , AppConfig > ( )
2022-04-28 18:40:37 +02:00
const pageTraceIncludes = new Map < string , Array < string > > ( )
const pageTraceExcludes = new Map < string , Array < string > > ( )
const pageInfos = new Map < string , PageInfo > ( )
const pagesManifest = JSON . parse (
await promises . readFile ( manifestPath , 'utf8' )
) as PagesManifest
const buildManifest = JSON . parse (
await promises . readFile ( buildManifestPath , 'utf8' )
) as BuildManifest
2022-08-10 21:31:01 +02:00
const appBuildManifest = appDir
? ( JSON . parse (
await promises . readFile ( appBuildManifestPath , 'utf8' )
) as AppBuildManifest )
: undefined
2022-04-28 18:40:37 +02:00
const timeout = config . staticPageGenerationTimeout || 0
const sharedPool = config . experimental . sharedPool || false
const staticWorker = sharedPool
? require . resolve ( './worker' )
: require . resolve ( './utils' )
let infoPrinted = false
2022-09-19 20:05:28 +02:00
let appPathsManifest : Record < string , string > = { }
const appPathRoutes : Record < string , string > = { }
if ( appDir ) {
appPathsManifest = JSON . parse (
await promises . readFile (
2022-10-18 18:47:13 +02:00
path . join ( distDir , SERVER_DIRECTORY , APP_PATHS_MANIFEST ) ,
2022-09-19 20:05:28 +02:00
'utf8'
)
)
Object . keys ( appPathsManifest ) . forEach ( ( entry ) = > {
appPathRoutes [ entry ] = normalizeAppPath ( entry ) || '/'
} )
await promises . writeFile (
path . join ( distDir , APP_PATH_ROUTES_MANIFEST ) ,
JSON . stringify ( appPathRoutes , null , 2 )
)
}
2022-04-28 18:40:37 +02:00
process . env . NEXT_PHASE = PHASE_PRODUCTION_BUILD
const staticWorkers = new Worker ( staticWorker , {
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 `
2021-08-12 21:54:49 +02:00
)
}
2022-04-28 18:40:37 +02:00
if ( ! infoPrinted ) {
Log . warn (
'See more info here https://nextjs.org/docs/messages/static-page-generation-timeout'
2021-08-12 21:54:49 +02:00
)
2022-04-28 18:40:37 +02:00
infoPrinted = true
2021-08-12 21:54:49 +02:00
}
2022-04-28 18:40:37 +02:00
} ,
numWorkers : config.experimental.cpus ,
enableWorkerThreads : config.experimental.workerThreads ,
exposedMethods : sharedPool
? [
'hasCustomGetInitialProps' ,
'isPageStatic' ,
'getNamedExports' ,
'exportPage' ,
]
: [ 'hasCustomGetInitialProps' , 'isPageStatic' , 'getNamedExports' ] ,
} ) as Worker &
Pick <
typeof import ( './worker' ) ,
| 'hasCustomGetInitialProps'
| 'isPageStatic'
| 'getNamedExports'
| 'exportPage'
>
const analysisBegin = process . hrtime ( )
const staticCheckSpan = nextBuildSpan . traceChild ( 'static-check' )
const {
customAppGetInitialProps ,
namedExports ,
isNextImageImported ,
hasSsrAmpPages ,
hasNonStaticErrorPage ,
} = await staticCheckSpan . traceAsyncFn ( async ( ) = > {
const { configFileName , publicRuntimeConfig , serverRuntimeConfig } =
config
const runtimeEnvConfig = { publicRuntimeConfig , serverRuntimeConfig }
const nonStaticErrorPageSpan = staticCheckSpan . traceChild (
'check-static-error-page'
)
const errorPageHasCustomGetInitialProps =
nonStaticErrorPageSpan . traceAsyncFn (
async ( ) = >
hasCustomErrorPage &&
( await staticWorkers . hasCustomGetInitialProps (
'/_error' ,
distDir ,
runtimeEnvConfig ,
false
) )
2021-08-12 21:54:49 +02:00
)
2022-04-28 18:40:37 +02:00
const errorPageStaticResult = nonStaticErrorPageSpan . traceAsyncFn (
2021-08-17 09:18:08 +02:00
async ( ) = >
hasCustomErrorPage &&
2022-08-30 20:18:02 +02:00
staticWorkers . isPageStatic ( {
page : '/_error' ,
2021-08-17 09:18:08 +02:00
distDir ,
2022-04-28 18:40:37 +02:00
configFileName ,
2021-08-17 09:18:08 +02:00
runtimeEnvConfig ,
2022-08-30 20:18:02 +02:00
httpAgentOptions : config.httpAgentOptions ,
2022-09-27 22:37:28 +02:00
enableUndici : config.experimental.enableUndici ,
2022-08-30 20:18:02 +02:00
locales : config.i18n?.locales ,
defaultLocale : config.i18n?.defaultLocale ,
pageRuntime : config.experimental.runtime ,
} )
2021-08-17 09:18:08 +02:00
)
2021-06-11 11:29:40 +02:00
2022-10-18 18:47:13 +02:00
const appPageToCheck = '/_app'
2022-04-28 18:40:37 +02:00
const customAppGetInitialPropsPromise =
staticWorkers . hasCustomGetInitialProps (
appPageToCheck ,
2021-06-11 11:29:40 +02:00
distDir ,
runtimeEnvConfig ,
2022-04-28 18:40:37 +02:00
true
2021-06-11 11:29:40 +02:00
)
2021-05-17 14:04:06 +02:00
2022-04-28 18:40:37 +02:00
const namedExportsPromise = staticWorkers . getNamedExports (
2021-08-17 09:18:08 +02:00
appPageToCheck ,
distDir ,
2022-04-28 18:40:37 +02:00
runtimeEnvConfig
2021-08-17 09:18:08 +02:00
)
2020-12-16 21:46:55 +01:00
2022-04-28 18:40:37 +02:00
// eslint-disable-next-line no-shadow
let isNextImageImported : boolean | undefined
// eslint-disable-next-line no-shadow
let hasSsrAmpPages = false
2020-11-11 16:46:48 +01:00
2022-04-28 18:40:37 +02:00
const computedManifestData = await computeFromManifest (
2022-08-10 21:31:01 +02:00
{ build : buildManifest , app : appBuildManifest } ,
2022-04-28 18:40:37 +02:00
distDir ,
config . experimental . gzipSize
)
2021-10-26 18:50:56 +02:00
2022-04-28 18:40:37 +02:00
await Promise . all (
2022-08-10 21:31:01 +02:00
Object . entries ( pageKeys )
. reduce < Array < { pageType : keyof typeof pageKeys ; page : string } > > (
( acc , [ key , files ] ) = > {
if ( ! files ) {
return acc
}
2021-05-17 14:04:06 +02:00
2022-08-10 21:31:01 +02:00
const pageType = key as keyof typeof pageKeys
2022-04-28 18:40:37 +02:00
2022-08-10 21:31:01 +02:00
for ( const page of files ) {
acc . push ( { pageType , page } )
}
2022-05-20 14:24:00 +02:00
2022-08-10 21:31:01 +02:00
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
)
2022-03-26 00:05:35 +01:00
2022-08-10 21:31:01 +02:00
let isSsg = false
let isStatic = false
let isServerComponent = false
let isHybridAmp = false
let ssgPageRoutes : string [ ] | null = null
2022-09-19 20:05:28 +02:00
let pagePath = ''
if ( pageType === 'pages' ) {
pagePath =
pagesPaths . find (
( p ) = >
p . startsWith ( actualPage + '.' ) ||
p . startsWith ( actualPage + '/index.' )
) || ''
}
let originalAppPath : string | undefined
if ( pageType === 'app' && mappedAppPages ) {
for ( const [ originalPath , normalizedPath ] of Object . entries (
appPathRoutes
) ) {
if ( normalizedPath === page ) {
pagePath = mappedAppPages [ originalPath ] . replace (
/^private-next-app-dir/ ,
''
2022-08-10 21:31:01 +02:00
)
2022-09-19 20:05:28 +02:00
originalAppPath = originalPath
break
}
}
}
2022-08-10 21:31:01 +02:00
2022-09-19 20:05:28 +02:00
const staticInfo = pagePath
? await getPageStaticInfo ( {
pageFilePath : join (
( pageType === 'pages' ? pagesDir : appDir ) || '' ,
pagePath
) ,
nextConfig : config ,
2022-10-27 23:55:35 +02:00
pageType ,
2022-09-19 20:05:28 +02:00
} )
: undefined
const pageRuntime = staticInfo ? . runtime
2022-09-18 02:00:16 +02:00
isServerComponent =
pageType === 'app' &&
2022-09-19 20:05:28 +02:00
staticInfo ? . rsc !== RSC_MODULE_TYPES . client
2022-04-01 18:13:38 +02:00
2022-09-19 20:05:28 +02:00
if ( ! isReservedPage ( page ) ) {
2022-08-10 21:31:01 +02:00
try {
2022-08-30 20:18:02 +02:00
let edgeInfo : any
if ( pageRuntime === SERVER_RUNTIME . edge ) {
const manifest = require ( join (
distDir ,
2022-10-18 18:47:13 +02:00
SERVER_DIRECTORY ,
2022-08-30 20:18:02 +02:00
MIDDLEWARE_MANIFEST
) )
2022-09-19 20:05:28 +02:00
const manifestKey =
2022-10-04 20:46:11 +02:00
pageType === 'pages' ? page : originalAppPath || ''
2022-08-30 20:18:02 +02:00
2022-09-19 20:05:28 +02:00
edgeInfo = manifest . functions [ manifestKey ]
2022-08-30 20:18:02 +02:00
}
2022-08-10 21:31:01 +02:00
let isPageStaticSpan =
checkPageSpan . traceChild ( 'is-page-static' )
let workerResult = await isPageStaticSpan . traceAsyncFn (
( ) = > {
2022-08-30 20:18:02 +02:00
return staticWorkers . isPageStatic ( {
2022-08-10 21:31:01 +02:00
page ,
2022-09-19 20:05:28 +02:00
originalAppPath ,
2022-08-10 21:31:01 +02:00
distDir ,
configFileName ,
runtimeEnvConfig ,
2022-08-30 20:18:02 +02:00
httpAgentOptions : config.httpAgentOptions ,
2022-09-27 22:37:28 +02:00
enableUndici : config.experimental.enableUndici ,
2022-08-30 20:18:02 +02:00
locales : config.i18n?.locales ,
defaultLocale : config.i18n?.defaultLocale ,
parentId : isPageStaticSpan.id ,
pageRuntime ,
edgeInfo ,
2022-09-19 20:05:28 +02:00
pageType ,
2022-09-21 21:30:46 +02:00
hasServerComponents : ! ! appDir ,
2022-08-30 20:18:02 +02:00
} )
2022-08-10 21:31:01 +02:00
}
2022-04-28 18:40:37 +02:00
)
2019-09-24 10:50:04 +02:00
2022-09-19 20:05:28 +02:00
if ( pageType === 'app' && originalAppPath ) {
appNormalizedPaths . set ( originalAppPath , page )
// TODO-APP: handle prerendering with edge
if ( pageRuntime === 'experimental-edge' ) {
2022-10-22 19:04:53 +02:00
isStatic = false
isSsg = false
} else {
if (
workerResult . encodedPrerenderRoutes &&
2022-09-19 20:05:28 +02:00
workerResult . prerenderRoutes
2022-10-22 19:04:53 +02:00
) {
appStaticPaths . set (
originalAppPath ,
workerResult . prerenderRoutes
)
appStaticPathsEncoded . set (
originalAppPath ,
workerResult . encodedPrerenderRoutes
)
ssgPageRoutes = workerResult . prerenderRoutes
isSsg = true
}
if (
! isDynamicRoute ( page ) &&
workerResult . appConfig ? . revalidate !== 0
) {
appStaticPaths . set ( originalAppPath , [ page ] )
appStaticPathsEncoded . set ( originalAppPath , [ page ] )
isStatic = true
}
if ( workerResult . prerenderFallback ) {
// whether or not to allow requests for paths not
// returned from generateStaticParams
appDynamicParamPaths . add ( originalAppPath )
}
appDefaultConfigs . set (
2022-09-19 20:05:28 +02:00
originalAppPath ,
2022-10-22 19:04:53 +02:00
workerResult . appConfig || { }
2022-09-19 20:05:28 +02:00
)
}
2022-10-22 19:04:53 +02:00
} else {
if ( pageRuntime === SERVER_RUNTIME . edge ) {
if ( workerResult . hasStaticProps ) {
console . warn (
` "getStaticProps" is not yet supported fully with "experimental-edge", detected on ${ page } `
)
}
// TODO: add handling for statically rendering edge
// pages and allow edge with Prerender outputs
workerResult . isStatic = false
workerResult . hasStaticProps = false
2022-09-19 20:05:28 +02:00
}
2022-10-22 19:04:53 +02:00
if ( config . outputFileTracing ) {
pageTraceIncludes . set (
page ,
workerResult . traceIncludes || [ ]
)
pageTraceExcludes . set (
page ,
workerResult . traceExcludes || [ ]
2022-08-30 20:18:02 +02:00
)
}
2022-10-22 19:04:53 +02:00
if (
workerResult . isStatic === false &&
( workerResult . isHybridAmp || workerResult . isAmpOnly )
) {
hasSsrAmpPages = true
}
2020-08-04 17:10:31 +02:00
2022-10-22 19:04:53 +02:00
if ( workerResult . isHybridAmp ) {
isHybridAmp = true
hybridAmpPages . add ( page )
}
2020-02-01 15:47:42 +01:00
2022-10-22 19:04:53 +02:00
if ( workerResult . isNextImageImported ) {
isNextImageImported = true
}
2020-12-21 17:02:41 +01:00
2022-10-22 19:04:53 +02:00
if ( workerResult . hasStaticProps ) {
ssgPages . add ( page )
isSsg = true
2021-01-10 02:12:13 +01:00
2022-10-22 19:04:53 +02:00
if (
workerResult . prerenderRoutes &&
2022-08-10 21:31:01 +02:00
workerResult . encodedPrerenderRoutes
2022-10-22 19:04:53 +02:00
) {
additionalSsgPaths . set (
page ,
workerResult . prerenderRoutes
)
additionalSsgPathsEncoded . set (
page ,
workerResult . encodedPrerenderRoutes
)
ssgPageRoutes = workerResult . prerenderRoutes
}
if ( workerResult . prerenderFallback === 'blocking' ) {
ssgBlockingFallbackPages . add ( page )
} else if ( workerResult . prerenderFallback === true ) {
ssgStaticFallbackPages . add ( page )
}
} else if ( workerResult . hasServerProps ) {
serverPropsPages . add ( page )
} else if (
workerResult . isStatic &&
! isServerComponent &&
( await customAppGetInitialPropsPromise ) === false
) {
staticPages . add ( page )
isStatic = true
} else if ( isServerComponent ) {
// This is a static server component page that doesn't have
// gSP or gSSP. We still treat it as a SSG page.
ssgPages . add ( page )
isSsg = true
2022-08-10 21:31:01 +02:00
}
2022-10-22 19:04:53 +02:00
if ( hasPages404 && page === '/404' ) {
if (
! workerResult . isStatic &&
! workerResult . hasStaticProps
) {
throw new Error (
` \` pages/404 \` ${ STATIC_STATUS_PAGE_GET_INITIAL_PROPS_ERROR } `
)
}
// we need to ensure the 404 lambda is present since we use
// it when _app has getInitialProps
if (
( await customAppGetInitialPropsPromise ) &&
! workerResult . hasStaticProps
) {
staticPages . delete ( page )
}
2022-08-10 21:31:01 +02:00
}
2021-02-22 17:29:50 +01:00
2022-08-10 21:31:01 +02:00
if (
2022-10-22 19:04:53 +02:00
STATIC_STATUS_PAGES . includes ( page ) &&
2022-08-10 21:31:01 +02:00
! workerResult . isStatic &&
! workerResult . hasStaticProps
) {
throw new Error (
2022-10-22 19:04:53 +02:00
` \` pages ${ page } \` ${ STATIC_STATUS_PAGE_GET_INITIAL_PROPS_ERROR } `
2022-08-10 21:31:01 +02:00
)
}
2022-04-28 18:40:37 +02:00
}
2022-08-10 21:31:01 +02:00
} catch ( err ) {
2022-04-28 18:40:37 +02:00
if (
2022-08-10 21:31:01 +02:00
! isError ( err ) ||
err . message !== 'INVALID_DEFAULT_EXPORT'
2022-04-28 18:40:37 +02:00
)
2022-08-10 21:31:01 +02:00
throw err
invalidPages . add ( page )
Telemetry-compatible tracing (#22713)
A number of changes here. I recommend viewing the diff with the <a href="?w=1">whitespace flag enabled</a>.
- OpenTelemetry is replaced with a custom and lightweight tracing solution.
- Three trace targets are currently supported: console, Zipkin, and NextJS.
- Tracing is now governed by environment variables rather than `--require instrument.js`.
+ `TRACE_TARGET`: one of `CONSOLE`, `ZIPKIN`, or `TELEMETRY`; defaults to `TELEMETRY` if unset or invalid.
+ `TRACE_ID`: an 8-byte hex-encoded value used as the Zipkin trace ID; if not provided, this value will be randomly generated and passed down to subprocesses.
Other sundry:
- I'm missing something, probably a setup step, with the Zipkin target. Traces are captured successfully, but you have to manually enter the Trace ID in order to view the trace - it doesn't show up in queries.
- I'm generally unhappy with [this commit](https://github.com/vercel/next.js/pull/22713/commits/235cedcb3ead76b630b4c8aa695f904489da2831). It is... untidy to provide a telemetry object via `setGlobal`, but I don't have a ready alternative. Is `distDir` strictly required when creating a new Telemetry object? I didn't dig too deep here.
As noted, there are a lot of changes, so it'd be great if a reviewer could:
- [ ] pull down the branch and try to break it
- [ ] check the Zipkin traces and identify possible regressions in the functionality
Closes #22570
Fixes #22574
2021-03-10 22:00:20 +01:00
}
}
2022-08-10 21:31:01 +02:00
pageInfos . set ( page , {
size : selfSize ,
totalSize : allSize ,
static : isStatic ,
isSsg ,
isHybridAmp ,
ssgPageRoutes ,
initialRevalidateSeconds : false ,
runtime : pageRuntime ,
pageDuration : undefined ,
ssgPageDurations : undefined ,
} )
2022-04-28 18:40:37 +02:00
} )
Telemetry-compatible tracing (#22713)
A number of changes here. I recommend viewing the diff with the <a href="?w=1">whitespace flag enabled</a>.
- OpenTelemetry is replaced with a custom and lightweight tracing solution.
- Three trace targets are currently supported: console, Zipkin, and NextJS.
- Tracing is now governed by environment variables rather than `--require instrument.js`.
+ `TRACE_TARGET`: one of `CONSOLE`, `ZIPKIN`, or `TELEMETRY`; defaults to `TELEMETRY` if unset or invalid.
+ `TRACE_ID`: an 8-byte hex-encoded value used as the Zipkin trace ID; if not provided, this value will be randomly generated and passed down to subprocesses.
Other sundry:
- I'm missing something, probably a setup step, with the Zipkin target. Traces are captured successfully, but you have to manually enter the Trace ID in order to view the trace - it doesn't show up in queries.
- I'm generally unhappy with [this commit](https://github.com/vercel/next.js/pull/22713/commits/235cedcb3ead76b630b4c8aa695f904489da2831). It is... untidy to provide a telemetry object via `setGlobal`, but I don't have a ready alternative. Is `distDir` strictly required when creating a new Telemetry object? I didn't dig too deep here.
As noted, there are a lot of changes, so it'd be great if a reviewer could:
- [ ] pull down the branch and try to break it
- [ ] check the Zipkin traces and identify possible regressions in the functionality
Closes #22570
Fixes #22574
2021-03-10 22:00:20 +01:00
} )
2022-04-28 18:40:37 +02:00
)
2021-06-11 11:29:40 +02:00
2022-04-28 18:40:37 +02:00
const errorPageResult = await errorPageStaticResult
const nonStaticErrorPage =
( await errorPageHasCustomGetInitialProps ) ||
( errorPageResult && errorPageResult . hasServerProps )
const returnValue = {
customAppGetInitialProps : await customAppGetInitialPropsPromise ,
namedExports : await namedExportsPromise ,
isNextImageImported ,
hasSsrAmpPages ,
hasNonStaticErrorPage : nonStaticErrorPage ,
}
2021-05-17 14:04:06 +02:00
2022-04-28 18:40:37 +02:00
if ( ! sharedPool ) staticWorkers . end ( )
return returnValue
} )
2021-05-17 14:04:06 +02:00
2022-04-28 18:40:37 +02:00
if ( customAppGetInitialProps ) {
console . warn (
chalk . bold . yellow ( ` Warning: ` ) +
chalk . yellow (
` You have opted-out of Automatic Static Optimization due to \` getInitialProps \` in \` pages/_app \` . This does not opt-out pages with \` getStaticProps \` `
)
)
console . warn (
'Read more: https://nextjs.org/docs/messages/opt-out-auto-static-optimization\n'
)
}
2019-04-24 10:48:43 +02:00
2022-04-28 18:40:37 +02:00
if ( ! hasSsrAmpPages ) {
requiredServerFiles . ignore . push (
path . relative (
dir ,
path . join (
path . dirname (
require . resolve (
'next/dist/compiled/@ampproject/toolbox-optimizer'
)
) ,
'**/*'
)
2020-12-21 17:02:41 +01:00
)
2020-12-16 21:46:55 +01:00
)
2022-04-28 18:40:37 +02:00
}
2020-01-27 23:50:59 +01:00
2022-04-28 18:40:37 +02:00
if ( config . outputFileTracing ) {
2022-10-25 11:56:26 +02:00
let nodeFileTrace : any
if ( config . experimental . turbotrace ) {
let binding = ( await loadBindings ( ) ) as any
if ( ! binding ? . isWasm ) {
nodeFileTrace = binding . turbo ? . startTrace
}
}
if ( ! nodeFileTrace ) {
nodeFileTrace =
require ( 'next/dist/compiled/@vercel/nft' ) . nodeFileTrace
}
2021-10-23 01:49:38 +02:00
2022-04-28 18:40:37 +02:00
const includeExcludeSpan = nextBuildSpan . traceChild (
'apply-include-excludes'
)
2021-10-23 01:49:38 +02:00
2022-04-28 18:40:37 +02:00
await includeExcludeSpan . traceAsyncFn ( async ( ) = > {
const globOrig =
require ( 'next/dist/compiled/glob' ) as typeof import ( 'next/dist/compiled/glob' )
const glob = ( pattern : string ) : Promise < string [ ] > = > {
return new Promise ( ( resolve , reject ) = > {
globOrig ( pattern , { cwd : dir } , ( err , files ) = > {
if ( err ) {
return reject ( err )
}
resolve ( files )
} )
2021-10-23 01:49:38 +02:00
} )
2022-04-28 18:40:37 +02:00
}
2021-08-16 21:29:11 +02:00
2022-08-10 21:31:01 +02:00
for ( let page of pageKeys . pages ) {
2022-04-28 18:40:37 +02:00
await includeExcludeSpan
. traceChild ( 'include-exclude' , { page } )
. traceAsyncFn ( async ( ) = > {
const includeGlobs = pageTraceIncludes . get ( page )
const excludeGlobs = pageTraceExcludes . get ( page )
page = normalizePagePath ( page )
2021-10-23 01:49:38 +02:00
2022-04-28 18:40:37 +02:00
if ( ! includeGlobs ? . length && ! excludeGlobs ? . length ) {
return
}
2021-10-23 01:49:38 +02:00
2022-04-28 18:40:37 +02:00
const traceFile = path . join (
distDir ,
'server/pages' ,
` ${ page } .js.nft.json `
)
const pageDir = path . dirname ( traceFile )
const traceContent = JSON . parse (
await promises . readFile ( traceFile , 'utf8' )
)
let includes : string [ ] = [ ]
if ( includeGlobs ? . length ) {
for ( const includeGlob of includeGlobs ) {
const results = await glob ( includeGlob )
includes . push (
. . . results . map ( ( file ) = > {
return path . relative ( pageDir , path . join ( dir , file ) )
} )
)
}
}
const combined = new Set ( [ . . . traceContent . files , . . . includes ] )
if ( excludeGlobs ? . length ) {
const resolvedGlobs = excludeGlobs . map ( ( exclude ) = >
path . join ( dir , exclude )
2021-10-23 01:49:38 +02:00
)
2022-04-28 18:40:37 +02:00
combined . forEach ( ( file ) = > {
if ( isMatch ( path . join ( pageDir , file ) , resolvedGlobs ) ) {
combined . delete ( file )
}
} )
2021-10-23 01:49:38 +02:00
}
2022-04-28 18:40:37 +02:00
await promises . writeFile (
traceFile ,
JSON . stringify ( {
version : traceContent.version ,
files : [ . . . combined ] ,
} )
2021-10-23 01:49:38 +02:00
)
2022-04-28 18:40:37 +02:00
} )
}
} )
2021-08-16 21:29:11 +02:00
2022-04-28 18:40:37 +02:00
// TODO: move this inside of webpack so it can be cached
// between builds. Should only need to be re-run on lockfile change
await nextBuildSpan
. traceChild ( 'trace-next-server' )
. traceAsyncFn ( async ( ) = > {
let cacheKey : string | undefined
// consider all lockFiles in tree in case user accidentally
// has both package-lock.json and yarn.lock
const lockFiles : string [ ] = (
await Promise . all (
[ 'package-lock.json' , 'yarn.lock' , 'pnpm-lock.yaml' ] . map (
( file ) = > findUp ( file , { cwd : dir } )
)
2021-10-23 01:49:38 +02:00
)
2022-04-28 18:40:37 +02:00
) . filter ( Boolean ) as any // TypeScript doesn't like this filter
2021-08-16 21:29:11 +02:00
2022-04-28 18:40:37 +02:00
const nextServerTraceOutput = path . join (
distDir ,
'next-server.js.nft.json'
)
const cachedTracePath = path . join (
distDir ,
'cache/next-server.js.nft.json'
2021-10-23 01:49:38 +02:00
)
2022-04-28 18:40:37 +02:00
if ( lockFiles . length > 0 ) {
const cacheHash = (
require ( 'crypto' ) as typeof import ( 'crypto' )
) . createHash ( 'sha256' )
2021-10-23 01:49:38 +02:00
2022-04-28 18:40:37 +02:00
cacheHash . update ( require ( 'next/package' ) . version )
cacheHash . update ( hasSsrAmpPages + '' )
cacheHash . update ( ciEnvironment . hasNextSupport + '' )
2021-10-23 01:49:38 +02:00
2022-04-28 18:40:37 +02:00
await Promise . all (
lockFiles . map ( async ( lockFile ) = > {
cacheHash . update ( await promises . readFile ( lockFile ) )
} )
)
cacheKey = cacheHash . digest ( 'hex' )
2021-10-23 01:49:38 +02:00
2022-04-28 18:40:37 +02:00
try {
const existingTrace = JSON . parse (
await promises . readFile ( cachedTracePath , 'utf8' )
)
2021-10-23 01:49:38 +02:00
2022-04-28 18:40:37 +02:00
if ( existingTrace . cacheKey === cacheKey ) {
await promises . copyFile (
cachedTracePath ,
nextServerTraceOutput
)
return
}
} catch ( _ ) { }
}
2021-10-23 01:49:38 +02:00
2022-10-26 08:31:49 +02:00
const root =
config . experimental ? . turbotrace ? . contextDirectory ? ?
config . experimental ? . outputFileTracingRoot ? ?
dir
2022-05-28 02:50:21 +02:00
const toTrace = [ require . resolve ( 'next/dist/server/next-server' ) ]
// ensure we trace any dependencies needed for custom
// incremental cache handler
if ( config . experimental . incrementalCacheHandlerPath ) {
toTrace . push (
require . resolve ( config . experimental . incrementalCacheHandlerPath )
)
}
2022-06-21 20:11:32 +02:00
2022-10-25 11:56:26 +02:00
let serverResult : import ( 'next/dist/compiled/@vercel/nft' ) . NodeFileTraceResult
if ( config . experimental . turbotrace ) {
// handle the cache in the turbo-tracing side in the future
await nodeFileTrace ( {
action : 'annotate' ,
input : toTrace ,
contextDirectory : root ,
2022-10-26 08:31:49 +02:00
logLevel : config.experimental.turbotrace.logLevel ,
processCwd : config.experimental.turbotrace.processCwd ,
logDetail : config.experimental.turbotrace.logDetail ,
showAll : config.experimental.turbotrace.logAll ,
2022-10-25 11:56:26 +02:00
} )
} else {
2022-11-08 02:43:58 +01:00
const ignores = [
'**/next/dist/pages/**/*' ,
'**/next/dist/compiled/webpack/(bundle4|bundle5).js' ,
'**/node_modules/webpack5/**/*' ,
'**/next/dist/server/lib/squoosh/**/*.wasm' ,
. . . ( ciEnvironment . hasNextSupport
? [
// only ignore image-optimizer code when
// this is being handled outside of next-server
'**/next/dist/server/image-optimizer.js' ,
'**/node_modules/sharp/**/*' ,
]
: [ ] ) ,
. . . ( ! hasSsrAmpPages
? [ '**/next/dist/compiled/@ampproject/toolbox-optimizer/**/*' ]
: [ ] ) ,
]
const ignoreFn = ( pathname : string ) = > {
return isMatch ( pathname , ignores , { contains : true , dot : true } )
}
2022-10-25 11:56:26 +02:00
serverResult = await nodeFileTrace ( toTrace , {
base : root ,
processCwd : dir ,
2022-11-08 02:43:58 +01:00
ignore : ignoreFn ,
2022-10-25 11:56:26 +02:00
} )
const tracedFiles = new Set ( )
2021-08-16 21:29:11 +02:00
2022-10-25 11:56:26 +02:00
serverResult . fileList . forEach ( ( file ) = > {
tracedFiles . add (
path
. relative ( distDir , path . join ( root , file ) )
. replace ( /\\/g , '/' )
)
} )
2021-10-23 01:49:38 +02:00
2022-10-25 11:56:26 +02:00
await promises . writeFile (
nextServerTraceOutput ,
JSON . stringify ( {
version : 1 ,
cacheKey ,
files : [ . . . tracedFiles ] ,
} as {
version : number
files : string [ ]
} )
2022-04-28 18:40:37 +02:00
)
2022-10-25 11:56:26 +02:00
await promises . unlink ( cachedTracePath ) . catch ( ( ) = > { } )
await promises
. copyFile ( nextServerTraceOutput , cachedTracePath )
. catch ( ( ) = > { } )
}
2021-10-22 20:55:45 +02:00
} )
2022-04-28 18:40:37 +02:00
}
2021-08-16 21:29:11 +02:00
2022-04-28 18:40:37 +02:00
if ( serverPropsPages . size > 0 || ssgPages . size > 0 ) {
// We update the routes manifest after the build with the
// data routes since we can't determine these until after build
routesManifest . dataRoutes = getSortedRoutes ( [
. . . serverPropsPages ,
. . . ssgPages ,
] ) . map ( ( page ) = > {
const pagePath = normalizePagePath ( page )
const dataRoute = path . posix . join (
'/_next/data' ,
buildId ,
` ${ pagePath } .json `
2021-10-23 01:49:38 +02:00
)
2022-04-28 18:40:37 +02:00
let dataRouteRegex : string
let namedDataRouteRegex : string | undefined
let routeKeys : { [ named : string ] : string } | undefined
if ( isDynamicRoute ( page ) ) {
2022-05-19 17:46:21 +02:00
const routeRegex = getNamedRouteRegex (
dataRoute . replace ( /\.json$/ , '' )
)
2022-04-28 18:40:37 +02:00
dataRouteRegex = normalizeRouteRegex (
routeRegex . re . source . replace ( /\(\?:\\\/\)\?\$$/ , ` \\ .json $ ` )
)
namedDataRouteRegex = routeRegex . namedRegex ! . replace (
/\(\?:\/\)\?\$$/ ,
` \\ .json $ `
)
routeKeys = routeRegex . routeKeys
} else {
dataRouteRegex = normalizeRouteRegex (
new RegExp (
` ^ ${ path . posix . join (
'/_next/data' ,
escapeStringRegexp ( buildId ) ,
` ${ pagePath } .json `
) } $ `
) . source
)
}
return {
page ,
routeKeys ,
dataRouteRegex ,
namedDataRouteRegex ,
}
2021-10-23 01:49:38 +02:00
} )
2022-04-28 18:40:37 +02:00
await promises . writeFile (
routesManifestPath ,
JSON . stringify ( routesManifest ) ,
'utf8'
2020-12-21 17:02:41 +01:00
)
2022-04-28 18:40:37 +02:00
}
// Since custom _app.js can wrap the 404 page we have to opt-out of static optimization if it has getInitialProps
// Only export the static 404 when there is no /_error present
const useStatic404 =
! customAppGetInitialProps && ( ! hasNonStaticErrorPage || hasPages404 )
if ( invalidPages . size > 0 ) {
const err = new Error (
` Build optimization failed: found page ${
invalidPages . size === 1 ? '' : 's'
} without a React Component as default export in \ n $ { [ . . . invalidPages ]
. map ( ( pg ) = > ` pages ${ pg } ` )
. join (
'\n'
) } \ n \ nSee https : //nextjs.org/docs/messages/page-without-valid-component for more info.\n`
) as NextError
err . code = 'BUILD_OPTIMIZATION_FAILED'
throw err
}
2020-04-28 09:59:47 +02:00
2022-04-28 18:40:37 +02:00
await writeBuildId ( distDir , buildId )
2020-04-28 09:59:47 +02:00
2022-04-28 18:40:37 +02:00
if ( config . experimental . optimizeCss ) {
const globOrig =
require ( 'next/dist/compiled/glob' ) as typeof import ( 'next/dist/compiled/glob' )
2020-04-28 09:59:47 +02:00
2022-04-28 18:40:37 +02:00
const cssFilePaths = await new Promise < string [ ] > ( ( resolve , reject ) = > {
globOrig (
'**/*.css' ,
{ cwd : join ( distDir , 'static' ) } ,
( err , files ) = > {
if ( err ) {
return reject ( err )
}
resolve ( files )
}
2020-12-21 17:02:41 +01:00
)
2022-04-28 18:40:37 +02:00
} )
requiredServerFiles . files . push (
. . . cssFilePaths . map ( ( filePath ) = >
path . join ( config . distDir , 'static' , filePath )
2020-12-21 17:02:41 +01:00
)
2022-04-28 18:40:37 +02:00
)
}
2020-01-27 23:50:59 +01:00
2022-04-28 18:40:37 +02:00
const features : EventBuildFeatureUsage [ ] = [
{
featureName : 'experimental/optimizeCss' ,
invocationCount : config.experimental.optimizeCss ? 1 : 0 ,
} ,
2022-05-11 21:18:44 +02:00
{
featureName : 'experimental/nextScriptWorkers' ,
invocationCount : config.experimental.nextScriptWorkers ? 1 : 0 ,
} ,
2022-04-28 18:40:37 +02:00
{
featureName : 'optimizeFonts' ,
invocationCount : config.optimizeFonts ? 1 : 0 ,
} ,
]
telemetry . record (
features . map ( ( feature ) = > {
return {
eventName : EVENT_BUILD_FEATURE_USAGE ,
payload : feature ,
}
} )
)
2019-06-14 02:08:19 +02:00
2020-12-21 17:02:41 +01:00
await promises . writeFile (
2022-04-28 18:40:37 +02:00
path . join ( distDir , SERVER_FILES_MANIFEST ) ,
JSON . stringify ( requiredServerFiles ) ,
2020-12-21 17:02:41 +01:00
'utf8'
)
2021-02-17 23:52:43 +01:00
2022-04-28 18:40:37 +02:00
const middlewareManifest : MiddlewareManifest = JSON . parse (
await promises . readFile (
2022-10-18 18:47:13 +02:00
path . join ( distDir , SERVER_DIRECTORY , MIDDLEWARE_MANIFEST ) ,
2022-04-28 18:40:37 +02:00
'utf8'
2022-01-27 23:22:20 +01:00
)
2021-02-17 23:52:43 +01:00
)
2021-10-11 23:15:18 +02:00
2022-04-28 18:40:37 +02:00
const outputFileTracingRoot =
config . experimental . outputFileTracingRoot || dir
2020-12-21 17:02:41 +01:00
2022-06-24 21:58:35 +02:00
if ( config . output === 'standalone' ) {
2022-04-28 18:40:37 +02:00
await nextBuildSpan
. traceChild ( 'copy-traced-files' )
. traceAsyncFn ( async ( ) = > {
await copyTracedFiles (
dir ,
distDir ,
2022-08-10 21:31:01 +02:00
pageKeys . pages ,
2022-04-28 18:40:37 +02:00
outputFileTracingRoot ,
requiredServerFiles . config ,
middlewareManifest
)
} )
}
2022-01-04 01:47:18 +01:00
2022-04-28 18:40:37 +02:00
const finalPrerenderRoutes : { [ route : string ] : SsgRoute } = { }
2022-09-19 20:05:28 +02:00
const finalDynamicRoutes : PrerenderManifest [ 'dynamicRoutes' ] = { }
2022-04-28 18:40:37 +02:00
const tbdPrerenderRoutes : string [ ] = [ ]
let ssgNotFoundPaths : string [ ] = [ ]
2021-11-09 18:03:20 +01:00
2022-04-28 18:40:37 +02:00
if ( postCompileSpinner ) postCompileSpinner . stopAndPersist ( )
2021-11-09 18:03:20 +01:00
2022-04-28 18:40:37 +02:00
const { i18n } = config
2020-12-21 17:02:41 +01:00
2022-04-28 18:40:37 +02:00
const usedStaticStatusPages = STATIC_STATUS_PAGES . filter (
( page ) = >
mappedPages [ page ] &&
mappedPages [ page ] . startsWith ( 'private-next-pages' )
)
usedStaticStatusPages . forEach ( ( page ) = > {
if ( ! ssgPages . has ( page ) && ! customAppGetInitialProps ) {
staticPages . add ( page )
}
} )
2020-12-21 17:02:41 +01:00
2022-04-28 18:40:37 +02:00
const hasPages500 = usedStaticStatusPages . includes ( '/500' )
const useDefaultStatic500 =
! hasPages500 && ! hasNonStaticErrorPage && ! customAppGetInitialProps
2021-01-27 12:24:00 +01:00
2022-04-28 18:40:37 +02:00
const combinedPages = [ . . . staticPages , . . . ssgPages ]
2021-02-22 17:29:50 +01:00
2022-09-19 20:05:28 +02:00
// we need to trigger automatic exporting when we have
// - static 404/500
// - getStaticProps paths
// - experimental app is enabled
if (
combinedPages . length > 0 ||
useStatic404 ||
useDefaultStatic500 ||
2022-10-07 00:16:42 +02:00
isAppDirEnabled
2022-09-19 20:05:28 +02:00
) {
2022-04-28 18:40:37 +02:00
const staticGenerationSpan =
nextBuildSpan . traceChild ( 'static-generation' )
await staticGenerationSpan . traceAsyncFn ( async ( ) = > {
detectConflictingPaths (
[
. . . combinedPages ,
2022-08-10 21:31:01 +02:00
. . . pageKeys . pages . filter ( ( page ) = > ! combinedPages . includes ( page ) ) ,
2022-04-28 18:40:37 +02:00
] ,
ssgPages ,
additionalSsgPaths
)
const exportApp : typeof import ( '../export' ) . default =
require ( '../export' ) . default
const exportOptions = {
silent : false ,
buildExport : true ,
threads : config.experimental.cpus ,
pages : combinedPages ,
outdir : path.join ( distDir , 'export' ) ,
statusMessage : 'Generating static pages' ,
exportPageWorker : sharedPool
? staticWorkers . exportPage . bind ( staticWorkers )
: undefined ,
endWorker : sharedPool
? async ( ) = > {
await staticWorkers . end ( )
}
: undefined ,
2022-09-03 02:13:47 +02:00
appPaths ,
2022-04-28 18:40:37 +02:00
}
const exportConfig : any = {
. . . config ,
initialPageRevalidationMap : { } ,
pageDurationMap : { } ,
ssgNotFoundPaths : [ ] as string [ ] ,
// Default map will be the collection of automatic statically exported
// pages and incremental pages.
// n.b. we cannot handle this above in combinedPages because the dynamic
// page must be in the `pages` array, but not in the mapping.
exportPathMap : ( defaultMap : any ) = > {
// Dynamically routed pages should be prerendered to be used as
// a client-side skeleton (fallback) while data is being fetched.
// This ensures the end-user never sees a 500 or slow response from the
// server.
//
// Note: prerendering disables automatic static optimization.
ssgPages . forEach ( ( page ) = > {
if ( isDynamicRoute ( page ) ) {
tbdPrerenderRoutes . push ( page )
if ( ssgStaticFallbackPages . has ( page ) ) {
// Override the rendering for the dynamic page to be treated as a
// fallback render.
if ( i18n ) {
defaultMap [ ` / ${ i18n . defaultLocale } ${ page } ` ] = {
page ,
query : { __nextFallback : true } ,
}
} else {
defaultMap [ page ] = {
page ,
query : { __nextFallback : true } ,
}
2021-03-12 09:36:28 +01:00
}
} else {
2022-04-28 18:40:37 +02:00
// Remove dynamically routed pages from the default path map when
// fallback behavior is disabled.
delete defaultMap [ page ]
2020-12-21 17:02:41 +01:00
}
2021-03-12 09:36:28 +01:00
}
} )
2022-04-28 18:40:37 +02:00
// 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 ] = {
page ,
query : { __nextSsgPath : encodedRoutes?. [ routeIdx ] } ,
}
} )
} )
2021-01-10 02:12:13 +01:00
2022-04-28 18:40:37 +02:00
if ( useStatic404 ) {
defaultMap [ '/404' ] = {
page : hasPages404 ? '/404' : '/_error' ,
}
2021-03-12 09:36:28 +01:00
}
2020-01-20 15:10:24 +01:00
2022-04-28 18:40:37 +02:00
if ( useDefaultStatic500 ) {
defaultMap [ '/500' ] = {
page : '/_error' ,
}
2021-03-12 09:36:28 +01:00
}
2020-10-09 11:13:05 +02:00
2022-09-19 20:05:28 +02:00
// TODO: output manifest specific to app paths and their
// revalidate periods and dynamicParams settings
appStaticPaths . forEach ( ( routes , originalAppPath ) = > {
const encodedRoutes = appStaticPathsEncoded . get ( originalAppPath )
2022-10-24 08:08:15 +02:00
const appConfig = appDefaultConfigs . get ( originalAppPath ) || { }
2022-09-19 20:05:28 +02:00
routes . forEach ( ( route , routeIdx ) = > {
defaultMap [ route ] = {
page : originalAppPath ,
query : { __nextSsgPath : encodedRoutes?. [ routeIdx ] } ,
2022-10-24 08:08:15 +02:00
_isDynamicError : appConfig.dynamic === 'error' ,
2022-09-19 20:05:28 +02:00
_isAppDir : true ,
}
} )
} )
2022-04-28 18:40:37 +02:00
if ( i18n ) {
for ( const page of [
. . . staticPages ,
. . . ssgPages ,
. . . ( useStatic404 ? [ '/404' ] : [ ] ) ,
. . . ( useDefaultStatic500 ? [ '/500' ] : [ ] ) ,
] ) {
const isSsg = ssgPages . has ( page )
const isDynamic = isDynamicRoute ( page )
const isFallback = isSsg && ssgStaticFallbackPages . has ( page )
for ( const locale of i18n . locales ) {
// skip fallback generation for SSG pages without fallback mode
if ( isSsg && isDynamic && ! isFallback ) continue
const outputPath = ` / ${ locale } ${ page === '/' ? '' : page } `
defaultMap [ outputPath ] = {
page : defaultMap [ page ] ? . page || page ,
query : { __nextLocale : locale } ,
}
2020-10-09 11:13:05 +02:00
2022-04-28 18:40:37 +02:00
if ( isFallback ) {
defaultMap [ outputPath ] . query . __nextFallback = true
}
2021-03-12 09:36:28 +01:00
}
2020-02-19 20:54:38 +01:00
2022-04-28 18:40:37 +02:00
if ( isSsg ) {
// remove non-locale prefixed variant from defaultMap
delete defaultMap [ page ]
}
2021-03-12 09:36:28 +01:00
}
2021-02-22 17:29:50 +01:00
}
2022-04-28 18:40:37 +02:00
return defaultMap
} ,
}
2020-06-17 05:40:07 +02:00
2022-04-28 18:40:37 +02:00
await exportApp ( dir , exportOptions , nextBuildSpan , exportConfig )
2019-05-22 18:36:53 +02:00
2022-04-28 18:40:37 +02:00
const postBuildSpinner = createSpinner ( {
prefixText : ` ${ Log . prefixes . info } Finalizing page optimization ` ,
} )
ssgNotFoundPaths = exportConfig . ssgNotFoundPaths
2019-08-06 22:26:01 +02:00
2022-04-28 18:40:37 +02:00
// remove server bundles that were exported
for ( const page of staticPages ) {
2022-10-18 18:47:13 +02:00
const serverBundle = getPagePath ( page , distDir )
2022-04-28 18:40:37 +02:00
await promises . unlink ( serverBundle )
}
2020-10-09 11:13:05 +02:00
2022-09-19 20:05:28 +02:00
for ( const [ originalAppPath , routes ] of appStaticPaths ) {
const page = appNormalizedPaths . get ( originalAppPath ) || ''
const appConfig = appDefaultConfigs . get ( originalAppPath ) || { }
let hasDynamicData = appConfig . revalidate === 0
routes . forEach ( ( route ) = > {
let revalidate = exportConfig . initialPageRevalidationMap [ route ]
if ( typeof revalidate === 'undefined' ) {
revalidate =
typeof appConfig . revalidate !== 'undefined'
? appConfig . revalidate
: false
}
2022-10-30 08:46:40 +01:00
// ensure revalidate is normalized correctly
if (
typeof revalidate !== 'number' &&
typeof revalidate !== 'boolean'
) {
revalidate = false
}
2022-09-19 20:05:28 +02:00
if ( revalidate !== 0 ) {
const normalizedRoute = normalizePagePath ( route )
const dataRoute = path . posix . join ( ` ${ normalizedRoute } .rsc ` )
finalPrerenderRoutes [ route ] = {
initialRevalidateSeconds : revalidate ,
srcRoute : page ,
dataRoute ,
}
} else {
hasDynamicData = true
2022-10-22 19:04:53 +02:00
// we might have determined during prerendering that this page
// used dynamic data
pageInfos . set ( route , {
. . . ( pageInfos . get ( route ) as PageInfo ) ,
isSsg : false ,
static : false ,
} )
2022-09-19 20:05:28 +02:00
}
} )
if ( ! hasDynamicData && isDynamicRoute ( originalAppPath ) ) {
const normalizedRoute = normalizePagePath ( page )
const dataRoute = path . posix . join ( ` ${ normalizedRoute } .rsc ` )
// TODO: create a separate manifest to allow enforcing
// dynamicParams for non-static paths?
finalDynamicRoutes [ page ] = {
routeRegex : normalizeRouteRegex (
getNamedRouteRegex ( page ) . re . source
) ,
dataRoute ,
// if dynamicParams are enabled treat as fallback:
// 'blocking' if not it's fallback: false
fallback : appDynamicParamPaths.has ( originalAppPath )
? null
: false ,
dataRouteRegex : normalizeRouteRegex (
getNamedRouteRegex (
dataRoute . replace ( /\.rsc$/ , '' )
) . re . source . replace ( /\(\?:\\\/\)\?\$$/ , '\\.rsc$' )
) ,
}
}
}
2022-04-28 18:40:37 +02:00
const moveExportedPage = async (
originPage : string ,
page : string ,
file : string ,
isSsg : boolean ,
ext : 'html' | 'json' ,
additionalSsgFile = false
) = > {
return staticGenerationSpan
. traceChild ( 'move-exported-page' )
. traceAsyncFn ( async ( ) = > {
file = ` ${ file } . ${ ext } `
const orig = path . join ( exportOptions . outdir , file )
2022-10-18 18:47:13 +02:00
const pagePath = getPagePath ( originPage , distDir )
2021-03-12 09:36:28 +01:00
2022-04-28 18:40:37 +02:00
const relativeDest = path
. relative (
2022-10-18 18:47:13 +02:00
path . join ( distDir , SERVER_DIRECTORY ) ,
2021-03-12 09:36:28 +01:00
path . join (
2022-04-28 18:40:37 +02:00
path . join (
pagePath ,
// strip leading / and then recurse number of nested dirs
// to place from base folder
originPage
. slice ( 1 )
. split ( '/' )
. map ( ( ) = > '..' )
. join ( '/' )
) ,
file
)
2021-03-12 09:36:28 +01:00
)
2022-04-28 18:40:37 +02:00
. replace ( /\\/g , '/' )
2021-03-12 09:36:28 +01:00
2022-04-28 18:40:37 +02:00
if (
! isSsg &&
! (
// don't add static status page to manifest if it's
// the default generated version e.g. no pages/500
(
STATIC_STATUS_PAGES . includes ( page ) &&
! usedStaticStatusPages . includes ( page )
)
2021-03-12 09:36:28 +01:00
)
2022-04-28 18:40:37 +02:00
) {
pagesManifest [ page ] = relativeDest
}
2020-10-15 23:55:38 +02:00
2022-10-18 18:47:13 +02:00
const dest = path . join ( distDir , SERVER_DIRECTORY , relativeDest )
2022-04-28 18:40:37 +02:00
const isNotFound = ssgNotFoundPaths . includes ( page )
// for SSG files with i18n the non-prerendered variants are
// output with the locale prefixed so don't attempt moving
// without the prefix
if ( ( ! i18n || additionalSsgFile ) && ! isNotFound ) {
await promises . mkdir ( path . dirname ( dest ) , { recursive : true } )
await promises . rename ( orig , dest )
} else if ( i18n && ! isSsg ) {
// this will be updated with the locale prefixed variant
// since all files are output with the locale prefix
delete pagesManifest [ page ]
}
2020-12-22 02:51:51 +01:00
2022-04-28 18:40:37 +02:00
if ( i18n ) {
if ( additionalSsgFile ) return
2020-10-09 11:13:05 +02:00
2022-04-28 18:40:37 +02:00
for ( const locale of i18n . locales ) {
const curPath = ` / ${ locale } ${ page === '/' ? '' : page } `
const localeExt = page === '/' ? path . extname ( file ) : ''
const relativeDestNoPages = relativeDest . slice (
'pages/' . length
)
2021-01-10 02:12:13 +01:00
2022-04-28 18:40:37 +02:00
if ( isSsg && ssgNotFoundPaths . includes ( curPath ) ) {
continue
}
2021-03-12 09:36:28 +01:00
2022-04-28 18:40:37 +02:00
const updatedRelativeDest = path
. join (
'pages' ,
locale + localeExt ,
// if it's the top-most index page we want it to be locale.EXT
// instead of locale/index.html
page === '/' ? '' : relativeDestNoPages
)
. replace ( /\\/g , '/' )
const updatedOrig = path . join (
exportOptions . outdir ,
2021-03-12 09:36:28 +01:00
locale + localeExt ,
2022-04-28 18:40:37 +02:00
page === '/' ? '' : file
)
const updatedDest = path . join (
distDir ,
2022-10-18 18:47:13 +02:00
SERVER_DIRECTORY ,
2022-04-28 18:40:37 +02:00
updatedRelativeDest
2021-03-12 09:36:28 +01:00
)
2021-02-22 17:29:50 +01:00
2022-04-28 18:40:37 +02:00
if ( ! isSsg ) {
pagesManifest [ curPath ] = updatedRelativeDest
}
await promises . mkdir ( path . dirname ( updatedDest ) , {
recursive : true ,
} )
await promises . rename ( updatedOrig , updatedDest )
2021-03-12 09:36:28 +01:00
}
2021-01-10 02:12:13 +01:00
}
2022-04-28 18:40:37 +02:00
} )
2021-07-16 11:21:44 +02:00
}
2022-04-28 18:40:37 +02:00
// Only move /404 to /404 when there is no custom 404 as in that case we don't know about the 404 page
if ( ! hasPages404 && useStatic404 ) {
await moveExportedPage ( '/_error' , '/404' , '/404' , false , 'html' )
}
2020-10-16 11:27:34 +02:00
2022-04-28 18:40:37 +02:00
if ( useDefaultStatic500 ) {
await moveExportedPage ( '/_error' , '/500' , '/500' , false , 'html' )
2021-03-12 09:36:28 +01:00
}
2021-01-10 02:12:13 +01:00
2022-04-28 18:40:37 +02:00
for ( const page of combinedPages ) {
const isSsg = ssgPages . has ( page )
const isStaticSsgFallback = ssgStaticFallbackPages . has ( page )
const isDynamic = isDynamicRoute ( page )
const hasAmp = hybridAmpPages . has ( page )
const file = normalizePagePath ( page )
const pageInfo = pageInfos . get ( page )
const durationInfo = exportConfig . pageDurationMap [ page ]
if ( pageInfo && durationInfo ) {
// Set Build Duration
if ( pageInfo . ssgPageRoutes ) {
pageInfo . ssgPageDurations = pageInfo . ssgPageRoutes . map (
( pagePath ) = > durationInfo [ pagePath ]
)
}
pageInfo . pageDuration = durationInfo [ page ]
}
// The dynamic version of SSG pages are only prerendered if the
// fallback is enabled. Below, we handle the specific prerenders
// of these.
const hasHtmlOutput = ! ( isSsg && isDynamic && ! isStaticSsgFallback )
2019-09-24 10:50:04 +02:00
2022-04-28 18:40:37 +02:00
if ( hasHtmlOutput ) {
await moveExportedPage ( page , page , file , isSsg , 'html' )
2021-03-12 09:36:28 +01:00
}
2021-02-22 17:29:50 +01:00
2022-04-28 18:40:37 +02:00
if ( hasAmp && ( ! isSsg || ( isSsg && ! isDynamic ) ) ) {
const ampPage = ` ${ file } .amp `
await moveExportedPage ( page , ampPage , ampPage , isSsg , 'html' )
2021-03-12 09:36:28 +01:00
2022-04-28 18:40:37 +02:00
if ( isSsg ) {
await moveExportedPage ( page , ampPage , ampPage , isSsg , 'json' )
}
}
2021-03-12 09:36:28 +01:00
2022-04-28 18:40:37 +02:00
if ( isSsg ) {
// For a non-dynamic SSG page, we must copy its data file
// from export, we already moved the HTML file above
if ( ! isDynamic ) {
await moveExportedPage ( page , page , file , isSsg , 'json' )
if ( i18n ) {
// TODO: do we want to show all locale variants in build output
for ( const locale of i18n . locales ) {
const localePage = ` / ${ locale } ${ page === '/' ? '' : page } `
finalPrerenderRoutes [ localePage ] = {
initialRevalidateSeconds :
exportConfig . initialPageRevalidationMap [ localePage ] ,
srcRoute : null ,
dataRoute : path.posix.join (
'/_next/data' ,
buildId ,
` ${ file } .json `
) ,
}
}
} else {
finalPrerenderRoutes [ page ] = {
2021-10-19 02:27:20 +02:00
initialRevalidateSeconds :
2022-04-28 18:40:37 +02:00
exportConfig . initialPageRevalidationMap [ page ] ,
2021-10-19 02:27:20 +02:00
srcRoute : null ,
dataRoute : path.posix.join (
'/_next/data' ,
buildId ,
` ${ file } .json `
) ,
2021-01-27 12:24:00 +01:00
}
}
2022-04-28 18:40:37 +02:00
// Set Page Revalidation Interval
if ( pageInfo ) {
pageInfo . initialRevalidateSeconds =
exportConfig . initialPageRevalidationMap [ page ]
2021-03-12 09:36:28 +01:00
}
2022-04-28 18:40:37 +02:00
} else {
// For a dynamic SSG page, we did not copy its data exports and only
// 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 )
2021-03-12 09:36:28 +01:00
await moveExportedPage (
page ,
2022-04-28 18:40:37 +02:00
route ,
pageFile ,
2021-03-12 09:36:28 +01:00
isSsg ,
'html' ,
true
)
await moveExportedPage (
page ,
2022-04-28 18:40:37 +02:00
route ,
pageFile ,
2021-03-12 09:36:28 +01:00
isSsg ,
'json' ,
true
)
2021-01-10 02:12:13 +01:00
2022-04-28 18:40:37 +02:00
if ( hasAmp ) {
const ampPage = ` ${ pageFile } .amp `
await moveExportedPage (
page ,
ampPage ,
ampPage ,
isSsg ,
'html' ,
true
)
await moveExportedPage (
page ,
ampPage ,
ampPage ,
isSsg ,
'json' ,
true
)
}
2021-03-12 09:36:28 +01:00
2022-04-28 18:40:37 +02:00
finalPrerenderRoutes [ route ] = {
initialRevalidateSeconds :
exportConfig . initialPageRevalidationMap [ route ] ,
srcRoute : page ,
dataRoute : path.posix.join (
'/_next/data' ,
buildId ,
` ${ normalizePagePath ( route ) } .json `
) ,
}
// Set route Revalidation Interval
if ( pageInfo ) {
pageInfo . initialRevalidateSeconds =
exportConfig . initialPageRevalidationMap [ route ]
}
2021-03-12 09:36:28 +01:00
}
2020-12-21 17:02:41 +01:00
}
2020-08-04 10:42:18 +02:00
}
2019-09-24 10:50:04 +02:00
}
2022-04-28 18:40:37 +02:00
// remove temporary export folder
await recursiveDelete ( exportOptions . outdir )
await promises . rmdir ( exportOptions . outdir )
await promises . writeFile (
manifestPath ,
JSON . stringify ( pagesManifest , null , 2 ) ,
'utf8'
)
if ( postBuildSpinner ) postBuildSpinner . stopAndPersist ( )
console . log ( )
} )
}
// ensure the worker is not left hanging
staticWorkers . close ( )
const analysisEnd = process . hrtime ( analysisBegin )
telemetry . record (
2022-08-10 21:31:01 +02:00
eventBuildOptimize ( pagesPaths , {
2022-04-28 18:40:37 +02:00
durationInSeconds : analysisEnd [ 0 ] ,
staticPageCount : staticPages.size ,
staticPropsPageCount : ssgPages.size ,
serverPropsPageCount : serverPropsPages.size ,
ssrPageCount :
2022-08-10 21:31:01 +02:00
pagesPaths . length -
2022-04-28 18:40:37 +02:00
( staticPages . size + ssgPages . size + serverPropsPages . size ) ,
hasStatic404 : useStatic404 ,
hasReportWebVitals :
namedExports ? . includes ( 'reportWebVitals' ) ? ? false ,
rewritesCount : combinedRewrites.length ,
headersCount : headers.length ,
redirectsCount : redirects.length - 1 , // reduce one for trailing slash
headersWithHasCount : headers.filter ( ( r : any ) = > ! ! r . has ) . length ,
rewritesWithHasCount : combinedRewrites.filter ( ( r : any ) = > ! ! r . has )
. length ,
redirectsWithHasCount : redirects.filter ( ( r : any ) = > ! ! r . has ) . length ,
2022-05-19 17:46:21 +02:00
middlewareCount : Object.keys ( rootPaths ) . length > 0 ? 1 : 0 ,
2022-04-28 18:40:37 +02:00
} )
)
if ( telemetryPlugin ) {
const events = eventBuildFeatureUsage ( telemetryPlugin )
telemetry . record ( events )
telemetry . record ( eventPackageUsedInGetServerSideProps ( telemetryPlugin ) )
}
2022-09-19 20:05:28 +02:00
if ( ssgPages . size > 0 || appDir ) {
2022-04-28 18:40:37 +02:00
tbdPrerenderRoutes . forEach ( ( tbdRoute ) = > {
const normalizedRoute = normalizePagePath ( tbdRoute )
const dataRoute = path . posix . join (
'/_next/data' ,
buildId ,
` ${ normalizedRoute } .json `
)
finalDynamicRoutes [ tbdRoute ] = {
2022-05-19 17:46:21 +02:00
routeRegex : normalizeRouteRegex (
getNamedRouteRegex ( tbdRoute ) . re . source
) ,
2022-04-28 18:40:37 +02:00
dataRoute ,
fallback : ssgBlockingFallbackPages.has ( tbdRoute )
? null
: ssgStaticFallbackPages . has ( tbdRoute )
? ` ${ normalizedRoute } .html `
: false ,
dataRouteRegex : normalizeRouteRegex (
2022-05-19 17:46:21 +02:00
getNamedRouteRegex (
dataRoute . replace ( /\.json$/ , '' )
) . re . source . replace ( /\(\?:\\\/\)\?\$$/ , '\\.json$' )
2022-04-28 18:40:37 +02:00
) ,
}
} )
const prerenderManifest : PrerenderManifest = {
version : 3 ,
routes : finalPrerenderRoutes ,
dynamicRoutes : finalDynamicRoutes ,
notFoundRoutes : ssgNotFoundPaths ,
preview : previewProps ,
2019-09-24 10:50:04 +02:00
}
2020-12-21 17:02:41 +01:00
2021-03-12 09:36:28 +01:00
await promises . writeFile (
2022-04-28 18:40:37 +02:00
path . join ( distDir , PRERENDER_MANIFEST ) ,
JSON . stringify ( prerenderManifest ) ,
2021-03-12 09:36:28 +01:00
'utf8'
)
2022-04-28 18:40:37 +02:00
await generateClientSsgManifest ( prerenderManifest , {
distDir ,
2020-12-21 17:02:41 +01:00
buildId ,
2022-04-28 18:40:37 +02:00
locales : config.i18n?.locales || [ ] ,
} )
} else {
const prerenderManifest : PrerenderManifest = {
version : 3 ,
routes : { } ,
dynamicRoutes : { } ,
preview : previewProps ,
notFoundRoutes : [ ] ,
2020-12-21 17:02:41 +01:00
}
2022-04-28 18:40:37 +02:00
await promises . writeFile (
path . join ( distDir , PRERENDER_MANIFEST ) ,
JSON . stringify ( prerenderManifest ) ,
'utf8'
)
2020-12-21 17:02:41 +01:00
}
2019-10-01 04:08:01 +02:00
2022-04-28 18:40:37 +02:00
const images = { . . . config . images }
const { deviceSizes , imageSizes } = images
; ( images as any ) . sizes = [ . . . deviceSizes , . . . imageSizes ]
2022-05-25 22:25:06 +02:00
; ( images as any ) . remotePatterns = (
2022-09-01 00:44:17 +02:00
config ? . images ? . remotePatterns || [ ]
2022-05-25 22:25:06 +02:00
) . map ( ( p : RemotePattern ) = > ( {
// Should be the same as matchRemotePattern()
protocol : p.protocol ,
hostname : makeRe ( p . hostname ) . source ,
port : p.port ,
pathname : makeRe ( p . pathname ? ? '**' ) . source ,
} ) )
2022-04-28 18:40:37 +02:00
await promises . writeFile (
path . join ( distDir , IMAGES_MANIFEST ) ,
JSON . stringify ( {
version : 1 ,
images ,
} ) ,
2020-12-21 17:02:41 +01:00
'utf8'
)
await promises . writeFile (
2022-04-28 18:40:37 +02:00
path . join ( distDir , EXPORT_MARKER ) ,
JSON . stringify ( {
version : 1 ,
hasExportPathMap : typeof config . exportPathMap === 'function' ,
exportTrailingSlash : config.trailingSlash === true ,
isNextImageImported : isNextImageImported === true ,
} ) ,
2020-12-21 17:02:41 +01:00
'utf8'
)
2022-04-28 18:40:37 +02:00
await promises . unlink ( path . join ( distDir , EXPORT_DETAIL ) ) . catch ( ( err ) = > {
if ( err . code === 'ENOENT' ) {
return Promise . resolve ( )
}
return Promise . reject ( err )
} )
2019-08-06 22:26:01 +02:00
2022-06-24 21:58:35 +02:00
if ( config . output === 'standalone' ) {
2022-04-28 18:40:37 +02:00
for ( const file of [
. . . requiredServerFiles . files ,
path . join ( config . distDir , SERVER_FILES_MANIFEST ) ,
. . . loadedEnvFiles . reduce < string [ ] > ( ( acc , envFile ) = > {
if ( [ '.env' , '.env.production' ] . includes ( envFile . path ) ) {
acc . push ( envFile . path )
}
return acc
} , [ ] ) ,
] ) {
const filePath = path . join ( dir , file )
const outputPath = path . join (
distDir ,
'standalone' ,
path . relative ( outputFileTracingRoot , filePath )
)
await promises . mkdir ( path . dirname ( outputPath ) , {
recursive : true ,
} )
await promises . copyFile ( filePath , outputPath )
}
await recursiveCopy (
path . join ( distDir , SERVER_DIRECTORY , 'pages' ) ,
path . join (
distDir ,
'standalone' ,
path . relative ( outputFileTracingRoot , distDir ) ,
SERVER_DIRECTORY ,
'pages'
) ,
{ overwrite : true }
2021-11-09 18:03:20 +01:00
)
}
2022-04-28 18:40:37 +02:00
staticPages . forEach ( ( pg ) = > allStaticPages . add ( pg ) )
pageInfos . forEach ( ( info : PageInfo , key : string ) = > {
allPageInfos . set ( key , info )
2021-01-10 02:12:13 +01:00
} )
2022-04-28 18:40:37 +02:00
await nextBuildSpan . traceChild ( 'print-tree-view' ) . traceAsyncFn ( ( ) = >
2022-10-18 18:47:13 +02:00
printTreeView ( pageKeys , allPageInfos , {
2022-08-10 21:31:01 +02:00
distPath : distDir ,
buildId : buildId ,
pagesDir ,
useStatic404 ,
pageExtensions : config.pageExtensions ,
appBuildManifest ,
buildManifest ,
middlewareManifest ,
gzipSize : config.experimental.gzipSize ,
} )
2020-12-21 17:02:41 +01:00
)
2019-08-18 21:45:39 +02:00
2022-04-28 18:40:37 +02:00
if ( debugOutput ) {
nextBuildSpan
. traceChild ( 'print-custom-routes' )
. traceFn ( ( ) = > printCustomRoutes ( { redirects , rewrites , headers } ) )
}
2022-03-11 23:26:46 +01:00
2022-04-28 18:40:37 +02:00
if ( config . analyticsId ) {
console . log (
chalk . bold . green ( 'Next.js Analytics' ) +
' is enabled for this production build. ' +
"You'll receive a Real Experience Score computed by all of your visitors."
)
console . log ( '' )
}
2021-08-17 09:18:47 +02:00
2022-04-28 18:40:37 +02:00
if ( Boolean ( config . experimental . nextScriptWorkers ) ) {
await nextBuildSpan
. traceChild ( 'verify-partytown-setup' )
. traceAsyncFn ( async ( ) = > {
await verifyPartytownSetup (
dir ,
join ( distDir , CLIENT_STATIC_FILES_PATH )
)
} )
}
2021-08-17 09:18:47 +02:00
2022-04-28 18:40:37 +02:00
await nextBuildSpan
. traceChild ( 'telemetry-flush' )
. traceAsyncFn ( ( ) = > telemetry . flush ( ) )
} )
return buildResult
} finally {
// Ensure we wait for lockfile patching if present
await lockfilePatchPromise . cur
// Ensure all traces are flushed before finishing the command
await flushAllTraces ( )
2022-05-03 00:20:59 +02:00
teardownTraceSubscriber ( )
2022-07-07 19:37:50 +02:00
teardownCrashReporter ( )
2022-04-28 18:40:37 +02:00
}
2018-12-03 14:18:52 +01:00
}