2020-02-12 02:16:42 +01:00
import crypto from 'crypto'
2020-05-02 06:10:19 +02:00
import { promises , writeFileSync } from 'fs'
2020-04-08 22:20:28 +02:00
import Worker from 'jest-worker'
import chalk from 'next/dist/compiled/chalk'
2020-03-29 01:18:22 +01:00
import devalue from 'next/dist/compiled/devalue'
2020-03-29 01:21:07 +01:00
import escapeStringRegexp from 'next/dist/compiled/escape-string-regexp'
2020-03-29 18:21:53 +02:00
import findUp from 'next/dist/compiled/find-up'
2019-10-01 04:08:01 +02:00
import nanoid from 'next/dist/compiled/nanoid/index.js'
2020-03-29 19:17:06 +02:00
import { pathToRegexp } from 'next/dist/compiled/path-to-regexp'
2020-04-08 22:20:28 +02:00
import path 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 {
PAGES_404_GET_INITIAL_PROPS_ERROR ,
2020-02-04 19:55:43 +01:00
PUBLIC_DIR_MIDDLEWARE_CONFLICT ,
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 , {
getRedirectStatus ,
2020-07-08 20:45:53 +02:00
normalizeRouteRegex ,
2020-06-10 22:35:34 +02:00
Redirect ,
RouteType ,
} from '../lib/load-custom-routes'
2020-04-08 22:20:28 +02:00
import { loadEnvConfig } from '../lib/load-env-config'
2019-10-01 04:08:01 +02:00
import { recursiveDelete } from '../lib/recursive-delete'
import { verifyTypeScriptSetup } from '../lib/verifyTypeScriptSetup'
2019-04-24 11:04:36 +02:00
import {
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 ,
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 ,
2019-11-15 08:19:41 +01:00
ROUTES_MANIFEST ,
2019-08-06 00:26:20 +02:00
SERVERLESS_DIRECTORY ,
2019-12-12 17:20:24 +01:00
SERVER_DIRECTORY ,
2019-09-04 16:00:54 +02:00
} from '../next-server/lib/constants'
2019-11-09 01:51:19 +01:00
import {
getRouteRegex ,
getSortedRoutes ,
2019-11-15 08:19:41 +01:00
isDynamicRoute ,
2019-11-09 01:51:19 +01:00
} from '../next-server/lib/router/utils'
2020-02-12 02:16:42 +01:00
import { __ApiPreviewProps } from '../next-server/server/api-utils'
2019-08-06 00:26:20 +02:00
import loadConfig , {
isTargetLikeServerless ,
2019-09-04 16:00:54 +02:00
} from '../next-server/server/config'
2020-05-29 10:16:22 +02:00
import { BuildManifest } from '../next-server/server/get-page-files'
2020-06-10 22:35:34 +02:00
import '../next-server/server/node-polyfill-fetch'
2020-02-12 02:16:42 +01:00
import { normalizePagePath } from '../next-server/server/normalize-page-path'
2020-06-29 20:50:32 +02:00
import { getPagePath } from '../next-server/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 ,
2019-10-10 19:18:07 +02:00
eventNextPlugins ,
2019-08-29 18:43:06 +02:00
} from '../telemetry/events'
2019-10-10 19:18:07 +02:00
import { Telemetry } from '../telemetry/storage'
2019-04-10 18:37:13 +02:00
import { CompilerResult , runCompiler } from './compiler'
import { createEntrypoints , createPagesMapping } from './entries'
2019-02-17 12:56:48 +01:00
import { generateBuildId } from './generate-build-id'
import { isWriteable } from './is-writeable'
2019-10-01 04:08:01 +02:00
import createSpinner from './spinner'
2019-04-10 18:41:59 +02:00
import {
collectPages ,
2020-04-01 11:39:25 +02:00
getJsPageSizeInKb ,
2020-05-29 10:16:22 +02:00
getNamedExports ,
2020-03-14 09:58:20 +01:00
hasCustomGetInitialProps ,
2020-02-04 19:55:43 +01:00
isPageStatic ,
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 ,
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'
2020-08-04 09:58:23 +02:00
import * as Log from './output/log'
2019-09-24 10:50:04 +02:00
const staticCheckWorker = require . resolve ( './utils' )
2019-06-26 04:54:28 +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 = {
2020-02-27 13:23:28 +01:00
version : 2
2020-01-15 21:57:07 +01:00
routes : { [ route : string ] : SsgRoute }
dynamicRoutes : { [ route : string ] : DynamicSsgRoute }
2020-02-12 02:16:42 +01:00
preview : __ApiPreviewProps
2019-08-06 22:26:01 +02:00
}
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 ,
debugOutput = false
2020-07-09 13:39:12 +02:00
) : Promise < void > {
2019-02-17 12:56:48 +01:00
if ( ! ( await isWriteable ( dir ) ) ) {
throw new Error (
2020-05-27 23:51:11 +02:00
'> Build directory is not writeable. https://err.sh/vercel/next.js/build-dir-not-writeable'
2019-02-17 12:56:48 +01:00
)
2018-12-03 14:18:52 +01:00
}
2020-03-26 13:32:41 +01:00
// attempt to load global env values so they are available in next.config.js
2020-05-26 21:01:57 +02:00
const { loadedEnvFiles } = loadEnvConfig ( dir )
2020-03-26 13:32:41 +01:00
2018-12-03 14:18:52 +01:00
const config = loadConfig ( PHASE_PRODUCTION_BUILD , dir , conf )
2019-06-06 15:57:42 +02:00
const { target } = config
2019-07-01 19:13:06 +02:00
const buildId = await generateBuildId ( config . generateBuildId , nanoid )
2019-04-03 02:22:04 +02:00
const distDir = path . join ( dir , config . distDir )
2019-11-09 23:34:53 +01:00
2020-06-09 22:16:23 +02:00
const { headers , rewrites , redirects } = await loadCustomRoutes ( config )
2019-10-10 19:18:07 +02:00
2020-05-05 17:04:57 +02:00
if ( ciEnvironment . isCI && ! ciEnvironment . hasNextSupport ) {
2019-10-18 18:40:47 +02:00
const cacheDir = path . join ( distDir , 'cache' )
2020-06-10 22:35:34 +02:00
const hasCache = await fileExists ( cacheDir )
2019-10-18 18:40:47 +02:00
if ( ! hasCache ) {
// Intentionally not piping to stderr in case people fail in CI when
// stderr is detected.
console . log (
2020-08-04 09:58:23 +02:00
` ${ Log . prefixes . warn } No build cache found. Please configure build caching for faster rebuilds. Read more: https://err.sh/next.js/no-cache `
2019-10-18 18:40:47 +02:00
)
}
}
const buildSpinner = createSpinner ( {
2020-08-04 09:58:23 +02:00
prefixText : ` ${ Log . prefixes . info } Creating an optimized production build ` ,
2019-10-18 18:40:47 +02:00
} )
2019-10-10 19:18:07 +02:00
const telemetry = new Telemetry ( { distDir } )
2019-09-16 23:06:30 +02:00
const publicDir = path . join ( dir , 'public' )
2019-09-24 17:15:14 +02:00
const pagesDir = findPagesDir ( dir )
2020-06-10 22:35:34 +02:00
const hasPublicDir = await fileExists ( publicDir )
2019-09-16 23:06:30 +02:00
2019-12-03 17:18:58 +01:00
telemetry . record (
2020-02-14 21:42:44 +01:00
eventCliSession ( PHASE_PRODUCTION_BUILD , dir , {
2019-12-03 17:18:58 +01:00
cliCommand : 'build' ,
isSrcDir : path.relative ( dir , pagesDir ! ) . startsWith ( 'src' ) ,
hasNowJson : ! ! ( await findUp ( 'now.json' , { cwd : dir } ) ) ,
isCustomServer : null ,
} )
2019-10-03 16:21:15 +02:00
)
2020-05-18 21:24:37 +02:00
eventNextPlugins ( path . resolve ( dir ) ) . then ( ( events ) = > telemetry . record ( events ) )
2019-12-03 17:18:58 +01:00
2020-05-29 10:16:22 +02:00
const ignoreTypeScriptErrors = Boolean ( config . typescript ? . ignoreBuildErrors )
await verifyTypeScriptSetup ( dir , pagesDir , ! ignoreTypeScriptErrors )
2019-09-24 17:15:14 +02:00
2019-08-18 21:45:39 +02:00
let tracer : any = null
if ( config . experimental . profiling ) {
const { createTrace } = require ( './profiler/profiler.js' )
tracer = createTrace ( path . join ( distDir , ` profile-events.json ` ) )
tracer . profiler . startProfiling ( )
}
2019-08-06 00:26:20 +02:00
const isLikeServerless = isTargetLikeServerless ( target )
2019-08-20 17:07:57 +02:00
const pagePaths : string [ ] = await collectPages (
pagesDir ,
config . pageExtensions
)
2019-04-10 18:41:59 +02:00
2019-05-22 18:36:53 +02:00
// needed for static exporting since we want to replace with HTML
2019-08-20 17:07:57 +02:00
// files
2019-05-22 18:36:53 +02:00
const allStaticPages = new Set < string > ( )
let allPageInfos = new Map < string , PageInfo > ( )
2019-04-10 18:41:59 +02:00
2020-02-12 02:16:42 +01:00
const previewProps : __ApiPreviewProps = {
previewModeId : crypto.randomBytes ( 16 ) . toString ( 'hex' ) ,
previewModeSigningKey : crypto.randomBytes ( 32 ) . toString ( 'hex' ) ,
previewModeEncryptionKey : crypto.randomBytes ( 32 ) . toString ( 'hex' ) ,
}
2019-03-27 16:51:05 +01:00
const mappedPages = createPagesMapping ( pagePaths , config . pageExtensions )
2020-02-12 02:16:42 +01:00
const entrypoints = createEntrypoints (
mappedPages ,
target ,
buildId ,
previewProps ,
2020-05-26 21:01:57 +02:00
config ,
loadedEnvFiles
2020-02-12 02:16:42 +01:00
)
2019-12-23 22:20:17 +01:00
const pageKeys = Object . keys ( mappedPages )
2019-09-16 23:06:30 +02:00
const conflictingPublicFiles : string [ ] = [ ]
2020-01-20 15:10:24 +01:00
const hasCustomErrorPage = mappedPages [ '/_error' ] . startsWith (
'private-next-pages'
)
2020-02-19 20:54:38 +01:00
const hasPages404 = Boolean (
mappedPages [ '/404' ] && mappedPages [ '/404' ] . startsWith ( 'private-next-pages' )
)
2020-03-14 09:58:20 +01:00
let hasNonStaticErrorPage : boolean
2019-09-16 23:06:30 +02:00
2019-10-06 13:44:03 +02:00
if ( hasPublicDir ) {
2020-06-10 22:35:34 +02:00
const hasPublicUnderScoreNextDir = await fileExists (
path . join ( publicDir , '_next' )
)
if ( hasPublicUnderScoreNextDir ) {
2019-10-06 13:44:03 +02:00
throw new Error ( PUBLIC_DIR_MIDDLEWARE_CONFLICT )
2020-06-10 22:35:34 +02:00
}
2019-10-06 13:44:03 +02:00
}
2019-09-16 23:06:30 +02:00
2020-06-10 22:35:34 +02:00
// 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 )
2019-09-16 23:06:30 +02:00
}
}
2020-06-10 22:35:34 +02:00
2019-09-16 23:06:30 +02:00
const numConflicting = conflictingPublicFiles . length
if ( numConflicting ) {
throw new Error (
` Conflicting public and page file ${
numConflicting === 1 ? ' was' : 's were'
2020-05-27 23:51:11 +02:00
} found . https : //err.sh/vercel/next.js/conflicting-public-file-page\n${conflictingPublicFiles.join(
2019-09-16 23:06:30 +02:00
'\n'
) } `
)
}
2020-05-27 18:45:53 +02:00
const nestedReservedPages = pageKeys . filter ( ( page ) = > {
return (
page . match ( /\/(_app|_document|_error)$/ ) && path . dirname ( page ) !== '/'
)
} )
if ( nestedReservedPages . length ) {
2020-08-04 09:58:23 +02:00
Log . warn (
` The following reserved Next.js pages were detected not directly under the pages directory: \ n ` +
2020-05-27 18:45:53 +02:00
nestedReservedPages . join ( '\n' ) +
2020-08-04 09:58:23 +02:00
` \ nSee more info here: https://err.sh/next.js/nested-reserved-page \ n `
2020-05-27 18:45:53 +02:00
)
}
2019-12-23 22:20:17 +01:00
const buildCustomRoute = (
r : {
source : string
2020-07-12 21:03:49 +02:00
basePath? : false
2019-12-23 22:20:17 +01:00
statusCode? : number
2020-07-12 21:03:49 +02:00
destination? : string
2019-12-23 22:20:17 +01:00
} ,
2020-01-01 13:47:58 +01:00
type : RouteType
2019-12-23 22:20:17 +01:00
) = > {
const keys : any [ ] = [ ]
2020-07-12 21:03:49 +02:00
if ( r . basePath !== false ) {
r . source = ` ${ config . basePath } ${ r . source } `
if ( r . destination && r . destination . startsWith ( '/' ) ) {
r . destination = ` ${ config . basePath } ${ r . destination } `
}
}
2019-12-23 22:20:17 +01:00
const routeRegex = pathToRegexp ( r . source , keys , {
strict : true ,
sensitive : false ,
delimiter : '/' , // default is `/#?`, but Next does not pass query info
} )
return {
. . . r ,
2020-01-01 13:47:58 +01:00
. . . ( type === 'redirect'
2019-12-23 22:20:17 +01:00
? {
2020-01-14 19:28:48 +01:00
statusCode : getRedirectStatus ( r as Redirect ) ,
permanent : undefined ,
2019-12-23 22:20:17 +01:00
}
: { } ) ,
2020-07-08 20:45:53 +02:00
regex : normalizeRouteRegex ( routeRegex . source ) ,
2019-12-23 22:20:17 +01:00
}
}
2020-01-27 23:50:59 +01:00
const routesManifestPath = path . join ( distDir , ROUTES_MANIFEST )
2020-08-13 14:39:36 +02:00
const routesManifest : {
version : number
pages404 : boolean
basePath : string
redirects : Array < ReturnType < typeof buildCustomRoute > >
rewrites : Array < ReturnType < typeof buildCustomRoute > >
headers : Array < ReturnType < typeof buildCustomRoute > >
dynamicRoutes : Array < {
page : string
regex : string
namedRegex? : string
routeKeys ? : { [ key : string ] : string }
} >
dataRoutes : Array < {
page : string
routeKeys ? : { [ key : string ] : string }
dataRouteRegex : string
namedDataRouteRegex? : string
} >
} = {
2020-06-16 15:49:13 +02:00
version : 3 ,
2020-02-19 20:54:38 +01:00
pages404 : true ,
2020-06-18 12:10:20 +02:00
basePath : config.basePath ,
2020-05-18 21:24:37 +02:00
redirects : redirects.map ( ( r ) = > buildCustomRoute ( r , 'redirect' ) ) ,
rewrites : rewrites.map ( ( r ) = > buildCustomRoute ( r , 'rewrite' ) ) ,
headers : headers.map ( ( r ) = > buildCustomRoute ( r , 'header' ) ) ,
2020-05-18 15:47:13 +02:00
dynamicRoutes : getSortedRoutes ( pageKeys )
. filter ( isDynamicRoute )
2020-05-18 21:24:37 +02:00
. map ( ( page ) = > {
2020-05-18 15:47:13 +02:00
const routeRegex = getRouteRegex ( page )
return {
page ,
2020-07-08 20:45:53 +02:00
regex : normalizeRouteRegex ( routeRegex . re . source ) ,
2020-06-16 15:49:13 +02:00
routeKeys : routeRegex.routeKeys ,
2020-05-18 15:47:13 +02:00
namedRegex : routeRegex.namedRegex ,
}
} ) ,
2020-08-13 14:39:36 +02:00
dataRoutes : [ ] ,
2020-01-27 23:50:59 +01:00
}
2020-05-02 06:10:19 +02:00
await promises . mkdir ( distDir , { recursive : true } )
2020-01-27 23:50:59 +01:00
// We need to write the manifest with rewrites before build
// so serverless can import the manifest
2020-05-02 06:10:19 +02:00
await promises . writeFile (
routesManifestPath ,
JSON . stringify ( routesManifest ) ,
'utf8'
)
2019-12-23 22:20:17 +01:00
2019-03-10 15:46:50 +01:00
const configs = await Promise . all ( [
getBaseWebpackConfig ( dir , {
2019-08-18 21:45:39 +02:00
tracer ,
2019-02-17 12:56:48 +01:00
buildId ,
2020-07-09 13:39:12 +02:00
reactProductionProfiling ,
2019-02-17 12:56:48 +01:00
isServer : false ,
config ,
2019-04-24 20:32:15 +02:00
target ,
2019-09-24 17:15:14 +02:00
pagesDir ,
2019-02-17 12:56:48 +01:00
entrypoints : entrypoints.client ,
2020-08-13 14:39:36 +02:00
rewrites ,
2019-02-17 12:56:48 +01:00
} ) ,
2019-03-10 15:46:50 +01:00
getBaseWebpackConfig ( dir , {
2019-08-18 21:45:39 +02:00
tracer ,
2019-02-17 12:56:48 +01:00
buildId ,
2020-07-09 13:39:12 +02:00
reactProductionProfiling ,
2019-02-17 12:56:48 +01:00
isServer : true ,
config ,
2019-04-24 20:32:15 +02:00
target ,
2019-09-24 17:15:14 +02:00
pagesDir ,
2019-02-17 12:56:48 +01:00
entrypoints : entrypoints.server ,
2020-08-13 14:39:36 +02:00
rewrites ,
2019-02-17 12:56:48 +01:00
} ) ,
2019-03-10 15:46:50 +01:00
] )
2018-12-03 14:18:52 +01:00
2019-08-13 05:13:12 +02:00
const clientConfig = configs [ 0 ]
if (
clientConfig . optimization &&
( clientConfig . optimization . minimize !== true ||
( clientConfig . optimization . minimizer &&
clientConfig . optimization . minimizer . length === 0 ) )
) {
2020-08-04 09:58:23 +02:00
Log . warn (
` Production code optimization has been disabled in your project. Read more: https://err.sh/vercel/next.js/minification-disabled `
2019-08-13 05:13:12 +02:00
)
}
2019-08-29 18:43:06 +02:00
const webpackBuildStart = process . hrtime ( )
2019-02-17 12:56:48 +01:00
let result : CompilerResult = { warnings : [ ] , errors : [ ] }
2020-05-27 23:51:11 +02:00
// TODO: why do we need this?? https://github.com/vercel/next.js/issues/8253
2019-08-06 00:26:20 +02:00
if ( isLikeServerless ) {
2019-08-13 05:13:12 +02:00
const clientResult = await runCompiler ( clientConfig )
2019-01-10 22:10:50 +01:00
// Fail build if clientResult contains errors
2019-02-17 12:56:48 +01:00
if ( clientResult . errors . length > 0 ) {
result = {
warnings : [ . . . clientResult . warnings ] ,
errors : [ . . . clientResult . errors ] ,
}
2019-01-10 22:10:50 +01:00
} else {
2019-03-10 15:46:50 +01:00
const serverResult = await runCompiler ( configs [ 1 ] )
2019-02-17 12:56:48 +01:00
result = {
warnings : [ . . . clientResult . warnings , . . . serverResult . warnings ] ,
errors : [ . . . clientResult . errors , . . . serverResult . errors ] ,
}
2019-01-10 22:10:50 +01:00
}
2018-12-11 21:46:23 +01:00
} else {
result = await runCompiler ( configs )
}
2019-08-29 18:43:06 +02:00
const webpackBuildEnd = process . hrtime ( webpackBuildStart )
2019-09-16 17:37:00 +02:00
if ( buildSpinner ) {
buildSpinner . stopAndPersist ( )
}
2019-08-29 18:43:06 +02:00
2019-02-17 12:56:48 +01:00
result = formatWebpackMessages ( result )
2018-12-03 14:18:52 +01:00
if ( result . errors . length > 0 ) {
2019-02-17 12:56:48 +01:00
// Only keep the first error. Others are often indicative
// of the same problem, but confuse the reader with noise.
if ( result . errors . length > 1 ) {
result . errors . length = 1
}
2019-03-17 13:13:29 +01:00
const error = result . errors . join ( '\n\n' )
2019-02-17 12:56:48 +01:00
console . error ( chalk . red ( 'Failed to compile.\n' ) )
2019-07-15 17:16:35 +02:00
if (
error . indexOf ( 'private-next-pages' ) > - 1 &&
error . indexOf ( 'does not contain a default export' ) > - 1
) {
2019-11-11 04:24:53 +01:00
const page_name_regex = /'private-next-pages\/(?<page_name>[^']*)'/
2019-07-15 17:16:35 +02:00
const parsed = page_name_regex . exec ( error )
const page_name = parsed && parsed . groups && parsed . groups . page_name
throw new Error (
2020-05-27 23:51:11 +02:00
` webpack build failed: found page without a React Component as default export in pages/ ${ page_name } \ n \ nSee https://err.sh/vercel/next.js/page-without-valid-component for more info. `
2019-07-15 17:16:35 +02:00
)
}
2019-03-17 13:13:29 +01:00
console . error ( error )
2019-02-17 12:56:48 +01:00
console . error ( )
2019-03-17 13:13:29 +01:00
2019-11-02 02:00:56 +01:00
if (
error . indexOf ( 'private-next-pages' ) > - 1 ||
error . indexOf ( '__next_polyfill__' ) > - 1
) {
2019-03-27 16:51:05 +01:00
throw new Error (
2020-06-20 21:59:47 +02:00
'> webpack config.resolve.alias was incorrectly overridden. https://err.sh/vercel/next.js/invalid-resolve-alias'
2019-03-27 16:51:05 +01:00
)
2019-03-17 13:13:29 +01:00
}
2018-12-03 14:18:52 +01:00
throw new Error ( '> Build failed because of webpack errors' )
2019-02-17 12:56:48 +01:00
} else {
2019-12-03 17:18:58 +01:00
telemetry . record (
2020-02-04 19:55:43 +01:00
eventBuildCompleted ( pagePaths , {
2019-12-03 17:18:58 +01:00
durationInSeconds : webpackBuildEnd [ 0 ] ,
} )
2019-08-29 18:43:06 +02:00
)
2020-02-11 23:09:00 +01:00
if ( result . warnings . length > 0 ) {
2020-08-04 09:58:23 +02:00
Log . warn ( 'Compiled with warnings\n' )
2020-02-11 23:09:00 +01:00
console . warn ( result . warnings . join ( '\n\n' ) )
console . warn ( )
} else {
2020-08-04 09:58:23 +02:00
Log . info ( 'Compiled successfully' )
2020-02-11 23:09:00 +01:00
}
2018-12-03 14:18:52 +01:00
}
2020-08-04 09:58:23 +02:00
const postCompileSpinner = createSpinner ( {
prefixText : ` ${ Log . prefixes . info } Collecting page data ` ,
2019-09-16 17:37:00 +02:00
} )
2019-05-29 13:57:26 +02:00
const manifestPath = path . join (
distDir ,
2019-08-06 00:26:20 +02:00
isLikeServerless ? SERVERLESS_DIRECTORY : SERVER_DIRECTORY ,
2019-05-29 13:57:26 +02:00
PAGES_MANIFEST
)
2019-09-19 18:16:51 +02:00
const buildManifestPath = path . join ( distDir , BUILD_MANIFEST )
2019-05-22 18:36:53 +02:00
2020-01-15 21:57:07 +01:00
const ssgPages = new Set < string > ( )
2020-08-04 17:10:31 +02:00
const ssgStaticFallbackPages = new Set < string > ( )
const ssgBlockingFallbackPages = new Set < string > ( )
2019-05-22 18:36:53 +02:00
const staticPages = new Set < string > ( )
2019-06-14 02:08:19 +02:00
const invalidPages = new Set < string > ( )
2019-08-12 03:56:57 +02:00
const hybridAmpPages = new Set < string > ( )
2020-01-27 23:50:59 +01:00
const serverPropsPages = new Set < string > ( )
2020-01-15 21:57:07 +01:00
const additionalSsgPaths = new Map < string , Array < string > > ( )
2019-05-22 18:36:53 +02:00
const pageInfos = new Map < string , PageInfo > ( )
2020-05-02 06:10:19 +02:00
const pagesManifest = JSON . parse (
await promises . readFile ( manifestPath , 'utf8' )
2020-05-25 23:15:56 +02:00
) as PagesManifest
2020-05-02 06:10:19 +02:00
const buildManifest = JSON . parse (
await promises . readFile ( buildManifestPath , 'utf8' )
2020-05-25 23:15:56 +02:00
) as BuildManifest
2019-09-19 18:16:51 +02:00
2019-05-22 18:36:53 +02:00
let customAppGetInitialProps : boolean | undefined
2020-05-20 20:44:39 +02:00
let namedExports : Array < string > | undefined
2019-05-22 18:36:53 +02:00
process . env . NEXT_PHASE = PHASE_PRODUCTION_BUILD
2019-05-01 22:31:08 +02:00
2019-08-24 20:55:43 +02:00
const staticCheckWorkers = new Worker ( staticCheckWorker , {
numWorkers : config.experimental.cpus ,
2019-10-29 01:01:25 +01:00
enableWorkerThreads : config.experimental.workerThreads ,
2020-01-25 00:41:00 +01:00
} ) as Worker & { isPageStatic : typeof isPageStatic }
2019-10-31 12:38:09 +01:00
staticCheckWorkers . getStdout ( ) . pipe ( process . stdout )
staticCheckWorkers . getStderr ( ) . pipe ( process . stderr )
2019-05-01 22:31:08 +02:00
2020-03-14 09:58:20 +01:00
const runtimeEnvConfig = {
publicRuntimeConfig : config.publicRuntimeConfig ,
serverRuntimeConfig : config.serverRuntimeConfig ,
}
hasNonStaticErrorPage =
hasCustomErrorPage &&
( await hasCustomGetInitialProps (
2020-06-15 16:41:17 +02:00
getPagePath ( '/_error' , distDir , isLikeServerless ) ,
2020-05-27 07:31:57 +02:00
runtimeEnvConfig ,
false
2020-03-14 09:58:20 +01:00
) )
2019-08-29 18:43:06 +02:00
const analysisBegin = process . hrtime ( )
2019-06-26 04:54:28 +02:00
await Promise . all (
2020-05-18 21:24:37 +02:00
pageKeys . map ( async ( page ) = > {
2020-02-05 22:10:39 +01:00
const actualPage = normalizePagePath ( page )
2020-04-01 11:39:25 +02:00
const [ selfSize , allSize ] = await getJsPageSizeInKb (
2019-09-19 18:16:51 +02:00
actualPage ,
distDir ,
buildManifest ,
config . experimental . modern
)
2019-06-10 20:46:30 +02:00
2019-12-12 10:45:45 +01:00
let isSsg = false
2019-12-14 20:23:04 +01:00
let isStatic = false
let isHybridAmp = false
2019-12-12 10:45:45 +01:00
let ssgPageRoutes : string [ ] | null = null
2019-05-22 18:36:53 +02:00
2019-06-28 22:01:11 +02:00
const nonReservedPage = ! page . match ( /^\/(_app|_error|_document|api)/ )
2020-06-15 16:41:17 +02:00
if ( nonReservedPage ) {
const serverBundle = getPagePath ( page , distDir , isLikeServerless )
if ( customAppGetInitialProps === undefined ) {
customAppGetInitialProps = hasCustomGetInitialProps (
isLikeServerless
? serverBundle
: getPagePath ( '/_app' , distDir , isLikeServerless ) ,
runtimeEnvConfig ,
true
2019-07-05 17:57:16 +02:00
)
2020-06-15 16:41:17 +02:00
namedExports = getNamedExports (
isLikeServerless
? serverBundle
: getPagePath ( '/_app' , distDir , isLikeServerless ) ,
runtimeEnvConfig
2019-05-29 13:57:26 +02:00
)
2020-06-15 16:41:17 +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://err.sh/next.js/opt-out-auto-static-optimization\n'
)
}
2019-06-28 22:01:11 +02:00
}
2019-06-26 04:54:28 +02:00
2019-06-28 22:01:11 +02:00
try {
2020-06-01 23:00:22 +02:00
let workerResult = await staticCheckWorkers . isPageStatic (
2019-09-24 10:50:04 +02:00
page ,
2019-08-24 20:55:43 +02:00
serverBundle ,
2019-09-24 10:50:04 +02:00
runtimeEnvConfig
)
2019-05-22 18:36:53 +02:00
2020-06-01 23:00:22 +02:00
if ( workerResult . isHybridAmp ) {
2019-12-14 20:23:04 +01:00
isHybridAmp = true
2019-08-12 03:56:57 +02:00
hybridAmpPages . add ( page )
}
2020-06-01 23:00:22 +02:00
if ( workerResult . hasStaticProps ) {
2020-01-15 21:57:07 +01:00
ssgPages . add ( page )
2019-12-12 10:45:45 +01:00
isSsg = true
2019-09-24 10:50:04 +02:00
2020-06-01 23:00:22 +02:00
if ( workerResult . prerenderRoutes ) {
additionalSsgPaths . set ( page , workerResult . prerenderRoutes )
ssgPageRoutes = workerResult . prerenderRoutes
2019-09-24 10:50:04 +02:00
}
2020-08-04 17:10:31 +02:00
if ( workerResult . prerenderFallback === 'unstable_blocking' ) {
ssgBlockingFallbackPages . add ( page )
} else if ( workerResult . prerenderFallback === true ) {
ssgStaticFallbackPages . add ( page )
2020-02-27 13:23:28 +01:00
}
2020-06-01 23:00:22 +02:00
} else if ( workerResult . hasServerProps ) {
2020-01-27 23:50:59 +01:00
serverPropsPages . add ( page )
2020-06-01 23:00:22 +02:00
} else if (
workerResult . isStatic &&
customAppGetInitialProps === false
) {
2019-06-28 22:01:11 +02:00
staticPages . add ( page )
isStatic = true
2019-06-14 02:08:19 +02:00
}
2020-02-01 15:47:42 +01:00
if ( hasPages404 && page === '/404' ) {
2020-06-01 23:00:22 +02:00
if ( ! workerResult . isStatic && ! workerResult . hasStaticProps ) {
2020-02-01 15:47:42 +01:00
throw new Error ( PAGES_404_GET_INITIAL_PROPS_ERROR )
}
// we need to ensure the 404 lambda is present since we use
// it when _app has getInitialProps
2020-06-01 23:00:22 +02:00
if ( customAppGetInitialProps && ! workerResult . hasStaticProps ) {
2020-02-01 15:47:42 +01:00
staticPages . delete ( page )
}
}
2019-06-28 22:01:11 +02:00
} catch ( err ) {
if ( err . message !== 'INVALID_DEFAULT_EXPORT' ) throw err
invalidPages . add ( page )
2019-05-31 02:34:05 +02:00
}
2019-05-22 18:36:53 +02:00
}
2019-12-12 10:45:45 +01:00
pageInfos . set ( page , {
2020-01-09 19:49:52 +01:00
size : selfSize ,
totalSize : allSize ,
2019-12-12 10:45:45 +01:00
static : isStatic ,
isSsg ,
2019-12-14 20:23:04 +01:00
isHybridAmp ,
2019-12-12 10:45:45 +01:00
ssgPageRoutes ,
2020-08-04 10:42:18 +02:00
initialRevalidateSeconds : false ,
2019-12-12 10:45:45 +01:00
} )
2019-06-26 04:54:28 +02:00
} )
)
2019-08-24 20:55:43 +02:00
staticCheckWorkers . end ( )
2019-04-24 10:48:43 +02:00
2020-02-26 20:35:02 +01:00
if ( serverPropsPages . size > 0 || ssgPages . size > 0 ) {
2020-01-27 23:50:59 +01:00
// We update the routes manifest after the build with the
2020-02-26 20:35:02 +01:00
// data routes since we can't determine these until after build
routesManifest . dataRoutes = getSortedRoutes ( [
. . . serverPropsPages ,
. . . ssgPages ,
2020-05-18 21:24:37 +02:00
] ) . map ( ( page ) = > {
2020-02-05 22:10:39 +01:00
const pagePath = normalizePagePath ( page )
2020-01-27 23:50:59 +01:00
const dataRoute = path . posix . join (
'/_next/data' ,
buildId ,
2020-02-05 22:10:39 +01:00
` ${ pagePath } .json `
2020-01-27 23:50:59 +01:00
)
2020-04-28 09:59:47 +02:00
let dataRouteRegex : string
let namedDataRouteRegex : string | undefined
2020-06-16 15:49:13 +02:00
let routeKeys : { [ named : string ] : string } | undefined
2020-04-28 09:59:47 +02:00
if ( isDynamicRoute ( page ) ) {
const routeRegex = getRouteRegex ( dataRoute . replace ( /\.json$/ , '' ) )
2020-07-08 20:45:53 +02:00
dataRouteRegex = normalizeRouteRegex (
routeRegex . re . source . replace ( /\(\?:\\\/\)\?\$$/ , '\\.json$' )
2020-04-28 09:59:47 +02:00
)
namedDataRouteRegex = routeRegex . namedRegex ! . replace (
/\(\?:\/\)\?\$$/ ,
'\\.json$'
)
2020-06-16 15:49:13 +02:00
routeKeys = routeRegex . routeKeys
2020-04-28 09:59:47 +02:00
} else {
2020-07-08 20:45:53 +02:00
dataRouteRegex = normalizeRouteRegex (
new RegExp (
` ^ ${ path . posix . join (
'/_next/data' ,
escapeStringRegexp ( buildId ) ,
` ${ pagePath } .json `
) } $ `
) . source
)
2020-04-28 09:59:47 +02:00
}
2020-02-26 20:35:02 +01:00
return {
2020-01-27 23:50:59 +01:00
page ,
2020-04-28 09:59:47 +02:00
routeKeys ,
dataRouteRegex ,
namedDataRouteRegex ,
2020-01-27 23:50:59 +01:00
}
2020-02-26 20:35:02 +01:00
} )
2020-01-27 23:50:59 +01:00
2020-05-02 06:10:19 +02:00
await promises . writeFile (
2020-01-27 23:50:59 +01:00
routesManifestPath ,
JSON . stringify ( routesManifest ) ,
'utf8'
)
}
2020-08-13 14:39:36 +02:00
2020-01-20 15:10:24 +01: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 =
2020-03-14 09:58:20 +01:00
! customAppGetInitialProps && ( ! hasNonStaticErrorPage || hasPages404 )
2020-01-20 15:10:24 +01:00
2019-06-14 02:08:19 +02:00
if ( invalidPages . size > 0 ) {
throw new Error (
2019-10-11 16:39:38 +02:00
` Build optimization failed: found page ${
2019-06-14 02:08:19 +02:00
invalidPages . size === 1 ? '' : 's'
2019-07-15 17:16:35 +02:00
} without a React Component as default export in \ n $ { [ . . . invalidPages ]
2020-05-18 21:24:37 +02:00
. map ( ( pg ) = > ` pages ${ pg } ` )
2019-06-14 02:08:19 +02:00
. join (
'\n'
2020-05-27 23:51:11 +02:00
) } \ n \ nSee https : //err.sh/vercel/next.js/page-without-valid-component for more info.\n`
2019-06-14 02:08:19 +02:00
)
}
2019-08-20 17:07:57 +02:00
await writeBuildId ( distDir , buildId )
2019-11-09 01:51:19 +01:00
2020-01-15 21:57:07 +01:00
const finalPrerenderRoutes : { [ route : string ] : SsgRoute } = { }
2019-09-27 22:34:37 +02:00
const tbdPrerenderRoutes : string [ ] = [ ]
2019-05-22 18:36:53 +02:00
2020-08-04 09:58:23 +02:00
if ( postCompileSpinner ) postCompileSpinner . stopAndPersist ( )
2020-01-20 15:10:24 +01:00
if ( staticPages . size > 0 || ssgPages . size > 0 || useStatic404 ) {
2020-01-15 21:57:07 +01:00
const combinedPages = [ . . . staticPages , . . . ssgPages ]
2019-05-22 18:36:53 +02:00
const exportApp = require ( '../export' ) . default
const exportOptions = {
2020-08-04 09:58:23 +02:00
silent : false ,
2019-05-22 18:36:53 +02:00
buildExport : true ,
2019-10-29 01:01:25 +01:00
threads : config.experimental.cpus ,
2019-08-06 22:26:01 +02:00
pages : combinedPages ,
2019-05-22 18:36:53 +02:00
outdir : path.join ( distDir , 'export' ) ,
2020-08-04 09:58:23 +02:00
statusMessage : 'Generating static pages' ,
2019-05-22 18:36:53 +02:00
}
2019-09-24 10:50:04 +02:00
const exportConfig : any = {
2019-05-22 18:36:53 +02:00
. . . config ,
2019-09-24 10:50:04 +02:00
initialPageRevalidationMap : { } ,
// Default map will be the collection of automatic statically exported
2020-06-28 22:58:43 +02:00
// pages and incremental pages.
2019-09-24 10:50:04 +02:00
// 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 ) = > {
2020-02-13 02:06:07 +01:00
// 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.
2019-09-24 10:50:04 +02:00
//
// Note: prerendering disables automatic static optimization.
2020-05-18 21:24:37 +02:00
ssgPages . forEach ( ( page ) = > {
2019-09-24 10:50:04 +02:00
if ( isDynamicRoute ( page ) ) {
2019-09-27 22:34:37 +02:00
tbdPrerenderRoutes . push ( page )
2020-02-13 02:06:07 +01:00
2020-08-04 17:10:31 +02:00
if ( ssgStaticFallbackPages . has ( page ) ) {
2020-02-27 13:23:28 +01:00
// Override the rendering for the dynamic page to be treated as a
// fallback render.
defaultMap [ page ] = { page , query : { __nextFallback : true } }
} else {
// Remove dynamically routed pages from the default path map when
// fallback behavior is disabled.
delete defaultMap [ page ]
}
2019-09-24 10:50:04 +02:00
}
} )
// Append the "well-known" routes we should prerender for, e.g. blog
// post slugs.
2020-01-15 21:57:07 +01:00
additionalSsgPaths . forEach ( ( routes , page ) = > {
2020-05-18 21:24:37 +02:00
routes . forEach ( ( route ) = > {
2019-09-24 10:50:04 +02:00
defaultMap [ route ] = { page }
} )
} )
2020-01-20 15:10:24 +01:00
if ( useStatic404 ) {
2020-02-19 20:54:38 +01:00
defaultMap [ '/404' ] = {
2020-02-01 15:47:42 +01:00
page : hasPages404 ? '/404' : '/_error' ,
}
2020-01-20 15:10:24 +01:00
}
2019-09-24 10:50:04 +02:00
return defaultMap
} ,
2020-08-03 16:03:11 +02:00
trailingSlash : false ,
2019-05-22 18:36:53 +02:00
}
2020-02-19 20:54:38 +01:00
2019-05-22 18:36:53 +02:00
await exportApp ( dir , exportOptions , exportConfig )
2020-08-04 09:58:23 +02:00
const postBuildSpinner = createSpinner ( {
prefixText : ` ${ Log . prefixes . info } Finalizing page optimization ` ,
} )
2019-05-22 18:36:53 +02:00
// remove server bundles that were exported
for ( const page of staticPages ) {
2020-06-15 16:41:17 +02:00
const serverBundle = getPagePath ( page , distDir , isLikeServerless )
2020-05-02 06:10:19 +02:00
await promises . unlink ( serverBundle )
2019-05-22 18:36:53 +02:00
}
2020-06-17 05:40:07 +02:00
const serverOutputDir = path . join (
distDir ,
isLikeServerless ? SERVERLESS_DIRECTORY : SERVER_DIRECTORY
)
2019-05-22 18:36:53 +02:00
2019-09-24 10:50:04 +02:00
const moveExportedPage = async (
2020-06-17 05:40:07 +02:00
originPage : string ,
2019-09-24 10:50:04 +02:00
page : string ,
file : string ,
2020-01-15 21:57:07 +01:00
isSsg : boolean ,
2019-09-24 10:50:04 +02:00
ext : 'html' | 'json'
) = > {
file = ` ${ file } . ${ ext } `
2019-05-22 18:36:53 +02:00
const orig = path . join ( exportOptions . outdir , file )
2020-06-17 05:40:07 +02:00
const pagePath = getPagePath ( originPage , distDir , isLikeServerless )
const relativeDest = path
. relative (
serverOutputDir ,
path . join (
path . join (
pagePath ,
// strip leading / and then recurse number of nested dirs
// to place from base folder
originPage
. substr ( 1 )
. split ( '/' )
. map ( ( ) = > '..' )
. join ( '/' )
) ,
file
)
)
. replace ( /\\/g , '/' )
2019-05-22 18:36:53 +02:00
2019-08-06 22:26:01 +02:00
const dest = path . join (
distDir ,
isLikeServerless ? SERVERLESS_DIRECTORY : SERVER_DIRECTORY ,
relativeDest
)
2020-01-15 21:57:07 +01:00
if ( ! isSsg ) {
2020-06-17 05:40:07 +02:00
pagesManifest [ page ] = relativeDest
2019-08-06 22:26:01 +02:00
}
2020-05-02 06:10:19 +02:00
await promises . mkdir ( path . dirname ( dest ) , { recursive : true } )
await promises . rename ( orig , dest )
2019-05-22 18:36:53 +02:00
}
2019-08-06 22:26:01 +02:00
2020-02-19 20:54:38 +01: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 ) {
2020-06-17 05:40:07 +02:00
await moveExportedPage ( '/_error' , '/404' , '/404' , false , 'html' )
2020-01-20 15:10:24 +01:00
}
2019-08-06 22:26:01 +02:00
for ( const page of combinedPages ) {
2020-01-15 21:57:07 +01:00
const isSsg = ssgPages . has ( page )
2020-08-04 17:10:31 +02:00
const isStaticSsgFallback = ssgStaticFallbackPages . has ( page )
2019-09-24 10:50:04 +02:00
const isDynamic = isDynamicRoute ( page )
2019-08-12 03:56:57 +02:00
const hasAmp = hybridAmpPages . has ( page )
2020-07-09 06:21:49 +02:00
const file = normalizePagePath ( page )
2020-02-07 14:09:06 +01:00
2020-02-27 13:23:28 +01:00
// The dynamic version of SSG pages are only prerendered if the fallback
// is enabled. Below, we handle the specific prerenders of these.
2020-08-04 17:10:31 +02:00
if ( ! ( isSsg && isDynamic && ! isStaticSsgFallback ) ) {
2020-06-17 05:40:07 +02:00
await moveExportedPage ( page , page , file , isSsg , 'html' )
2020-02-27 13:23:28 +01:00
}
2020-02-07 14:09:06 +01:00
2020-05-19 13:58:50 +02:00
if ( hasAmp && ( ! isSsg || ( isSsg && ! isDynamic ) ) ) {
2020-06-17 05:40:07 +02:00
const ampPage = ` ${ file } .amp `
await moveExportedPage ( page , ampPage , ampPage , isSsg , 'html' )
2020-05-19 13:58:50 +02:00
if ( isSsg ) {
2020-06-17 05:40:07 +02:00
await moveExportedPage ( page , ampPage , ampPage , isSsg , 'json' )
2020-05-19 13:58:50 +02:00
}
2019-09-24 10:50:04 +02:00
}
2020-01-15 21:57:07 +01:00
if ( isSsg ) {
// For a non-dynamic SSG page, we must copy its data file from export.
2019-09-24 10:50:04 +02:00
if ( ! isDynamic ) {
2020-06-17 05:40:07 +02:00
await moveExportedPage ( page , page , file , true , 'json' )
2019-09-24 10:50:04 +02:00
finalPrerenderRoutes [ page ] = {
initialRevalidateSeconds :
exportConfig . initialPageRevalidationMap [ page ] ,
2019-10-02 15:28:38 +02:00
srcRoute : null ,
2020-02-05 22:10:39 +01:00
dataRoute : path.posix.join ( '/_next/data' , buildId , ` ${ file } .json ` ) ,
2019-09-24 10:50:04 +02:00
}
2020-08-04 10:42:18 +02:00
// Set Page Revalidation Interval
const pageInfo = pageInfos . get ( page )
if ( pageInfo ) {
pageInfo . initialRevalidateSeconds =
exportConfig . initialPageRevalidationMap [ page ]
pageInfos . set ( page , pageInfo )
}
2019-09-24 10:50:04 +02:00
} else {
2020-02-07 14:09:06 +01:00
// For a dynamic SSG page, we did not copy its data exports and only
2020-02-27 13:23:28 +01:00
// copy the fallback HTML file (if present).
2020-02-07 14:09:06 +01:00
// We must also copy specific versions of this page as defined by
2020-02-27 18:57:39 +01:00
// `getStaticPaths` (additionalSsgPaths).
2020-01-15 21:57:07 +01:00
const extraRoutes = additionalSsgPaths . get ( page ) || [ ]
2019-09-24 10:50:04 +02:00
for ( const route of extraRoutes ) {
2020-07-09 06:21:49 +02:00
const pageFile = normalizePagePath ( route )
await moveExportedPage ( page , route , pageFile , true , 'html' )
await moveExportedPage ( page , route , pageFile , true , 'json' )
2020-05-19 13:58:50 +02:00
if ( hasAmp ) {
2020-07-09 06:21:49 +02:00
const ampPage = ` ${ pageFile } .amp `
2020-06-17 05:40:07 +02:00
await moveExportedPage ( page , ampPage , ampPage , true , 'html' )
await moveExportedPage ( page , ampPage , ampPage , true , 'json' )
2020-05-19 13:58:50 +02:00
}
2019-09-24 10:50:04 +02:00
finalPrerenderRoutes [ route ] = {
initialRevalidateSeconds :
exportConfig . initialPageRevalidationMap [ route ] ,
2019-10-02 15:28:38 +02:00
srcRoute : page ,
2019-10-01 04:08:01 +02:00
dataRoute : path.posix.join (
'/_next/data' ,
2019-10-10 19:07:51 +02:00
buildId ,
2020-02-05 22:10:39 +01:00
` ${ normalizePagePath ( route ) } .json `
2019-10-01 04:08:01 +02:00
) ,
2019-09-24 10:50:04 +02:00
}
2020-08-04 10:42:18 +02:00
// Set route Revalidation Interval
const pageInfo = pageInfos . get ( route )
if ( pageInfo ) {
pageInfo . initialRevalidateSeconds =
exportConfig . initialPageRevalidationMap [ route ]
pageInfos . set ( route , pageInfo )
}
2019-09-24 10:50:04 +02:00
}
}
}
2019-08-06 22:26:01 +02:00
}
2019-05-22 18:36:53 +02:00
// remove temporary export folder
await recursiveDelete ( exportOptions . outdir )
2020-05-02 06:10:19 +02:00
await promises . rmdir ( exportOptions . outdir )
await promises . writeFile (
manifestPath ,
2020-06-04 19:32:45 +02:00
JSON . stringify ( pagesManifest , null , 2 ) ,
2020-05-02 06:10:19 +02:00
'utf8'
)
2019-11-09 23:34:53 +01:00
2020-08-04 09:58:23 +02:00
if ( postBuildSpinner ) postBuildSpinner . stopAndPersist ( )
console . log ( )
}
2019-08-06 22:26:01 +02:00
2019-08-29 18:43:06 +02:00
const analysisEnd = process . hrtime ( analysisBegin )
2019-12-03 17:18:58 +01:00
telemetry . record (
2020-02-04 16:46:22 +01:00
eventBuildOptimize ( pagePaths , {
2019-12-03 17:18:58 +01:00
durationInSeconds : analysisEnd [ 0 ] ,
staticPageCount : staticPages.size ,
2020-03-03 01:01:12 +01:00
staticPropsPageCount : ssgPages.size ,
serverPropsPageCount : serverPropsPages.size ,
ssrPageCount :
pagePaths . length -
( staticPages . size + ssgPages . size + serverPropsPages . size ) ,
2020-02-04 21:10:40 +01:00
hasStatic404 : useStatic404 ,
2020-05-20 20:44:39 +02:00
hasReportWebVitals : namedExports?.includes ( 'reportWebVitals' ) ? ? false ,
2019-12-03 17:18:58 +01:00
} )
2019-08-29 18:43:06 +02:00
)
2020-01-15 21:57:07 +01:00
if ( ssgPages . size > 0 ) {
2019-10-01 04:08:01 +02:00
const finalDynamicRoutes : PrerenderManifest [ 'dynamicRoutes' ] = { }
2020-05-18 21:24:37 +02:00
tbdPrerenderRoutes . forEach ( ( tbdRoute ) = > {
2020-02-07 14:09:06 +01:00
const normalizedRoute = normalizePagePath ( tbdRoute )
2019-10-01 04:08:01 +02:00
const dataRoute = path . posix . join (
'/_next/data' ,
2019-10-10 19:07:51 +02:00
buildId ,
2020-02-07 14:09:06 +01:00
` ${ normalizedRoute } .json `
2019-10-01 04:08:01 +02:00
)
finalDynamicRoutes [ tbdRoute ] = {
2020-07-08 20:45:53 +02:00
routeRegex : normalizeRouteRegex ( getRouteRegex ( tbdRoute ) . re . source ) ,
2019-10-01 04:08:01 +02:00
dataRoute ,
2020-08-04 17:10:31 +02:00
fallback : ssgBlockingFallbackPages.has ( tbdRoute )
? null
: ssgStaticFallbackPages . has ( tbdRoute )
2020-02-27 13:23:28 +01:00
? ` ${ normalizedRoute } .html `
: false ,
2020-07-08 20:45:53 +02:00
dataRouteRegex : normalizeRouteRegex (
getRouteRegex ( dataRoute . replace ( /\.json$/ , '' ) ) . re . source . replace (
/\(\?:\\\/\)\?\$$/ ,
'\\.json$'
)
) ,
2019-10-01 04:08:01 +02:00
}
} )
2019-09-24 10:50:04 +02:00
const prerenderManifest : PrerenderManifest = {
2020-02-27 13:23:28 +01:00
version : 2 ,
2019-09-24 10:50:04 +02:00
routes : finalPrerenderRoutes ,
2019-10-01 04:08:01 +02:00
dynamicRoutes : finalDynamicRoutes ,
2020-02-12 02:16:42 +01:00
preview : previewProps ,
2019-09-24 10:50:04 +02:00
}
2020-05-02 06:10:19 +02:00
await promises . writeFile (
2020-02-10 23:43:34 +01:00
path . join ( distDir , PRERENDER_MANIFEST ) ,
JSON . stringify ( prerenderManifest ) ,
'utf8'
)
2020-03-02 18:14:40 +01:00
await generateClientSsgManifest ( prerenderManifest , {
distDir ,
buildId ,
isModern : ! ! config . experimental . modern ,
} )
2020-02-10 23:43:34 +01:00
} else {
const prerenderManifest : PrerenderManifest = {
2020-02-27 13:23:28 +01:00
version : 2 ,
2020-02-10 23:43:34 +01:00
routes : { } ,
dynamicRoutes : { } ,
2020-02-12 02:16:42 +01:00
preview : previewProps ,
2020-02-10 23:43:34 +01:00
}
2020-05-02 06:10:19 +02:00
await promises . writeFile (
2019-08-06 22:26:01 +02:00
path . join ( distDir , PRERENDER_MANIFEST ) ,
2019-09-24 10:50:04 +02:00
JSON . stringify ( prerenderManifest ) ,
2019-08-06 22:26:01 +02:00
'utf8'
)
}
2020-05-02 06:10:19 +02:00
await promises . writeFile (
2019-12-13 20:30:22 +01:00
path . join ( distDir , EXPORT_MARKER ) ,
JSON . stringify ( {
version : 1 ,
hasExportPathMap : typeof config . exportPathMap === 'function' ,
2020-08-03 16:03:11 +02:00
exportTrailingSlash : config.trailingSlash === true ,
2019-12-13 20:30:22 +01:00
} ) ,
'utf8'
)
2020-05-18 21:24:37 +02:00
await promises . unlink ( path . join ( distDir , EXPORT_DETAIL ) ) . catch ( ( err ) = > {
2019-12-13 20:30:22 +01:00
if ( err . code === 'ENOENT' ) {
return Promise . resolve ( )
}
return Promise . reject ( err )
} )
2020-05-18 21:24:37 +02:00
staticPages . forEach ( ( pg ) = > allStaticPages . add ( pg ) )
2019-05-22 18:36:53 +02:00
pageInfos . forEach ( ( info : PageInfo , key : string ) = > {
allPageInfos . set ( key , info )
} )
2019-01-25 18:36:29 +01:00
2019-12-12 17:20:24 +01:00
await printTreeView (
Object . keys ( mappedPages ) ,
allPageInfos ,
isLikeServerless ,
{
2019-12-12 20:44:34 +01:00
distPath : distDir ,
2020-01-09 19:49:52 +01:00
buildId : buildId ,
2019-12-12 17:20:24 +01:00
pagesDir ,
2020-03-14 09:58:20 +01:00
useStatic404 ,
2019-12-12 17:20:24 +01:00
pageExtensions : config.pageExtensions ,
2019-12-12 20:44:34 +01:00
buildManifest ,
isModern : config.experimental.modern ,
2019-12-12 17:20:24 +01:00
}
)
2020-08-20 17:43:38 +02:00
if ( debugOutput ) {
printCustomRoutes ( { redirects , rewrites , headers } )
}
2019-08-18 21:45:39 +02:00
if ( tracer ) {
const parsedResults = await tracer . profiler . stopProfiling ( )
2020-05-18 21:24:37 +02:00
await new Promise ( ( resolve ) = > {
2019-08-18 21:45:39 +02:00
if ( parsedResults === undefined ) {
tracer . profiler . destroy ( )
tracer . trace . flush ( )
tracer . end ( resolve )
return
}
const cpuStartTime = parsedResults . profile . startTime
const cpuEndTime = parsedResults . profile . endTime
tracer . trace . completeEvent ( {
name : 'TaskQueueManager::ProcessTaskFromWorkQueue' ,
id : ++ tracer . counter ,
cat : [ 'toplevel' ] ,
ts : cpuStartTime ,
args : {
src_file : '../../ipc/ipc_moji_bootstrap.cc' ,
src_func : 'Accept' ,
} ,
} )
tracer . trace . completeEvent ( {
name : 'EvaluateScript' ,
id : ++ tracer . counter ,
cat : [ 'devtools.timeline' ] ,
ts : cpuStartTime ,
dur : cpuEndTime - cpuStartTime ,
args : {
data : {
url : 'webpack' ,
lineNumber : 1 ,
columnNumber : 1 ,
frame : '0xFFF' ,
} ,
} ,
} )
tracer . trace . instantEvent ( {
name : 'CpuProfile' ,
id : ++ tracer . counter ,
cat : [ 'disabled-by-default-devtools.timeline' ] ,
ts : cpuEndTime ,
args : {
data : {
cpuProfile : parsedResults.profile ,
} ,
} ,
} )
tracer . profiler . destroy ( )
tracer . trace . flush ( )
tracer . end ( resolve )
} )
}
2019-08-29 18:43:06 +02:00
2019-12-03 17:18:58 +01:00
await telemetry . flush ( )
2018-12-03 14:18:52 +01:00
}
2020-03-02 18:14:40 +01:00
2020-08-12 22:42:05 +02:00
export type ClientSsgManifest = Set < string >
2020-03-02 18:14:40 +01:00
function generateClientSsgManifest (
prerenderManifest : PrerenderManifest ,
{
buildId ,
distDir ,
isModern ,
} : { buildId : string ; distDir : string ; isModern : boolean }
) {
2020-08-12 22:42:05 +02:00
const ssgPages : ClientSsgManifest = new Set < string > ( [
2020-03-02 18:14:40 +01:00
. . . Object . entries ( prerenderManifest . routes )
// Filter out dynamic routes
. filter ( ( [ , { srcRoute } ] ) = > srcRoute == null )
. map ( ( [ route ] ) = > route ) ,
. . . Object . keys ( prerenderManifest . dynamicRoutes ) ,
] )
const clientSsgManifestPaths = [
'_ssgManifest.js' ,
isModern && '_ssgManifest.module.js' ,
]
. filter ( Boolean )
2020-05-18 21:24:37 +02:00
. map ( ( f ) = >
path . join ( ` ${ CLIENT_STATIC_FILES_PATH } / ${ buildId } ` , f as string )
)
2020-03-02 18:14:40 +01:00
const clientSsgManifestContent = ` self.__SSG_MANIFEST= ${ devalue (
ssgPages
) } ; self . __SSG_MANIFEST_CB && self . __SSG_MANIFEST_CB ( ) `
2020-05-18 21:24:37 +02:00
clientSsgManifestPaths . forEach ( ( clientSsgManifestPath ) = >
2020-05-02 06:10:19 +02:00
writeFileSync (
2020-03-02 18:14:40 +01:00
path . join ( distDir , clientSsgManifestPath ) ,
clientSsgManifestContent
)
)
}