import { BuildOptions , Config , FileBlob , FileFsRef , Files , Lambda , NowBuildError , PackageJson , PrepareCacheOptions , Prerender , } from '@vercel/build-utils' ; import { Handler , Route , Source } from '@vercel/routing-utils' ; import { convertHeaders , convertRedirects , convertRewrites , } from '@vercel/routing-utils/dist/superstatic' ; import { nodeFileTrace , NodeFileTraceReasons } from '@vercel/nft' ; import { Sema } from 'async-sema' ; import { ChildProcess , fork } from 'child_process' ; import escapeStringRegexp from 'escape-string-regexp' ; import findUp from 'find-up' ; import { lstat , pathExists , readFile , remove , writeFile } from 'fs-extra' ; import os from 'os' ; import path from 'path' ; import resolveFrom from 'resolve-from' ; import semver from 'semver' ; import url from 'url' ; import buildUtils from './build-utils' ; import createServerlessConfig from './create-serverless-config' ; import nextLegacyVersions from './legacy-versions' ; import { addLocaleOrDefault , createLambdaFromPseudoLayers , createPseudoLayer , EnvConfig , excludeFiles , ExperimentalTraceVersion , getDynamicRoutes , getExportIntent , getExportStatus , getImagesManifest , getNextConfig , getPathsInside , getPrerenderManifest , getRoutes , getRoutesManifest , getSourceFilePathFromPage , isDynamicRoute , normalizeLocalePath , normalizePackageJson , normalizePage , PseudoLayer , stringMap , syncEnvVars , validateEntrypoint , } from './utils' ; const { createLambda , debug , download , getLambdaOptionsFromFunction , getNodeVersion , getSpawnOptions , getScriptName , glob , runNpmInstall , runPackageJsonScript , execCommand , getNodeBinPath , } = buildUtils ; interface BuildParamsMeta { isDev : boolean | undefined ; env ? : EnvConfig ; buildEnv ? : EnvConfig ; } interface BuildParamsType extends BuildOptions { files : Files ; entrypoint : string ; workPath : string ; meta : BuildParamsMeta ; } export const version = 2 ; const htmlContentType = 'text/html; charset=utf-8' ; const nowDevChildProcesses = new Set < ChildProcess > ( ) ; [ 'SIGINT' , 'SIGTERM' ] . forEach ( signal => { process . once ( signal as NodeJS . Signals , ( ) => { for ( const child of nowDevChildProcesses ) { debug ( `Got ${ signal }, killing dev server child process (pid=${ child . pid })` ) ; process . kill ( child . pid , signal ) ; } process . exit ( 0 ) ; } ) ; } ) ; const MAX_AGE_ONE_YEAR = 31536000 ; /**
 * Read package.json from files
 */ async function readPackageJson ( entryPath ) { const packagePath = path . join ( entryPath , 'package.json' ) ; try { return JSON . parse ( await readFile ( packagePath , 'utf8' ) ) ; } catch ( err ) { debug ( 'package.json not found in entry' ) ; return { } ; } } /**
 * Write package.json
 */ async function writePackageJson ( workPath , packageJson ) { await writeFile ( path . join ( workPath , 'package.json' ) , JSON . stringify ( packageJson , null , 2 ) ) ; } /**
 * Write .npmrc with npm auth token
 */ async function writeNpmRc ( workPath , token ) { await writeFile ( path . join ( workPath , '.npmrc' ) , `//registry.npmjs.org/:_authToken=${ token }` ) ; } /**
 * Get the installed Next version.
 */ function getRealNextVersion ( entryPath ) { try { const nextVersion = require ( resolveFrom ( entryPath , 'next/package.json' ) ) . version ; debug ( `Detected Next.js version: ${ nextVersion }` ) ; return nextVersion ; } catch ( _ignored ) { debug ( `Could not identify real Next.js version, ensure it is defined as a project dependency.` ) ; return false ; } } /**
 * Get the package.json Next version.
 */ async function getNextVersionRange ( entryPath ) { let nextVersion = false ; const pkg = await readPackageJson ( entryPath ) ; if ( pkg . dependencies && pkg . dependencies . next ) { nextVersion = pkg . dependencies . next ; } else if ( pkg . devDependencies && pkg . devDependencies . next ) { nextVersion = pkg . devDependencies . next ; } return nextVersion ; } function isLegacyNext ( nextVersion ) { if ( nextVersion === 'canary' || nextVersion === 'latest' ) { return false ; } if ( nextLegacyVersions . indexOf ( nextVersion ) !== - 1 ) { return true ; } const maxSatisfying = semver . maxSatisfying ( nextLegacyVersions , nextVersion ) ; if ( maxSatisfying === null ) { return false ; } return true ; } const name = '[@vercel/next]' ; const urls = { } ; function startDevServer ( entryPath , runtimeEnv ) { const forked = fork ( path . join ( __dirname , 'dev-server.js' ) , [ ] , { cwd : entryPath , execArgv : [ ] , } ) ; const getUrl = ( ) => new Promise < string > ( ( resolve , reject ) => { forked . once ( 'message' , resolve ) ; forked . once ( 'error' , reject ) ; } ) ; forked . send ( { dir : entryPath , runtimeEnv } ) ; return { forked , getUrl } ; } export async function build ( { files , workPath , repoRootPath , entrypoint , config = { } as Config , meta = { } as BuildParamsMeta , } ) { validateEntrypoint ( entrypoint ) ; const lambdaCompressedByteLimit = config . maxLambdaSize || 50 * 1000 * 1000 ; let entryDirectory = path . dirname ( entrypoint ) ; const entryPath = path . join ( workPath , entryDirectory ) ; const outputDirectory = config . outputDirectory || '.next' ; const dotNextStatic = path . join ( entryPath , outputDirectory , 'static' ) ; const baseDir = repoRootPath || workPath ; await download ( files , workPath , meta ) ; let pkg = await readPackageJson ( entryPath ) ; const nextVersionRange = await getNextVersionRange ( entryPath ) ; const nodeVersion = await getNodeVersion ( entryPath , undefined , config , meta ) ; const spawnOpts = getSpawnOptions ( meta , nodeVersion ) ; spawnOpts . env = { ... spawnOpts . env , NOW_BUILDER : '1' , VERCEL : '1' , NEXT_PRIVATE_TARGET : 'experimental-serverless-trace' , INIT_CWD : entryPath , } ; const nowJsonPath = await findUp ( [ 'now.json' , 'vercel.json' ] , { cwd : entryPath , } ) ; let hasLegacyRoutes = false ; const hasFunctionsConfig = ! ! config . functions ; if ( nowJsonPath ) { const nowJsonData = JSON . parse ( await readFile ( nowJsonPath , 'utf8' ) ) ; if ( Array . isArray ( nowJsonData . routes ) && nowJsonData . routes . length > 0 ) { hasLegacyRoutes = true ; console . warn ( `WARNING: your application is being opted out of @vercel/next's optimized lambdas mode due to legacy routes in ${ path . basename ( nowJsonPath ) }. http://err.sh/vercel/vercel/next-legacy-routes-optimized-lambdas` ) ; } } if ( hasFunctionsConfig ) { console . warn ( `WARNING: Your application is being opted out of "@vercel/next" optimized lambdas mode due to \`functions\` config.\nMore info: http://err.sh/vercel/vercel/next-functions-config-optimized-lambdas` ) ; } const isSharedLambdas = ! hasLegacyRoutes && ! hasFunctionsConfig && typeof config . sharedLambdas === 'undefined' ? true : ! ! config . sharedLambdas ; if ( meta . isDev ) { let childProcess ; if ( ! urls [ entrypoint ] ) { if ( ! process . env . NODE_ENV ) { process . env . NODE_ENV = 'development' ; } const runtimeEnv = Object . assign ( { } , process . env ) ; syncEnvVars ( runtimeEnv , meta . buildEnv || { } , meta . env || { } ) ; const { forked , getUrl } = startDevServer ( entryPath , runtimeEnv ) ; urls [ entrypoint ] = await getUrl ( ) ; childProcess = forked ; nowDevChildProcesses . add ( forked ) ; debug ( `${ name } Development server for ${ entrypoint } running at ${ urls [ entrypoint ] }` ) ; } const pathsInside = getPathsInside ( entryDirectory , files ) ; return { output : { } , images : undefined , routes : await getRoutes ( entryPath , entryDirectory , pathsInside , files , urls [ entrypoint ] ) , watch : pathsInside , childProcesses : childProcess ? [ childProcess ] : [ ] , } ; } if ( await pathExists ( dotNextStatic ) ) { console . warn ( 'WARNING: You should not upload the `.next` directory.' ) ; } const isLegacy = nextVersionRange && isLegacyNext ( nextVersionRange ) ; debug ( `MODE: ${ isLegacy ? 'legacy' : 'serverless' }` ) ; if ( isLegacy ) { console . warn ( "WARNING: your application is being deployed in @vercel/next's legacy mode. http://err.sh/vercel/vercel/now-next-legacy-mode" ) ; await Promise . all ( [ remove ( path . join ( entryPath , 'yarn.lock' ) ) , remove ( path . join ( entryPath , 'package-lock.json' ) ) , ] ) ; debug ( 'Normalizing package.json' ) ; pkg = normalizePackageJson ( pkg ) ; debug ( 'Normalized package.json result: ' , pkg ) ; await writePackageJson ( entryPath , pkg ) ; } let buildScriptName = getScriptName ( pkg , [ 'vercel-build' , 'now-build' , 'build' , ] ) ; const { installCommand , buildCommand } = config ; if ( ! buildScriptName && ! buildCommand ) { console . log ( 'Your application is being built using `next build`. ' + 'If you need to define a different build step, please create a `vercel-build` script in your `package.json` ' + '(e.g. `{ "scripts": { "vercel-build": "npm run prepare && next build" } }`).' ) ; await writePackageJson ( entryPath , { ... pkg , scripts : { 'vercel-build' : 'next build' , ... pkg . scripts , } , } ) ; buildScriptName = 'vercel-build' ; } if ( process . env . NPM_AUTH_TOKEN ) { debug ( 'Found NPM_AUTH_TOKEN in environment, creating .npmrc' ) ; await writeNpmRc ( entryPath , process . env . NPM_AUTH_TOKEN ) ; } if ( typeof installCommand === 'string' ) { if ( installCommand . trim ( ) ) { console . log ( `Running "install" command: \`${ installCommand }\`...` ) ; await execCommand ( installCommand , { ... spawnOpts , env : { YARN_NODE_LINKER : 'node-modules' , ... spawnOpts . env , } , cwd : entryPath , } ) ; } else { console . log ( `Skipping "install" command...` ) ; } } else { console . log ( 'Installing dependencies...' ) ; const installTime = Date . now ( ) ; await runNpmInstall ( entryPath , [ ] , spawnOpts , meta ) ; debug ( `Install complete [${ Date . now ( ) - installTime }ms]` ) ; } const nextVersion = getRealNextVersion ( entryPath ) ; if ( ! nextVersion ) { throw new NowBuildError ( { code : 'NEXT_NO_VERSION' , message : 'No Next.js version could be detected in your project. Make sure `"next"` is installed in "dependencies" or "devDependencies"' , } ) ; } if ( ! isLegacy ) { await createServerlessConfig ( workPath , entryPath , nextVersion ) ; } const memoryToConsume = Math . floor ( os . totalmem ( ) / 1024 ** 2 ) - 128 ; const env = { ... spawnOpts . env } ; env . NODE_OPTIONS = `--max_old_space_size=${ memoryToConsume }` ; if ( buildCommand ) { const nodeBinPath = await getNodeBinPath ( { cwd : entryPath } ) ; env . PATH = `${ nodeBinPath }${ path . delimiter }${ env . PATH }` ; if ( ! env . YARN_NODE_LINKER ) { env . YARN_NODE_LINKER = 'node-modules' ; } debug ( `Added "${ nodeBinPath }" to PATH env because a build command was used.` ) ; console . log ( `Running "${ buildCommand }"` ) ; await execCommand ( buildCommand , { ... spawnOpts , cwd : entryPath , env , } ) ; } else if ( buildScriptName ) { await runPackageJsonScript ( entryPath , buildScriptName , { ... spawnOpts , env , } ) ; } let appMountPrefixNoTrailingSlash = path . posix . join ( '/' , entryDirectory ) . replace ( /\/+$/ , '' ) ; const routesManifest = await getRoutesManifest ( entryPath , outputDirectory , nextVersion ) ; const imagesManifest = await getImagesManifest ( entryPath , outputDirectory ) ; const prerenderManifest = await getPrerenderManifest ( entryPath , outputDirectory ) ; const headers = [ ] ; const rewrites = [ ] ; let redirects = [ ] ; const dataRoutes = [ ] ; let dynamicRoutes = [ ] ; let hasPages404 = false ; let buildId = '' ; let escapedBuildId = '' ; if ( isLegacy || isSharedLambdas ) { try { buildId = await readFile ( path . join ( entryPath , outputDirectory , 'BUILD_ID' ) , 'utf8' ) ; escapedBuildId = escapeStringRegexp ( buildId ) ; } catch ( err ) { throw new NowBuildError ( { code : 'NOW_NEXT_NO_BUILD_ID' , message : 'The BUILD_ID file was not found in the Output Directory. Did you forget to run "next build" in your Build Command?' , } ) ; } } if ( routesManifest ) { switch ( routesManifest . version ) { case 1 : case 2 : case 3 : { redirects . push ( ... convertRedirects ( routesManifest . redirects ) ) ; rewrites . push ( ... convertRewrites ( routesManifest . rewrites ) ) ; if ( routesManifest . headers ) { headers . push ( ... convertHeaders ( routesManifest . headers ) ) ; } if ( routesManifest . basePath && routesManifest . basePath !== '/' ) { const nextBasePath = routesManifest . basePath ; if ( ! nextBasePath . startsWith ( '/' ) ) { throw new NowBuildError ( { code : 'NEXT_BASEPATH_STARTING_SLASH' , message : 'basePath must start with `/`. Please upgrade your `@vercel/next` builder and try again. Contact support if this continues to happen.' , } ) ; } if ( nextBasePath . endsWith ( '/' ) ) { throw new NowBuildError ( { code : 'NEXT_BASEPATH_TRAILING_SLASH' , message : 'basePath must not end with `/`. Please upgrade your `@vercel/next` builder and try again. Contact support if this continues to happen.' , } ) ; } entryDirectory = path . join ( entryDirectory , nextBasePath ) ; appMountPrefixNoTrailingSlash = path . posix . join ( '/' , entryDirectory ) . replace ( /\/+$/ , '' ) ; } if ( routesManifest . dataRoutes ) { for ( const dataRoute of routesManifest . dataRoutes ) { const ssgDataRoute = prerenderManifest . fallbackRoutes [ dataRoute . page ] || prerenderManifest . blockingFallbackRoutes [ dataRoute . page ] ; if ( prerenderManifest . staticRoutes [ dataRoute . page ] || prerenderManifest . omittedRoutes . includes ( dataRoute . page ) ) { continue ; } const route = { src : ( dataRoute . namedDataRouteRegex || dataRoute . dataRouteRegex ) . replace ( /^\^/ , `^${ appMountPrefixNoTrailingSlash }` ) , dest : path . join ( '/' , entryDirectory , `${ ( ssgDataRoute && ssgDataRoute . dataRoute ) || dataRoute . page }${ dataRoute . routeKeys ? `?${ Object . keys ( dataRoute . routeKeys ) . map ( key => `${ dataRoute . routeKeys ! [ key ] }=$${ key }` ) . join ( '&' ) }` : '' }` ) , check : true , } ; const { i18n } = routesManifest ; if ( i18n ) { const origSrc = route . src ; route . src = route . src . replace ( `/${ escapedBuildId }/` , `/${ escapedBuildId }/(?${ ssgDataRoute ? '<nextLocale>' : ':' }${ i18n . locales . map ( locale => escapeStringRegexp ( locale ) ) . join ( '|' ) })/` ) ; if ( route . src === origSrc ) { route . src = route . src . replace ( `/${ escapedBuildId }` , `/${ escapedBuildId }/(?${ ssgDataRoute ? '<nextLocale>' : ':' }${ i18n . locales . map ( locale => escapeStringRegexp ( locale ) ) . join ( '|' ) })[/]?` ) ; } if ( ssgDataRoute ) { route . dest = route . dest . replace ( `/${ buildId }/` , `/${ buildId }/$nextLocale/` ) ; } } dataRoutes . push ( route ) ; } } if ( routesManifest . pages404 ) { hasPages404 = true ; } break ; } default : { throw new NowBuildError ( { code : 'NEXT_VERSION_OUTDATED' , message : 'This version of `@vercel/next` does not support the version of Next.js you are trying to deploy.\n' + 'Please upgrade your `@vercel/next` builder and try again. Contact support if this continues to happen.' , } ) ; } } } if ( imagesManifest ) { switch ( imagesManifest . version ) { case 1 : { if ( ! imagesManifest . images ) { throw new NowBuildError ( { code : 'NEXT_IMAGES_MISSING' , message : 'image-manifest.json "images" is required. Contact support if this continues to happen.' , } ) ; } const { images } = imagesManifest ; if ( ! Array . isArray ( images . domains ) ) { throw new NowBuildError ( { code : 'NEXT_IMAGES_DOMAINS' , message : 'image-manifest.json "images.domains" must be an array. Contact support if this continues to happen.' , } ) ; } if ( ! Array . isArray ( images . sizes ) ) { throw new NowBuildError ( { code : 'NEXT_IMAGES_DOMAINS' , message : 'image-manifest.json "images.sizes" must be an array. Contact support if this continues to happen.' , } ) ; } break ; } default : { throw new NowBuildError ( { code : 'NEXT_IMAGES_VERSION_UNKNOWN' , message : 'This version of `@vercel/next` does not support the version of Next.js you are trying to deploy.\n' + 'Please upgrade your `@vercel/next` builder and try again. Contact support if this continues to happen.' , } ) ; } } } const userExport = await getExportStatus ( entryPath ) ; if ( userExport ) { const exportIntent = await getExportIntent ( entryPath ) ; const { trailingSlash = false } = exportIntent || { } ; const resultingExport = await getExportStatus ( entryPath ) ; if ( ! resultingExport ) { throw new NowBuildError ( { code : 'NEXT_EXPORT_FAILED' , message : 'Exporting Next.js app failed. Please check your build logs and contact us if this continues.' , } ) ; } if ( resultingExport . success !== true ) { throw new NowBuildError ( { code : 'NEXT_EXPORT_FAILED' , message : 'Export of Next.js app failed. Please check your build logs.' , } ) ; } const outDirectory = resultingExport . outDirectory ; debug ( `next export should use trailing slash: ${ trailingSlash }` ) ; const filesAfterBuild = await glob ( '**' , outDirectory ) ; const output = { ... filesAfterBuild } ; Object . entries ( output ) . filter ( ( [ name ] ) => name . endsWith ( '.html' ) ) . forEach ( ( [ name , value ] ) => { const cleanName = name . slice ( 0 , - 5 ) ; delete output [ name ] ; output [ cleanName ] = value ; if ( value . type === 'FileBlob' || value . type === 'FileFsRef' ) { value . contentType = value . contentType || 'text/html; charset=utf-8' ; } } ) ; return { output , images : imagesManifest ?. images ?. loader === 'default' ? { domains : imagesManifest . images . domains , sizes : imagesManifest . images . sizes , } : undefined , routes : [ ... headers , ... redirects , { src : path . join ( '/' , entryDirectory , '404' ) , status : 404 , continue : true , } , { handle : 'filesystem' } , ... rewrites , { handle : 'resource' } , { src : path . join ( '/' , entryDirectory , '.*' ) , status : 404 } , { handle : 'miss' } , { src : path . join ( '/' , entryDirectory , '_next/static/(?:[^/]+/pages|pages|chunks|runtime|css|media)/.+' ) , status : 404 , check : true , dest : '$0' , } , { handle : 'hit' } , { src : path . join ( '/' , entryDirectory , `_next/static/(?:[^/]+/pages|pages|chunks|runtime|css|media|${ escapedBuildId })/.+` ) , headers : { 'cache-control' : `public,max-age=${ MAX_AGE_ONE_YEAR },immutable` , } , continue : true , important : true , } , ... ( output [ path . join ( './' , entryDirectory , '404' ) ] || output [ path . join ( './' , entryDirectory , '404/index' ) ] ? [ { handle : 'error' } as Handler , { status : 404 , src : path . join ( entryDirectory , '.*' ) , dest : path . join ( '/' , entryDirectory , '404' ) , } , ] : [ ] ) , ] , watch : [ ] , childProcesses : [ ] , } ; } if ( isLegacy ) { debug ( 'Running npm install --production...' ) ; await runNpmInstall ( entryPath , [ '--production' ] , spawnOpts , meta ) ; } if ( process . env . NPM_AUTH_TOKEN ) { await remove ( path . join ( entryPath , '.npmrc' ) ) ; } const pageLambdaRoutes = [ ] ; const dynamicPageLambdaRoutes = [ ] ; const dynamicPageLambdaRoutesMap = { } ; const pageLambdaMap = { } ; const lambdas = { } ; const prerenders = { } ; const staticPages = { } ; const dynamicPages = [ ] ; let static404Page ; let page404Path = '' ; if ( isLegacy ) { const filesAfterBuild = await glob ( '**' , entryPath ) ; debug ( 'Preparing serverless function files...' ) ; const dotNextRootFiles = await glob ( `${ outputDirectory }/*` , entryPath ) ; const dotNextServerRootFiles = await glob ( `${ outputDirectory }/server/*` , entryPath ) ; const nodeModules = excludeFiles ( await glob ( 'node_modules/**' , entryPath ) , file => file . startsWith ( 'node_modules/.cache' ) ) ; const launcherFiles = { 'now__bridge.js' : new FileFsRef ( { fsPath : path . join ( __dirname , 'now__bridge.js' ) , } ) , } ; const nextFiles = { ... nodeModules , ... dotNextRootFiles , ... dotNextServerRootFiles , ... launcherFiles , } ; if ( filesAfterBuild [ 'next.config.js' ] ) { nextFiles [ 'next.config.js' ] = filesAfterBuild [ 'next.config.js' ] ; } const pagesDir = path . join ( entryPath , outputDirectory , 'server' , 'static' , buildId , 'pages' ) ; const pages = await glob ( '**/*.js' , pagesDir ) ; const launcherPath = path . join ( __dirname , 'legacy-launcher.js' ) ; const launcherData = await readFile ( launcherPath , 'utf8' ) ; await Promise . all ( Object . keys ( pages ) . map ( async ( page ) => { if ( [ '_app.js' , '_error.js' , '_document.js' ] . includes ( page ) ) { return ; } const pathname = page . replace ( /\.js$/ , '' ) ; const launcher = launcherData . replace ( 'PATHNAME_PLACEHOLDER' , `/${ pathname . replace ( /(^|\/)index$/ , '' ) }` ) ; const pageFiles = { [ `${ outputDirectory }/server/static/${ buildId }/pages/_document.js` ] : filesAfterBuild [ `${ outputDirectory }/server/static/${ buildId }/pages/_document.js` ] , [ `${ outputDirectory }/server/static/${ buildId }/pages/_app.js` ] : filesAfterBuild [ `${ outputDirectory }/server/static/${ buildId }/pages/_app.js` ] , [ `${ outputDirectory }/server/static/${ buildId }/pages/_error.js` ] : filesAfterBuild [ `${ outputDirectory }/server/static/${ buildId }/pages/_error.js` ] , [ `${ outputDirectory }/server/static/${ buildId }/pages/${ page }` ] : filesAfterBuild [ `${ outputDirectory }/server/static/${ buildId }/pages/${ page }` ] , } ; let lambdaOptions = { } ; if ( config && config . functions ) { lambdaOptions = await getLambdaOptionsFromFunction ( { sourceFile : await getSourceFilePathFromPage ( { workPath : entryPath , page , } ) , config , } ) ; } debug ( `Creating serverless function for page: "${ page }"...` ) ; lambdas [ path . join ( entryDirectory , pathname ) ] = await createLambda ( { files : { ... nextFiles , ... pageFiles , 'now__launcher.js' : new FileBlob ( { data : launcher } ) , } , handler : 'now__launcher.launcher' , runtime : nodeVersion . runtime , ... lambdaOptions , } ) ; debug ( `Created serverless function for page: "${ page }"` ) ; } ) ) ; } else { debug ( 'Preparing serverless function files...' ) ; const pagesDir = path . join ( entryPath , outputDirectory , 'serverless' , 'pages' ) ; const pages = await glob ( '**/*.js' , pagesDir ) ; const staticPageFiles = await glob ( '**/*.html' , pagesDir ) ; Object . keys ( staticPageFiles ) . forEach ( ( page ) => { const pathname = page . replace ( /\.html$/ , '' ) ; const routeName = normalizeLocalePath ( normalizePage ( pathname ) , routesManifest ?. i18n ?. locales ) . pathname ; if ( prerenderManifest . staticRoutes [ routeName ] || prerenderManifest . fallbackRoutes [ routeName ] || prerenderManifest . staticRoutes [ normalizePage ( pathname ) ] || prerenderManifest . fallbackRoutes [ normalizePage ( pathname ) ] ) { return ; } const staticRoute = path . join ( entryDirectory , pathname ) ; staticPages [ staticRoute ] = staticPageFiles [ page ] ; staticPages [ staticRoute ] . contentType = htmlContentType ; if ( isDynamicRoute ( pathname ) ) { dynamicPages . push ( routeName ) ; return ; } } ) ; static404Page = staticPages [ path . join ( entryDirectory , '404' ) ] && hasPages404 ? path . join ( entryDirectory , '404' ) : staticPages [ path . join ( entryDirectory , '_errors/404' ) ] ? path . join ( entryDirectory , '_errors/404' ) : undefined ; const { i18n } = routesManifest || { } ; if ( ! static404Page && i18n ) { static404Page = staticPages [ path . join ( entryDirectory , i18n . defaultLocale , '404' ) ] ? path . join ( entryDirectory , i18n . defaultLocale , '404' ) : undefined ; } const pageKeys = Object . keys ( pages ) ; let hasLambdas = ! static404Page || pageKeys . length > 1 ; if ( pageKeys . length === 0 ) { const nextConfig = await getNextConfig ( workPath , entryPath ) ; if ( nextConfig != null ) { console . info ( 'Found next.config.js:' ) ; console . info ( nextConfig ) ; console . info ( ) ; } throw new NowBuildError ( { code : 'NEXT_NO_SERVERLESS_PAGES' , message : 'No serverless pages were built' , link : 'https://err.sh/vercel/vercel/now-next-no-serverless-pages-built' , } ) ; } let requiresTracing = hasLambdas ; try { if ( nextVersion && semver . lt ( nextVersion , ExperimentalTraceVersion ) ) { debug ( 'Next.js version is too old for us to trace the required dependencies.\n' + 'Assuming Next.js has handled it!' ) ; requiresTracing = false ; } } catch ( err ) { console . log ( 'Failed to check Next.js version for tracing compatibility: ' + err ) ; } let assets ; const isApiPage = ( page ) => page . replace ( /\\/g , '/' ) . match ( /serverless\/pages\/api(\/|\.js$)/ ) ; const canUsePreviewMode = Object . keys ( pages ) . some ( page => isApiPage ( pages [ page ] . fsPath ) ) ; let pseudoLayerBytes = 0 ; let apiPseudoLayerBytes = 0 ; const pseudoLayers = [ ] ; const apiPseudoLayers = [ ] ; const nonLambdaSsgPages = new Set < string > ( ) ; const onPrerenderRouteInitial = ( routeKey ) => { const pr = prerenderManifest . staticRoutes [ routeKey ] ; const { initialRevalidate , srcRoute } = pr ; const route = srcRoute || routeKey ; if ( initialRevalidate === false && ( ! canUsePreviewMode || ( hasPages404 && routeKey === '/404' ) ) && ! prerenderManifest . fallbackRoutes [ route ] && ! prerenderManifest . blockingFallbackRoutes [ route ] ) { if ( route === '/404' ) { static404Page = path . join ( entryDirectory , '404' ) ; } nonLambdaSsgPages . add ( route === '/' ? '/index' : route ) ; } } ; Object . keys ( prerenderManifest . staticRoutes ) . forEach ( route => onPrerenderRouteInitial ( route ) ) ; const tracedFiles = { } ; const apiTracedFiles = { } ; if ( requiresTracing ) { const apiPages = [ ] ; const nonApiPages = [ ] ; const pageKeys = Object . keys ( pages ) ; for ( const page of pageKeys ) { const pagePath = pages [ page ] . fsPath ; const route = `/${ page . replace ( /\.js$/ , '' ) }` ; if ( route === '/_error' && static404Page ) continue ; if ( isApiPage ( pagePath ) ) { apiPages . push ( pagePath ) ; } else if ( ! nonLambdaSsgPages . has ( route ) ) { nonApiPages . push ( pagePath ) ; } } hasLambdas = ! static404Page || apiPages . length > 0 || nonApiPages . length > 0 ; const tracingLabel = 'Traced Next.js serverless functions for external files in' ; if ( hasLambdas ) { console . time ( tracingLabel ) ; } const nftCache = Object . create ( null ) ; const { fileList : apiFileList , reasons : apiReasons , } = await nodeFileTrace ( apiPages , { base : baseDir , processCwd : entryPath , cache : nftCache , } ) ; debug ( `node-file-trace result for api routes: ${ apiFileList }` ) ; const { fileList , reasons : nonApiReasons } = await nodeFileTrace ( nonApiPages , { base : baseDir , processCwd : entryPath , cache : nftCache , } ) ; debug ( `node-file-trace result for pages: ${ fileList }` ) ; const lstatSema = new Sema ( 25 , { capacity : fileList . length + apiFileList . length , } ) ; const lstatResults = { } ; const collectTracedFiles = ( reasons , files ) => async ( file ) => { const reason = reasons [ file ] ; if ( reason && reason . type === 'initial' ) { return ; } const filePath = path . join ( baseDir , file ) ; if ( ! lstatResults [ filePath ] ) { lstatResults [ filePath ] = lstatSema . acquire ( ) . then ( ( ) => lstat ( filePath ) ) . finally ( ( ) => lstatSema . release ( ) ) ; } const { mode } = await lstatResults [ filePath ] ; files [ file ] = new FileFsRef ( { fsPath : path . join ( baseDir , file ) , mode , } ) ; } ; await Promise . all ( fileList . map ( collectTracedFiles ( nonApiReasons , tracedFiles ) ) ) ; await Promise . all ( apiFileList . map ( collectTracedFiles ( apiReasons , apiTracedFiles ) ) ) ; if ( hasLambdas ) { console . timeEnd ( tracingLabel ) ; } const zippingLabel = 'Compressed shared serverless function files' ; if ( hasLambdas ) { console . time ( zippingLabel ) ; } let pseudoLayer ; let apiPseudoLayer ; ( { pseudoLayer , pseudoLayerBytes } = await createPseudoLayer ( tracedFiles ) ) ; ( { pseudoLayer : apiPseudoLayer , pseudoLayerBytes : apiPseudoLayerBytes , } = await createPseudoLayer ( apiTracedFiles ) ) ; pseudoLayers . push ( pseudoLayer ) ; apiPseudoLayers . push ( apiPseudoLayer ) ; if ( hasLambdas ) { console . timeEnd ( zippingLabel ) ; } } else { assets = await glob ( 'assets/**' , path . join ( entryPath , outputDirectory , 'serverless' ) ) ; const assetKeys = Object . keys ( assets ) ; if ( assetKeys . length > 0 ) { debug ( 'detected (legacy) assets to be bundled with serverless function:' ) ; assetKeys . forEach ( assetFile => debug ( `\t${ assetFile }` ) ) ; debug ( '\nPlease upgrade to Next.js 9.1 to leverage modern asset handling.' ) ; } } const launcherPath = path . join ( __dirname , 'templated-launcher.js' ) ; const launcherData = await readFile ( launcherPath , 'utf8' ) ; const allLambdasLabel = `All serverless functions created in` ; if ( hasLambdas ) { console . time ( allLambdasLabel ) ; } type LambdaGroup = { pages : { [ outputName ] : { pageName : string ; pageFileName : string ; pageLayer : PseudoLayer ; } ; } ; isApiLambda : boolean ; lambdaIdentifier : string ; lambdaCombinedBytes : number ; } ; const apiLambdaGroups = [ ] ; const pageLambdaGroups = [ ] ; if ( isSharedLambdas ) { if ( pseudoLayerBytes >= lambdaCompressedByteLimit || apiPseudoLayerBytes >= lambdaCompressedByteLimit ) { throw new Error ( `Required lambda files exceed max lambda size of ${ lambdaCompressedByteLimit } bytes` ) ; } for ( const page of pageKeys ) { if ( [ '_app.js' , '_document.js' ] . includes ( page ) ) { continue ; } if ( page === '_error.js' && ( ( static404Page && staticPages [ static404Page ] ) || ( hasPages404 && pages [ '404.js' ] ) ) ) { continue ; } const pageFileName = path . normalize ( path . relative ( workPath , pages [ page ] . fsPath ) ) ; const pathname = page . replace ( /\.js$/ , '' ) ; const routeIsApi = isApiPage ( pageFileName ) ; const routeIsDynamic = isDynamicRoute ( pathname ) ; if ( routeIsDynamic ) { dynamicPages . push ( normalizePage ( pathname ) ) ; } if ( nonLambdaSsgPages . has ( `/${ pathname }` ) ) { continue ; } const outputName = path . join ( '/' , entryDirectory , pathname ) ; const lambdaGroups = routeIsApi ? apiLambdaGroups : pageLambdaGroups ; let lambdaGroupIndex = lambdaGroups . length - 1 ; const lastLambdaGroup = lambdaGroups [ lambdaGroupIndex ] ; let currentLambdaGroup = lastLambdaGroup ; if ( ! currentLambdaGroup || currentLambdaGroup . lambdaCombinedBytes >= lambdaCompressedByteLimit ) { lambdaGroupIndex ++ ; currentLambdaGroup = { pages : { } , isApiLambda : ! ! routeIsApi , lambdaCombinedBytes : ! requiresTracing ? 0 : routeIsApi ? apiPseudoLayerBytes : pseudoLayerBytes , lambdaIdentifier : path . join ( entryDirectory , `__NEXT_${ routeIsApi ? 'API' : 'PAGE' }_LAMBDA_${ lambdaGroupIndex }` ) , } ; } const addPageLambdaRoute = ( escapedOutputPath ) => { const pageLambdaRoute = { src : `^${ escapedOutputPath . replace ( /\/index$/ , '(/|/index|)' ) }/?$` , dest : `${ path . join ( '/' , currentLambdaGroup . lambdaIdentifier ) }` , headers : { 'x-nextjs-page' : outputName , } , check : true , } ; if ( routeIsDynamic ) { dynamicPageLambdaRoutes . push ( pageLambdaRoute ) ; dynamicPageLambdaRoutesMap [ outputName ] = pageLambdaRoute ; } else { pageLambdaRoutes . push ( pageLambdaRoute ) ; } } ; const { i18n } = routesManifest || { } ; if ( i18n ) { addPageLambdaRoute ( `[/]?(?:${ i18n . locales . map ( locale => escapeStringRegexp ( locale ) ) . join ( '|' ) })?${ escapeStringRegexp ( outputName ) }` ) ; } else { addPageLambdaRoute ( escapeStringRegexp ( outputName ) ) ; } if ( page === '_error.js' || ( hasPages404 && page === '404.js' ) ) { page404Path = path . join ( '/' , entryDirectory , pathname ) ; } const { pseudoLayer : pageLayer , pseudoLayerBytes : pageLayerBytes , } = await createPseudoLayer ( { [ path . join ( path . relative ( baseDir , entryPath ) , pageFileName ) ] : pages [ page ] , } ) ; currentLambdaGroup . pages [ outputName ] = { pageLayer , pageFileName , pageName : page , } ; currentLambdaGroup . lambdaCombinedBytes += pageLayerBytes ; lambdaGroups [ lambdaGroupIndex ] = currentLambdaGroup ; } } else { await Promise . all ( pageKeys . map ( async ( page ) => { if ( [ '_app.js' , '_document.js' ] . includes ( page ) ) { return ; } if ( page === '_error.js' && ( ( static404Page && staticPages [ static404Page ] ) || ( hasPages404 && pages [ '404.js' ] ) ) ) { return ; } const pathname = page . replace ( /\.js$/ , '' ) ; if ( isDynamicRoute ( pathname ) ) { dynamicPages . push ( normalizePage ( pathname ) ) ; } const pageFileName = path . normalize ( path . relative ( entryPath , pages [ page ] . fsPath ) ) ; const launcher = launcherData . replace ( /__LAUNCHER_PAGE_PATH__/g , JSON . stringify ( requiresTracing ? `./${ pageFileName }` : './page' ) ) ; const launcherFiles = { [ path . join ( path . relative ( baseDir , entryPath ) , 'now__bridge.js' ) ] : new FileFsRef ( { fsPath : path . join ( __dirname , 'now__bridge.js' ) , } ) , [ path . join ( path . relative ( baseDir , entryPath ) , 'now__launcher.js' ) ] : new FileBlob ( { data : launcher } ) , } ; const lambdaOptions = await getLambdaOptionsFromFunction ( { sourceFile : await getSourceFilePathFromPage ( { workPath : entryPath , page , } ) , config , } ) ; const outputName = path . join ( entryDirectory , pathname ) ; if ( requiresTracing ) { lambdas [ outputName ] = await createLambdaFromPseudoLayers ( { files : { ... launcherFiles , [ path . join ( path . relative ( baseDir , entryPath ) , pageFileName ) ] : pages [ page ] , } , layers : isApiPage ( pageFileName ) ? apiPseudoLayers : pseudoLayers , handler : path . join ( path . relative ( baseDir , entryPath ) , 'now__launcher.launcher' ) , runtime : nodeVersion . runtime , ... lambdaOptions , } ) ; } else { lambdas [ outputName ] = await createLambda ( { files : { ... launcherFiles , ... assets , ... tracedFiles , [ 'page.js' ] : pages [ page ] , } , handler : 'now__launcher.launcher' , runtime : nodeVersion . runtime , ... lambdaOptions , } ) ; } } ) ) ; } let dynamicPrefix = path . join ( '/' , entryDirectory ) ; dynamicPrefix = dynamicPrefix === '/' ? '' : dynamicPrefix ; dynamicRoutes = await getDynamicRoutes ( entryPath , entryDirectory , dynamicPages , false , routesManifest , new Set ( prerenderManifest . omittedRoutes ) ) . then ( arr => arr . map ( route => { const { i18n } = routesManifest || { } ; if ( i18n ) { const { pathname } = url . parse ( route . dest ! ) ; const isFallback = prerenderManifest . fallbackRoutes [ pathname ! ] ; const isBlocking = prerenderManifest . blockingFallbackRoutes [ pathname ! ] ; const isAutoExport = staticPages [ addLocaleOrDefault ( pathname ! , routesManifest ) . substr ( 1 ) ] ; const isLocalePrefixed = isFallback || isBlocking || isAutoExport ; route . src = route . src . replace ( '^' , `^${ dynamicPrefix ? `${ dynamicPrefix }[/]?` : '[/]?' }(?${ isLocalePrefixed ? '<nextLocale>' : ':' }${ i18n . locales . map ( locale => escapeStringRegexp ( locale ) ) . join ( '|' ) })?` ) ; if ( isLocalePrefixed ) { route . dest = route . dest ! . replace ( `${ path . join ( '/' , entryDirectory , '/' ) }` , `${ path . join ( '/' , entryDirectory , '$nextLocale' , '/' ) }` ) ; } } else { route . src = route . src . replace ( '^' , `^${ dynamicPrefix }` ) ; } return route ; } ) ) ; if ( isSharedLambdas ) { const launcherPath = path . join ( __dirname , 'templated-launcher-shared.js' ) ; const launcherData = await readFile ( launcherPath , 'utf8' ) ; const completeDynamicRoutes = await getDynamicRoutes ( entryPath , entryDirectory , dynamicPages , false , routesManifest ) . then ( arr => arr . map ( route => { route . src = route . src . replace ( '^' , `^${ dynamicPrefix }` ) ; return route ; } ) ) ; await Promise . all ( [ ... apiLambdaGroups , ... pageLambdaGroups ] . map ( async function buildLambdaGroup ( group ) { const groupPageKeys = Object . keys ( group . pages ) ; const launcher = launcherData . replace ( /\/\/ __LAUNCHER_PAGE_HANDLER__/g , `
              const url = require('url');

              ${ routesManifest ?. i18n ? `
                  function stripLocalePath(pathname) {
                  // first item will be empty string from splitting at first char
                  const pathnameParts = pathname.split('/')

                  ;(${ JSON . stringify ( routesManifest . i18n . locales ) }).some((locale) => {
                    if (pathnameParts[1].toLowerCase() === locale.toLowerCase()) {
                      pathnameParts.splice(1, 1)
                      pathname = pathnameParts.join('/') || '/index'
                      return true
                    }
                    return false
                  })

                  return pathname
                }
                ` : `function stripLocalePath(pathname) { return pathname }` }

              page = function(req, res) {
                try {
                  const pages = {
                    ${ groupPageKeys . map ( page => `'${ page }': () => require('./${ path . join ( './' , group . pages [ page ] . pageFileName ) }')` ) . join ( ',\n' ) }
                    ${ '' }
                  }
                  let toRender = req.headers['x-nextjs-page']

                  if (!toRender) {
                    try {
                      const { pathname } = url.parse(req.url)
                      toRender = stripLocalePath(pathname).replace(/\\/$/, '') || '/index'
                    } catch (_) {
                      // handle failing to parse url
                      res.statusCode = 400
                      return res.end('Bad Request')
                    }
                  }

                  let currentPage = pages[toRender]

                  if (
                    toRender &&
                    !currentPage
                  ) {
                    if (toRender.includes('/_next/data')) {
                      toRender = toRender
                        .replace(new RegExp('/_next/data/${ escapedBuildId }/'), '/')
                        .replace(/\\.json$/, '')

                      toRender = stripLocalePath(toRender) || '/index'
                      currentPage = pages[toRender]
                    }

                    if (!currentPage) {
                      // for prerendered dynamic routes (/blog/post-1) we need to
                      // find the match since it won't match the page directly
                      const dynamicRoutes = ${ JSON . stringify ( completeDynamicRoutes . map ( route => ( { src : route . src , dest : route . dest , } ) ) ) }

                      for (const route of dynamicRoutes) {
                        const matcher = new RegExp(route.src)

                        if (matcher.test(toRender)) {
                          toRender = url.parse(route.dest).pathname
                          currentPage = pages[toRender]
                          break
                        }
                      }
                    }
                  }

                  if (!currentPage) {
                    console.error(
                      "Failed to find matching page for", {toRender, header: req.headers['x-nextjs-page'], url: req.url }, "in lambda"
                    )
                    console.error('pages in lambda', Object.keys(pages))
                    res.statusCode = 500
                    return res.end('internal server error')
                  }

                  const mod = currentPage()
                  const method = mod.render || mod.default || mod

                  return method(req, res)
                } catch (err) {
                  console.error('Unhandled error during request:', err)
                  throw err
                }
              }
              ` ) ; const launcherFiles = { [ path . join ( path . relative ( baseDir , entryPath ) , 'now__bridge.js' ) ] : new FileFsRef ( { fsPath : path . join ( __dirname , 'now__bridge.js' ) , } ) , [ path . join ( path . relative ( baseDir , entryPath ) , 'now__launcher.js' ) ] : new FileBlob ( { data : launcher } ) , } ; const pageLayers = [ ] ; for ( const page of groupPageKeys ) { const { pageLayer } = group . pages [ page ] ; pageLambdaMap [ page ] = group . lambdaIdentifier ; pageLayers . push ( pageLayer ) ; } if ( requiresTracing ) { lambdas [ group . lambdaIdentifier ] = await createLambdaFromPseudoLayers ( { files : { ... launcherFiles , } , layers : [ ... ( group . isApiLambda ? apiPseudoLayers : pseudoLayers ) , ... pageLayers , ] , handler : path . join ( path . relative ( baseDir , entryPath ) , 'now__launcher.launcher' ) , runtime : nodeVersion . runtime , } ) ; } else { lambdas [ group . lambdaIdentifier ] = await createLambdaFromPseudoLayers ( { files : { ... launcherFiles , ... assets , } , layers : pageLayers , handler : path . join ( path . relative ( baseDir , entryPath ) , 'now__launcher.launcher' ) , runtime : nodeVersion . runtime , } ) ; } } ) ) ; } if ( hasLambdas ) { console . timeEnd ( allLambdasLabel ) ; } let prerenderGroup = 1 ; const onPrerenderRoute = ( routeKey , { isBlocking , isFallback , locale , } ) => { if ( isBlocking && isFallback ) { throw new NowBuildError ( { code : 'NEXT_ISBLOCKING_ISFALLBACK' , message : 'invariant: isBlocking and isFallback cannot both be true' , } ) ; } let routeFileNoExt = routeKey === '/' ? '/index' : routeKey ; const origRouteFileNoExt = routeFileNoExt ; const nonDynamicSsg = ! isFallback && ! isBlocking && ! prerenderManifest . staticRoutes [ routeKey ] . srcRoute ; if ( nonDynamicSsg || isFallback ) { routeFileNoExt = addLocaleOrDefault ( routeFileNoExt , routesManifest , locale ) ; } const isNotFound = prerenderManifest . notFoundRoutes . includes ( routeFileNoExt ) ; const htmlFsRef = isBlocking ? null : new FileFsRef ( { fsPath : path . join ( pagesDir , isFallback ? addLocaleOrDefault ( prerenderManifest . fallbackRoutes [ routeKey ] . fallback , routesManifest , locale ) : `${ routeFileNoExt }.html` ) , } ) ; const jsonFsRef = isFallback || isBlocking ? null : new FileFsRef ( { fsPath : path . join ( pagesDir , `${ routeFileNoExt }.json` ) , } ) ; let initialRevalidate ; let srcRoute ; let dataRoute ; if ( isFallback || isBlocking ) { const pr = isFallback ? prerenderManifest . fallbackRoutes [ routeKey ] : prerenderManifest . blockingFallbackRoutes [ routeKey ] ; initialRevalidate = 1 ; if ( initialRevalidate === false ) { throw new NowBuildError ( { code : 'NEXT_ISLAZY_INITIALREVALIDATE' , message : 'invariant isLazy: initialRevalidate !== false' , } ) ; } srcRoute = null ; dataRoute = pr . dataRoute ; } else { const pr = prerenderManifest . staticRoutes [ routeKey ] ; ( { initialRevalidate , srcRoute , dataRoute } = pr ) ; } const outputPathPage = path . posix . join ( entryDirectory , routeFileNoExt ) ; const outputPathPageOrig = path . posix . join ( entryDirectory , origRouteFileNoExt ) ; let lambda ; let outputPathData = path . posix . join ( entryDirectory , dataRoute ) ; if ( nonDynamicSsg || isFallback ) { outputPathData = outputPathData . replace ( new RegExp ( `${ escapeStringRegexp ( origRouteFileNoExt ) }.json$` ) , `${ routeFileNoExt }.json` ) ; } if ( isSharedLambdas ) { const outputSrcPathPage = path . join ( '/' , srcRoute == null ? outputPathPageOrig : path . join ( entryDirectory , srcRoute === '/' ? '/index' : srcRoute ) ) ; const lambdaId = pageLambdaMap [ outputSrcPathPage ] ; lambda = lambdas [ lambdaId ] ; } else { const outputSrcPathPage = srcRoute == null ? outputPathPageOrig : path . posix . join ( entryDirectory , srcRoute === '/' ? '/index' : srcRoute ) ; lambda = lambdas [ outputSrcPathPage ] ; } if ( ! isNotFound && initialRevalidate === false ) { if ( htmlFsRef == null || jsonFsRef == null ) { throw new NowBuildError ( { code : 'NEXT_HTMLFSREF_JSONFSREF' , message : 'invariant: htmlFsRef != null && jsonFsRef != null' , } ) ; } if ( ! canUsePreviewMode || ( hasPages404 && routeKey === '/404' ) ) { htmlFsRef . contentType = htmlContentType ; prerenders [ outputPathPage ] = htmlFsRef ; prerenders [ outputPathData ] = jsonFsRef ; } } if ( prerenders [ outputPathPage ] == null && ! isNotFound ) { if ( lambda == null ) { throw new NowBuildError ( { code : 'NEXT_MISSING_LAMBDA' , message : `Unable to find lambda for route: ${ routeFileNoExt }` , } ) ; } prerenders [ outputPathPage ] = new Prerender ( { expiration : initialRevalidate , lambda , fallback : htmlFsRef , group : prerenderGroup , bypassToken : prerenderManifest . bypassToken , } ) ; prerenders [ outputPathData ] = new Prerender ( { expiration : initialRevalidate , lambda , fallback : jsonFsRef , group : prerenderGroup , bypassToken : prerenderManifest . bypassToken , } ) ; ++ prerenderGroup ; if ( routesManifest ?. i18n && isBlocking ) { for ( const locale of routesManifest . i18n . locales ) { const localeRouteFileNoExt = addLocaleOrDefault ( routeFileNoExt , routesManifest , locale ) ; const localeOutputPathPage = path . posix . join ( entryDirectory , localeRouteFileNoExt ) ; const localeOutputPathData = outputPathData . replace ( new RegExp ( `${ escapeStringRegexp ( origRouteFileNoExt ) }.json$` ) , `${ localeRouteFileNoExt }${ localeRouteFileNoExt !== origRouteFileNoExt && origRouteFileNoExt === '/index' ? '/index' : '' }.json` ) ; const origPrerenderPage = prerenders [ outputPathPage ] ; const origPrerenderData = prerenders [ outputPathData ] ; prerenders [ localeOutputPathPage ] = { ... origPrerenderPage , group : prerenderGroup , } as Prerender ; prerenders [ localeOutputPathData ] = { ... origPrerenderData , group : prerenderGroup , } as Prerender ; ++ prerenderGroup ; } } } if ( ( nonDynamicSsg || isFallback ) && routesManifest ?. i18n && ! locale ) { for ( const locale of routesManifest . i18n . locales ) { if ( locale === routesManifest . i18n . defaultLocale ) continue ; onPrerenderRoute ( routeKey , { isBlocking , isFallback , locale , } ) ; } } } ; Object . keys ( prerenderManifest . staticRoutes ) . forEach ( route => onPrerenderRoute ( route , { isBlocking : false , isFallback : false } ) ) ; Object . keys ( prerenderManifest . fallbackRoutes ) . forEach ( route => onPrerenderRoute ( route , { isBlocking : false , isFallback : true } ) ) ; Object . keys ( prerenderManifest . blockingFallbackRoutes ) . forEach ( route => onPrerenderRoute ( route , { isBlocking : true , isFallback : false } ) ) ; if ( ! ( routesManifest && routesManifest . dataRoutes ) ) { [ ... Object . entries ( prerenderManifest . fallbackRoutes ) , ... Object . entries ( prerenderManifest . blockingFallbackRoutes ) , ] . forEach ( ( [ , { dataRouteRegex , dataRoute } ] ) => { dataRoutes . push ( { src : dataRouteRegex . replace ( /^\^/ , `^${ appMountPrefixNoTrailingSlash }` ) , dest : path . posix . join ( entryDirectory , dataRoute ) , check : true , } ) ; } ) ; } } const nextStaticFiles = await glob ( '**' , path . join ( entryPath , outputDirectory , 'static' ) ) ; const staticFolderFiles = await glob ( '**' , path . join ( entryPath , 'static' ) ) ; let publicFolderFiles = { } ; let publicFolderPath ; if ( await pathExists ( path . join ( entryPath , 'public' ) ) ) { publicFolderPath = path . join ( entryPath , 'public' ) ; } else if ( await pathExists ( path . join ( entryPath , outputDirectory , '../public' ) ) ) { publicFolderPath = path . join ( entryPath , outputDirectory , '../public' ) ; } if ( publicFolderPath ) { debug ( `Using public folder at ${ publicFolderPath }` ) ; publicFolderFiles = await glob ( '**/*' , publicFolderPath ) ; } else { debug ( 'No public folder found' ) ; } const staticFiles = Object . keys ( nextStaticFiles ) . reduce ( ( mappedFiles , file ) => ( { ... mappedFiles , [ path . join ( entryDirectory , `_next/static/${ file }` ) ] : nextStaticFiles [ file ] , } ) , { } ) ; const staticDirectoryFiles = Object . keys ( staticFolderFiles ) . reduce ( ( mappedFiles , file ) => ( { ... mappedFiles , [ path . join ( entryDirectory , 'static' , file ) ] : staticFolderFiles [ file ] , } ) , { } ) ; const publicDirectoryFiles = Object . keys ( publicFolderFiles ) . reduce ( ( mappedFiles , file ) => ( { ... mappedFiles , [ path . join ( entryDirectory , file ) ] : publicFolderFiles [ file ] , } ) , { } ) ; if ( ! isSharedLambdas ) { prerenderManifest . omittedRoutes . forEach ( routeKey => { const routeFileNoExt = path . posix . join ( entryDirectory , routeKey === '/' ? '/index' : routeKey ) ; if ( typeof lambdas [ routeFileNoExt ] === undefined ) { throw new NowBuildError ( { code : 'NEXT__UNKNOWN_ROUTE_KEY' , message : `invariant: unknown lambda ${ routeKey } (lookup: ${ routeFileNoExt }) | please report this immediately` , } ) ; } delete lambdas [ routeFileNoExt ] ; } ) ; } const mergedDataRoutesLambdaRoutes = [ ] ; const mergedDynamicRoutesLambdaRoutes = [ ] ; if ( isSharedLambdas ) { for ( let i = 0 ; i < dynamicRoutes . length ; i ++ ) { const route = dynamicRoutes [ i ] ; mergedDynamicRoutesLambdaRoutes . push ( route ) ; const { pathname } = url . parse ( route . dest ! ) ; if ( pathname && pageLambdaMap [ pathname ] ) { mergedDynamicRoutesLambdaRoutes . push ( dynamicPageLambdaRoutesMap [ pathname ] ) ; } } for ( let i = 0 ; i < dataRoutes . length ; i ++ ) { const route = dataRoutes [ i ] ; mergedDataRoutesLambdaRoutes . push ( route ) ; const { pathname } = url . parse ( route . dest ! ) ; if ( pathname && pageLambdaMap [ pathname ] && dynamicPageLambdaRoutesMap [ pathname ] ) { mergedDataRoutesLambdaRoutes . push ( dynamicPageLambdaRoutesMap [ pathname ] ) ; } } } const { i18n } = routesManifest || { } ; const trailingSlashRedirects = [ ] ; redirects = redirects . filter ( _redir => { const redir = _redir as Source ; const location = redir . headers && ( redir . headers . location || redir . headers . Location ) ; if ( redir . status === 308 && ( location === '/$1' || location === '/$1/' ) ) { redir . continue = true ; trailingSlashRedirects . push ( redir ) ; return false ; } return true ; } ) ; return { output : { ... publicDirectoryFiles , ... lambdas , ... prerenders , ... staticPages , ... staticFiles , ... staticDirectoryFiles , } , wildcard : i18n ?. domains ? i18n ?. domains . map ( item => { return { domain : item . domain , value : item . defaultLocale === i18n . defaultLocale ? '' : `/${ item . defaultLocale }` , } ; } ) : undefined , images : imagesManifest ?. images ?. loader === 'default' ? { domains : imagesManifest . images . domains , sizes : imagesManifest . images . sizes , } : undefined , routes : [ ... trailingSlashRedirects , ... ( i18n ? [ { src : `^${ path . join ( '/' , entryDirectory , '/' ) }(?!(?:_next/.*|${ i18n . locales . map ( locale => escapeStringRegexp ( locale ) ) . join ( '|' ) })(?:/.*|$))(.*)$` , dest : '$wildcard/$1' , continue : true , } , ... ( i18n . domains && i18n . localeDetection !== false ? [ { src : `^${ path . join ( '/' , entryDirectory ) }/?(?:${ i18n . locales . map ( locale => escapeStringRegexp ( locale ) ) . join ( '|' ) })?/?$` , locale : { redirect : i18n . domains . reduce ( ( prev , item ) => { prev [ item . defaultLocale ] = `http${ item . http ? '' : 's' }://${ item . domain }/` ; if ( item . locales ) { item . locales . map ( locale => { prev [ locale ] = `http${ item . http ? '' : 's' }://${ item . domain }/${ locale }` ; } ) ; } return prev ; } , { } ) , cookie : 'NEXT_LOCALE' , } , continue : true , } , ] : [ ] ) , ... ( i18n . localeDetection !== false ? [ { src : '/' , locale : { redirect : i18n . locales . reduce ( ( prev , locale ) => { prev [ locale ] = locale === i18n . defaultLocale ? `/` : `/${ locale }` ; return prev ; } , { } ) , cookie : 'NEXT_LOCALE' , } , continue : true , } , ] : [ ] ) , { src : `^${ path . join ( '/' , entryDirectory ) }$` , dest : `/${ i18n . defaultLocale }` , continue : true , } , { src : `^${ path . join ( '/' , entryDirectory , '/' ) }(?!(?:_next/.*|${ i18n . locales . map ( locale => escapeStringRegexp ( locale ) ) . join ( '|' ) })(?:/.*|$))(.*)$` , dest : `/${ i18n . defaultLocale }/$1` , continue : true , } , ] : [ ] ) , ... headers , ... redirects , ... ( i18n ? [ { src : `${ path . join ( '/' , entryDirectory , '/' ) }(?:${ i18n . locales . map ( locale => escapeStringRegexp ( locale ) ) . join ( '|' ) })?[/]?404` , status : 404 , continue : true , } , ] : [ { src : path . join ( '/' , entryDirectory , '404' ) , status : 404 , continue : true , } , ] ) , { handle : 'filesystem' } , ... pageLambdaRoutes . filter ( route => { if ( 'headers' in route ) { let page = route . headers ?. [ 'x-nextjs-page' ] ! ; page = page === '/index' ? '/' : page ; if ( prerenderManifest . staticRoutes [ page ] || prerenderManifest . fallbackRoutes [ page ] || prerenderManifest . blockingFallbackRoutes [ page ] ) { return false ; } } return true ; } ) , ... rewrites , { handle : 'resource' } , { src : path . join ( '/' , entryDirectory , '.*' ) , status : 404 } , { handle : 'miss' } , { src : path . join ( '/' , entryDirectory , '_next/static/(?:[^/]+/pages|pages|chunks|runtime|css|media)/.+' ) , status : 404 , check : true , dest : '$0' , } , ... ( i18n ? [ { src : `^${ path . join ( '/' , entryDirectory ) }/?(?:${ i18n . locales . map ( locale => escapeStringRegexp ( locale ) ) . join ( '|' ) })/(.*)` , dest : `${ path . join ( '/' , entryDirectory , '/' ) }$1` , check : true , } , ] : [ ] ) , ... ( isSharedLambdas || ! i18n ? [ ] : [ { src : `${ path . join ( '/' , entryDirectory , '/' ) }(?:${ i18n ?. locales . map ( locale => escapeStringRegexp ( locale ) ) . join ( '|' ) })/(.*)` , dest : '/$1' , check : true , } , ] ) , { handle : 'rewrite' } , ... ( isSharedLambdas ? mergedDataRoutesLambdaRoutes : dataRoutes ) , ... pageLambdaRoutes , ... ( isSharedLambdas ? mergedDynamicRoutesLambdaRoutes : dynamicRoutes ) , { handle : 'hit' } , { src : path . join ( '/' , entryDirectory , `_next/static/(?:[^/]+/pages|pages|chunks|runtime|css|media|${ escapedBuildId })/.+` ) , headers : { 'cache-control' : `public,max-age=${ MAX_AGE_ONE_YEAR },immutable` , } , continue : true , important : true , } , ... ( isLegacy ? [ ] : [ { handle : 'error' } as Handler , ... ( i18n && static404Page ? [ { src : `${ path . join ( '/' , entryDirectory , '/' ) }(?<nextLocale>${ i18n . locales . map ( locale => escapeStringRegexp ( locale ) ) . join ( '|' ) })(/.*|$)` , dest : '/$nextLocale/404' , status : 404 , } , { src : path . join ( '/' , entryDirectory , '.*' ) , dest : `/${ i18n . defaultLocale }/404` , status : 404 , } , ] : [ isSharedLambdas ? { src : path . join ( '/' , entryDirectory , '.*' ) , dest : path . join ( '/' , ( static404Page ? static404Page : pageLambdaMap [ page404Path ] ) as string ) , status : 404 , ... ( static404Page ? { } : { headers : { 'x-nextjs-page' : page404Path , } , } ) , } : { src : path . join ( '/' , entryDirectory , '.*' ) , dest : static404Page ? path . join ( '/' , static404Page ) : path . join ( '/' , entryDirectory , hasPages404 && lambdas [ path . join ( './' , entryDirectory , '404' ) ] ? '404' : '_error' ) , status : 404 , } , ] ) , ] ) , ] , watch : [ ] , childProcesses : [ ] , } ; } export const prepareCache = async ( { workPath , entrypoint , config = { } , } ) : Promise < Files > => { debug ( 'Preparing cache...' ) ; const entryDirectory = path . dirname ( entrypoint ) ; const entryPath = path . join ( workPath , entryDirectory ) ; const outputDirectory = config . outputDirectory || '.next' ; const nextVersionRange = await getNextVersionRange ( entryPath ) ; const isLegacy = nextVersionRange && isLegacyNext ( nextVersionRange ) ; if ( isLegacy ) { return { } ; } debug ( 'Producing cache file manifest...' ) ; const cacheEntrypoint = path . relative ( workPath , entryPath ) ; const cache = { ... ( await glob ( path . join ( cacheEntrypoint , 'node_modules/**' ) , workPath ) ) , ... ( await glob ( path . join ( cacheEntrypoint , outputDirectory , 'cache/**' ) , workPath ) ) , } ; debug ( 'Cache file manifest produced' ) ; return cache ; } ;