Relevant source files
The following files were used as context for generating this wiki page:
- packages/next/src/bin/next.ts
- packages/next/src/build/index.ts
- packages/next/src/cli/next-dev.ts
- packages/next/taskfile.js
- packages/next/src/cli/next-build.ts
- packages/next-codemod/bin/next-codemod.ts
- packages/next/src/cli/next-info.ts
- packages/next/src/server/lib/start-server.ts
- packages/create-next-app/index.ts
- packages/next/src/cli/next-start.ts
- packages/next/src/server/lib/utils.ts
- packages/next/src/cli/internal/static-routes-info.ts
- packages/next/src/cli/next-analyze.ts
- packages/next/src/cli/internal/turbo-trace-server.ts
- packages/next/src/server/next-server.ts
- packages/next/src/build/utils.ts
- packages/next/src/cli/next-request-insights.ts
- packages/devlow-bench/src/cli.ts
- packages/next/src/cli/next-test.ts
- packages/next/src/cli/next-export.ts
- packages/next/src/server/next.ts
- packages/next/src/lib/bundler.ts
- package.json
- packages/next/src/lib/upgrade/nudge.ts
- packages/next/src/lib/upgrade/prompt-output.ts
- packages/next/src/telemetry/agent-name.ts
- packages/next/src/telemetry/storage.ts
Overview
The command-line interface infrastructure of Next.js provides the primary entrypoint for managing application lifecycles, development workflows, production builds, and diagnostic reporting. Rooted in the main binary dispatcher, the CLI parses subcommands and flags to coordinate execution flows across development and production environments.
Sources: packages/next/src/bin/next.ts:390-578
Entrypoint routing and command dispatch
Subcommand definitions and action bindings
The primary CLI entrypoint configures commands and option parsers for subcommands including dev, start, info, and export. The dev command is designated as the default subcommand and accepts an optional [directory] argument alongside configuration flags.
program
.command('dev', { isDefault: true })
.description(
'Starts Next.js in development mode with hot-code reloading, error reporting, and more.'
)
.argument(
'[directory]',
`A directory on which to build the application. ${italic(
'If no directory is provided, the current directory will be used.'
)}`
)
.addOption(
new Option(
'--inspect [[host:]port]',
'Allows inspecting server-side code. See https://nextjs.org/docs/app/guides/debugging#server-side-code'
).argParser(parseValidInspectAddress)
)
.option('--turbo', 'Starts development mode using Turbopack.')
.option('--turbopack', 'Starts development mode using Turbopack.')
.option('--webpack', 'Starts development mode using the bundled webpack.')
.option(
'--custom-webpack',
'Starts development mode using your project-installed webpack.'
)
.addOption(
new Option(
'-p, --port <port>',
'Specify a port number on which to start the application.'
)
.argParser(parseValidPositiveInteger)
.default(3000)
.env('PORT')
)
.option(
'-H, --hostname <hostname>',
'Specify a hostname on which to start the application (default: 0.0.0.0).'
)
.option(
'--disable-source-maps',
"Don't start the Dev server with `--enable-source-maps`.",
false
)
.option(
'--experimental-https',
'Starts the server with HTTPS and generates a self-signed certificate.'
)
.option('--experimental-https-key, <path>', 'Path to a HTTPS key file.')
.option(
'--experimental-https-cert, <path>',
'Path to a HTTPS certificate file.'
)
.option(
'--experimental-https-ca, <path>',
'Path to a HTTPS certificate authority file.'
)
// `--server-fast-refresh` is hidden because it's the default behavior and
// only needs to be explicitly passed to override a
// `experimental.turbopackServerFastRefresh: false` in next.config. The
// `--no-server-fast-refresh` negation is the meaningful user-facing flag.
.addOption(new Option('--server-fast-refresh').default(undefined).hideHelp())
.addOption(
new Option('--no-server-fast-refresh', 'Disable server-side Fast Refresh')
)
.option(
'--experimental-upload-trace, <traceUrl>',
'Reports a subset of the debugging trace to a remote HTTP URL. Includes sensitive data.'
)
.option(
'--experimental-next-config-strip-types',
'Use Node.js native TypeScript resolution for next.config.(ts|mts)'
)
.option(
'--experimental-cpu-prof',
'Enable CPU profiling. Profiles are saved to .next-profiles/ on exit.'
)
.addOption(
new Option(
'--internal-trace [level]',
'Enable Turbopack tracing. "all" (default) enables turbo-tasks level tracing, "overview" enables overview tracing.'
)Sources: packages/next/src/bin/next.ts:390-501
The start subcommand configures production startup parameters, port resolution, and debugger bindings.
program
.command('start')
.description(
'Starts Next.js in production mode. The application should be compiled with `next build` first.'
)
.argument(
'[directory]',
`A directory on which to start the application. ${italic(
'If no directory is provided, the current directory will be used.'
)}`
)
.addOption(
new Option(
'-p, --port <port>',
'Specify a port number on which to start the application.'
)
.argParser(parseValidPositiveInteger)
.default(3000)
.env('PORT')
)
.option(
'-H, --hostname <hostname>',
'Specify a hostname on which to start the application (default: 0.0.0.0).'
)
.addOption(
new Option(
'--inspect [[host:]port]',
'Allows inspecting server-side code. See https://nextjs.org/docs/app/guides/debugging#server-side-code'
).argParser(parseValidInspectAddress)
)
.addOption(
new Option(
'--keepAliveTimeout <keepAliveTimeout>',
'Specify the maximum amount of milliseconds to wait before closing inactive connections.'
).argParser(parseValidPositiveInteger)
)
.option(
'--experimental-next-config-strip-types',
'Use Node.js native TypeScript resolution for next.config.(ts|mts)'
)
.option(
'--experimental-cpu-prof',
'Enable CPU profiling. Profiles are saved to .next-profiles/ on exit.'
)
.action((directory: string, options: NextStartOptions) => {
if (options.experimentalNextConfigStripTypes) {
process.env.__NEXT_NODE_NATIVE_TS_LOADER_ENABLED = 'true'
}
if (options.experimentalCpuProf) {
process.env.NEXT_CPU_PROF = '1'
process.env.__NEXT_PRIVATE_CPU_PROFILE = 'start-main'
}
setupProfilesDir(directory || process.cwd())
return import('../cli/next-start.js').then((mod) =>
mod.nextStart(options, directory)
)
})
.usage('[directory] [options]')Sources: packages/next/src/bin/next.ts:521-578
Argument and environment option parsing
Utility functions support tokenizing argument strings, parsing environment variables like NODE_OPTIONS, and handling option constraints. Repeatable Node.js CLI arguments are preserved via specific type definitions.
const REPEATABLE_OPTIONS: Record<string, { type: 'string'; multiple: true }> = {
require: { type: 'string', multiple: true },
import: { type: 'string', multiple: true },
}
const parseNodeArgs = (args: string[]): NodeOptions => {
const parsed = parseArgs({
args,
strict: false,
tokens: true,
options: REPEATABLE_OPTIONS,
})
const values: NodeOptions = parsed.values
const tokens = parsed.tokens
// For the `NODE_OPTIONS`, we support arguments with values without the `=`
// sign. We need to parse them manually.
let orphan = null
for (let i = 0; i < tokens.length; i++) {
const token = tokens[i]
if (token.kind === 'option-terminator') {
break
}
// When we encounter an option, if it's value is undefined, we should check
// to see if the following tokens are positional parameters. If they are,
// then the option is orphaned, and we can assign it.
if (token.kind === 'option') {
orphan = typeof token.value === 'undefined' ? token : null
continue
}
// If the token isn't a positional one, then we can't assign it to the found
// orphaned option.
if (token.kind !== 'positional') {
orphan = null
continue
}
// If we don't have an orphan, then we can skip this token.
if (!orphan) {
continue
}
const name = orphan.name
// If the option is repeatable, accumulate each occurrence instead of
// collapsing them into a single value.
if (name in REPEATABLE_OPTIONS) {
if (Array.isArray(values[name])) {
values[name].push(token.value)
} else if (typeof values[name] === 'string') {
values[name] = [values[name], token.value]
} else {
values[name] = token.value
}
continue
}
// If the token is a positional one, and it has a value, so add it to the
// values object. If it already exists, append it with a space.
if (name in values && typeof values[name] === 'string') {
values[name] += ` ${token.value}`
} else {
values[name] = token.value
}
}
return values
}Sources: packages/next/src/server/lib/utils.ts:26-96
Note
Repeatable arguments such as --require and --import are accumulated into arrays rather than collapsed into single string values during NODE_OPTIONS parsing.
export const tokenizeArgs = (input: string): string[] => {
let args: string[] = []
let isInString = false
let willStartNewArg = true
for (let i = 0; i < input.length; i++) {
let char = input[i]
// Skip any escaped characters in strings.
if (char === '\\' && isInString) {
// Ensure we don't have an escape character at the end.
if (input.length === i + 1) {
throw new Error('Invalid escape character at the end.')
}
// Skip the next character.
char = input[++i]
}
// If we find a space outside of a string, we should start a new argument.
else if (char === ' ' && !isInString) {
willStartNewArg = true
continue
}
// If we find a quote, we should toggle the string flag.
else if (char === '"') {
isInString = !isInString
continue
}
// If we're starting a new argument, we should add it to the array.
if (willStartNewArg) {
args.push(char)
willStartNewArg = false
}
// Otherwise, add it to the last argument.
else {
args[args.length - 1] += char
}
}
if (isInString) {
throw new Error('Unterminated string')
}
return args
}Sources: packages/next/src/server/lib/utils.ts:106-152
Flags restricted to direct execArgv usage are validated against a predefined set of inspection options.
const EXEC_ARGV_ONLY_OPTIONS = new Set([
'experimental-network-inspection',
'experimental-storage-inspection',
'experimental-worker-inspection',
'experimental-inspector-network-resource',
])
Sources: packages/next/src/server/lib/utils.ts:214-220
Development server CLI bootstrapping
Bundler resolution and options parsing
The development server determines its active compiler through parseBundlerArgs, which processes flags such as --turbo, --turbopack, --webpack, and --custom-webpack alongside environment variables like TURBOPACK, IS_TURBOPACK_TEST, IS_WEBPACK_TEST, NEXT_RSPACK, and NEXT_TEST_USE_RSPACK. If multiple conflicting bundler flags are supplied, Next.js logs an error and terminates execution with exit code 1. When no bundler flags are provided, Turbopack is selected by default by setting process.env.TURBOPACK = 'auto'.
export function parseBundlerArgs(options: {
turbo?: boolean
turbopack?: boolean
webpack?: boolean
customWebpack?: boolean
}): Bundler {
const bundlerFlags = new Map<Bundler, string[]>()
const setBundlerFlag = (bundler: Bundler, flag: string) => {
bundlerFlags.set(bundler, (bundlerFlags.get(bundler) ?? []).concat(flag))
}
// What turbo flag was set? We allow multiple to be set, which is silly but not ambiguous, just pick the most relevant one.
if (options.turbopack) {
setBundlerFlag(Bundler.Turbopack, '--turbopack')
}
if (options.turbo) {
setBundlerFlag(Bundler.Turbopack, '--turbo')
} else if (process.env.TURBOPACK) {
// We don't really want to support this but it is trivial and not really confusing.
// If we don't support it and someone sets it, we would have inconsistent behavior
// since some parts of next would read the return value of this function and other
// parts will read the env variable.
setBundlerFlag(Bundler.Turbopack, `TURBOPACK=${process.env.TURBOPACK}`)
} else if (process.env.IS_TURBOPACK_TEST) {
setBundlerFlag(
Bundler.Turbopack,
`IS_TURBOPACK_TEST=${process.env.IS_TURBOPACK_TEST}`
)
}
if (options.webpack) {
setBundlerFlag(Bundler.Webpack, '--webpack')
}
if (options.customWebpack) {
setBundlerFlag(Bundler.Webpack, '--custom-webpack')
}
if (process.env.IS_WEBPACK_TEST) {
setBundlerFlag(
Bundler.Webpack,
`IS_WEBPACK_TEST=${process.env.IS_WEBPACK_TEST}`
)
}
// Mostly this is set via the NextConfig but it can also be set via the command line which is
// common for testing.
if (process.env.NEXT_RSPACK) {
setBundlerFlag(Bundler.Rspack, `NEXT_RSPACK=${process.env.NEXT_RSPACK}`)
}
if (process.env.NEXT_TEST_USE_RSPACK) {
setBundlerFlag(
Bundler.Rspack,
`NEXT_TEST_USE_RSPACK=${process.env.NEXT_TEST_USE_RSPACK}`
)
}
if (
bundlerFlags.size > 1 ||
(options.webpack === true && options.customWebpack === true)
) {
console.error(
`Multiple bundler flags set: ${Array.from(bundlerFlags.values()).flat().join(', ')}.
Edit your command or your package.json script to configure only one bundler.`
)
process.exit(1)
}
// The default is turbopack when nothing is configured.
if (bundlerFlags.size === 0) {
process.env.TURBOPACK = 'auto'
return Bundler.Turbopack
}
if (bundlerFlags.has(Bundler.Turbopack)) {
// Only conditionally assign to the environment variable, preserving already set values.
// If it was set to 'auto' because no flag was set and this function is called a second time we
// would upgrade to '1' but we don't really want that.
process.env.TURBOPACK ??= '1'
return Bundler.Turbopack
}
// Otherwise it is one of rspack or webpack. At this point there must be exactly one key in the map.
return bundlerFlags.keys().next().value!
}Sources: packages/next/src/lib/bundler.ts:41-120
| Bundler Option / Flag | Environment Variable | Target Bundler |
|---|---|---|
--turbopack / --turbo | TURBOPACK, IS_TURBOPACK_TEST | Bundler.Turbopack |
--webpack / --custom-webpack | IS_WEBPACK_TEST | Bundler.Webpack |
| (None configured) | NEXT_RSPACK, NEXT_TEST_USE_RSPACK | Bundler.Rspack |
Sources: packages/next/src/lib/bundler.ts:24-93
Warning
Setting multiple distinct bundler flags simultaneously triggers a fatal exit code 1 after listing all activated flag arguments.
if (
bundlerFlags.size > 1 ||
(options.webpack === true && options.customWebpack === true)
) {
console.error(
`Multiple bundler flags set: ${Array.from(bundlerFlags.values()).flat().join(', ')}.
Edit your command or your package.json script to configure only one bundler.`
)
process.exit(1)
}Sources: packages/next/src/lib/bundler.ts:95-105
Worker handoff and watchpack management
When executing as an internal worker (process.env.NEXT_PRIVATE_WORKER), the server listens for parent messages containing nextWorkerOptions. It initializes tracing infrastructure via trace('start-dev-server') combined with heap statistics, CPU counts, free memory metrics, and parent-provided environment flags, eventually invoking startServer.
if (process.env.NEXT_PRIVATE_WORKER && process.send) {
// Output is piped to the CLI to hold it while the upgrade menu is open.
if (process.env.NEXT_PRIVATE_PROMPT_OUTPUT === '1') {
blockOnOutputWrites()
}
process.addListener('message', async (msg: any) => {
if (
msg &&
typeof msg === 'object' &&
msg.nextWorkerOptions &&
process.send
) {
let enabledFeaturesFromParent = {}
if (process.env.NEXT_PRIVATE_ENABLED_FEATURES) {
const parsed = JSON.parse(process.env.NEXT_PRIVATE_ENABLED_FEATURES)
enabledFeaturesFromParent = Object.fromEntries(
Object.entries(parsed).map(([key, value]) => [
`feature.${key}`,
value,
])
)
}
let rageRestartAttrsFromParent = {}
if (process.env.NEXT_PRIVATE_DEV_SPAN_ATTRS) {
rageRestartAttrsFromParent = JSON.parse(
process.env.NEXT_PRIVATE_DEV_SPAN_ATTRS
)
}
startServerSpan = trace('start-dev-server', undefined, {
cpus: String(os.cpus().length),
platform: os.platform(),
'memory.freeMem': String(os.freemem()),
'memory.totalMem': String(os.totalmem()),
'memory.heapSizeLimit': String(v8.getHeapStatistics().heap_size_limit),
...enabledFeaturesFromParent,
...rageRestartAttrsFromParent,
})
initializeTraceState({
...exportTraceState(),
defaultParentSpanId: startServerSpan.getId(),
})
const result = await startServerSpan.traceAsyncFn(() =>
startServer(msg.nextWorkerOptions)
)
const memoryUsage = process.memoryUsage()
startServerSpan.setAttribute('memory.rss', String(memoryUsage.rss))
startServerSpan.setAttribute(
'memory.heapTotal',
String(memoryUsage.heapTotal)
)
startServerSpan.setAttribute(
'memory.heapUsed',
String(memoryUsage.heapUsed)
)
process.send({
nextServerReady: true,
port: process.env.PORT,
distDir: result.distDir,
})
}
})
process.send({ nextWorkerReady: true })
}Sources: packages/next/src/server/lib/start-server.ts:634-700
During development startup, Watchpack monitors project configuration files and distribution directory ancestors. Any modification to configuration files or deletion of watched path ancestors forces the server to exit with RESTART_EXIT_CODE.
// Watch config files for changes and distDir ancestors for deletion.
if (isDev) {
// Note: dir is absolute and normalized (`..` segments removed), `absDistDir`
// is also normalized because `path.join()` performs normalization. `distDir`
// does not have to be inside of `dir`!
const absDistDir = path.join(dir, distDir)
// always watch dir and absDistDir
const dirWatchPaths: string[] = [dir, absDistDir]
// also watch ancestors of absDistDir that are inside of dir.
let prevAncestor = absDistDir
while (true) {
const nextAncestor = path.dirname(prevAncestor)
// note: `dirname('/') === '/'` if we happen to reach the FS root
if (
!nextAncestor.startsWith(dir + path.sep) ||
nextAncestor === prevAncestor
) {
break
}
dirWatchPaths.push(nextAncestor)
prevAncestor = nextAncestor
}
const configFiles = CONFIG_FILES.map((file) => path.join(dir, file))
const Watchpack =
require('next/dist/compiled/watchpack') as typeof import('next/dist/compiled/watchpack').default
const wp = new Watchpack()
wp.watch({
files: configFiles,
missing: dirWatchPaths,
})
wp.on('change', async (filename) => {
if (!configFiles.includes(filename)) {
return
}
Log.warn(
`Found a change in ${path.basename(
filename
)}. Restarting the server to apply the changes...`
)
process.exit(RESTART_EXIT_CODE)
})
wp.on('remove', (removedPath: string) => {
if (dirWatchPaths.includes(removedPath)) {
Log.error(
`The directory at "${removedPath}" was deleted.\n\n` +
'Deleting this directory removes caches and will cause slower ' +
'performance.\n\n' +
'Deleting this directory while Next.js is running can lead to ' +
'undefined behavior. Restarting the server to recover...'
)
process.exit(RESTART_EXIT_CODE)
}
})
}Sources: packages/next/src/server/lib/start-server.ts:575-629
Note
The threshold window for identifying a rapid user restart as a "rage restart" spans 90,000 milliseconds (RAGE_RESTART_THRESHOLD_MS).
// If the user restarts the dev server within this window we count it as a "rage restart".
const RAGE_RESTART_THRESHOLD_MS = 90_000Sources: packages/next/src/cli/next-dev.ts:102-103
Production build invocation and child process management
Child process execution and output interception
When executing [[Build Pipeline]] via next build, execution begins inside nextBuild where the process title is updated and signal handlers are attached for SIGTERM and SIGINT to safely invoke saveCpuProfile and exit with status codes 143 and 130 respectively. If process.env.NEXT_PRIVATE_UPGRADE_BUILD_CHILD is set to '1', output writes are blocked via blockOnOutputWrites. Otherwise, shouldBuildInChild evaluates whether debuggers, profilers, or AI coding agents are active.
const nextBuild = async (options: NextBuildOptions, directory?: string) => {
process.title = `next-build (v${process.env.__NEXT_VERSION})`
// To show the upgrade menu without pausing the build, run the build in a
// child and keep the menu here.
if (process.env.NEXT_PRIVATE_UPGRADE_BUILD_CHILD === '1') {
blockOnOutputWrites()
} else if (await shouldBuildInChild()) {
return buildInChild()
}
Sources: packages/next/src/cli/next-build.ts:57-67
const onTerminate = () => {
saveCpuProfile()
process.exit(143)
}
const onInterrupt = () => {
saveCpuProfile()
process.exit(130)
}
process.on('SIGTERM', onTerminate)
process.on('SIGINT', onInterrupt)
Sources: packages/next/src/cli/next-build.ts:68-78
If building in a child process is approved, buildInChild forks the current script with process.argv and configures stdio with ['inherit', 'pipe', 'pipe', 'ipc'], passing NEXT_PRIVATE_UPGRADE_BUILD_CHILD: '1' and environment variables from getPromptOutputEnv. The child process output is captured and managed through createPromptOutput, which attaches data listeners to child.stdout and child.stderr.
async function buildInChild(): Promise<never> {
// Rerun the same command as a child, with its output piped to us.
const child = fork(process.argv[1], process.argv.slice(2), {
stdio: ['inherit', 'pipe', 'pipe', 'ipc'],
env: {
...process.env,
...getPromptOutputEnv(),
NEXT_PRIVATE_UPGRADE_BUILD_CHILD: '1',
},
})
const exited = once(child, 'exit') as Promise<
[number | null, NodeJS.Signals | null]
>
const output = createPromptOutput()
output.attach(child)
Sources: packages/next/src/cli/next-build.ts:215-230
export function createPromptOutput() {
// print: show now. hold: keep for later. drop: throw away.
let mode: 'print' | 'hold' | 'drop' = 'print'
// One list for stdout and stderr keeps their order.
let held: Array<[NodeJS.WriteStream, Buffer]> = []
let heldBytes = 0
let droppedBytes = 0
function forward(destination: NodeJS.WriteStream) {
return (chunk: Buffer) => {
if (mode === 'print') {
destination.write(chunk)
} else if (mode === 'hold') {
held.push([destination, chunk])
heldBytes += chunk.length
// Over the limit: drop the oldest output.
while (heldBytes > MAX_HELD_BYTES && held.length > 1) {
const [, oldest] = held.shift()!
heldBytes -= oldest.length
droppedBytes += oldest.length
}
}
}
}Sources: packages/next/src/lib/upgrade/prompt-output.ts:25-48
Warning
Held output bytes are bounded by MAX_HELD_BYTES (10 * 1024 * 1024 bytes). When exceeded while the upgrade menu is open, the oldest chunks are dropped and a warning is logged upon release.
// Keep at most this much while the menu is open, so a menu left open doesn't
// grow memory without bound.
const MAX_HELD_BYTES = 10 * 1024 * 1024Sources: packages/next/src/lib/upgrade/prompt-output.ts:19-21
heldBytes += chunk.length
// Over the limit: drop the oldest output.
while (heldBytes > MAX_HELD_BYTES && held.length > 1) {
const [, oldest] = held.shift()!
heldBytes -= oldest.length
droppedBytes += oldest.length
}Sources: packages/next/src/lib/upgrade/prompt-output.ts:39-45
Call-chain execution walkthroughs
The execution paths governing build isolation, output forwarding, and agent evaluation follow strict step-by-General call orders:
nextBuildinvokesbuildInChildwhen child execution is required.buildInChildexecutesshowUpgradeMenuto display upgrade prompts alongside the running process.showUpgradeMenuinitializes and invokesflushon the telemetry storage instance.
} else if (await shouldBuildInChild()) {
return buildInChild()
}Sources: packages/next/src/cli/next-build.ts:64-66
export async function showUpgradeMenu(
output: PromptOutput,
options: {
dir: string
context: UpgradeContext
command: 'dev' | 'build'
signal: AbortSignal
initialAssessment: Promise<UpgradeReminder | null> | null
// The child's NEXT_TELEMETRY_DISABLED. It loaded .env; this process did
// not.
telemetryDisabled: string | undefined
}
): Promise<UpgradeMenuResult> {Sources: packages/next/src/lib/upgrade/prompt-output.ts:124-136
pendingTelemetry = telemetry.flush()
Sources: packages/next/src/lib/upgrade/prompt-output.ts:169-170
Sources: packages/next/src/telemetry/storage.ts:226-228
nextBuildtriggersbuildInChildto fork execution.buildInChildcallscreatePromptOutputto instantiate output control buffers.createPromptOutputreturns handlers includingforwardto route data streams.
} else if (await shouldBuildInChild()) {
return buildInChild()
}Sources: packages/next/src/cli/next-build.ts:64-66
const output = createPromptOutput()
output.attach(child)Sources: packages/next/src/cli/next-build.ts:228-229
export function createPromptOutput() {
// print: show now. hold: keep for later. drop: throw away.
let mode: 'print' | 'hold' | 'drop' = 'print'
// One list for stdout and stderr keeps their order.
let held: Array<[NodeJS.WriteStream, Buffer]> = []
let heldBytes = 0
let droppedBytes = 0
function forward(destination: NodeJS.WriteStream) {Sources: packages/next/src/lib/upgrade/prompt-output.ts:25-33
nextBuildevaluatesshouldBuildInChild.shouldBuildInChildcallsshouldPromptForUpgrade.shouldPromptForUpgradechecks terminal capabilities and callsgetAgentName.
} else if (await shouldBuildInChild()) {Sources: packages/next/src/cli/next-build.ts:64-64
async function shouldBuildInChild() {
const nodeOptions = getParsedNodeOptions()
if (
process.env.NEXT_CPU_PROF ||
getNodeDebugType(nodeOptions) ||
['inspect-wait', 'cpu-prof', 'heap-prof', 'prof'].some(
(flag) => nodeOptions[flag]
)
) {
return false
}
const { shouldPromptForUpgrade } = await import('../lib/upgrade/nudge.js')
return shouldPromptForUpgrade()
}Sources: packages/next/src/cli/next-build.ts:199-212
export async function shouldPromptForUpgrade(): Promise<boolean> {
return (
canPromptForUpgrade() &&
(isTerminalForcedForTesting() || !(await getAgentName()))
)
}Sources: packages/next/src/lib/upgrade/nudge.ts:527-532
Sources: packages/next/src/telemetry/agent-name.ts:17-25
Sources: packages/next/src/cli/next-build.ts:57-230
Sources: packages/next/src/lib/upgrade/prompt-output.ts:25-180
Sources: packages/next/src/lib/upgrade/nudge.ts:527-532
Sources: packages/next/src/telemetry/agent-name.ts:17-25
Sources: packages/next/src/telemetry/storage.ts:226-228
Build options and configuration validation
Command-line options parsed into NextBuildOptions dictate compiler parameters, feature flags, and diagnostic toggles. Options such as --analyze require Turbopack and will terminate via printAndExit if combined with other bundlers.
export type NextBuildOptions = {
analyze?: boolean
experimentalAnalyze?: boolean
debug?: boolean
debugPrerender?: boolean
profile?: boolean
mangling: boolean
turbo?: boolean
turbopack?: boolean
webpack?: boolean
customWebpack?: boolean
experimentalDebugMemoryUsage: boolean
experimentalAppOnly?: boolean
experimentalTurbo?: boolean
experimentalBuildMode: 'default' | 'compile' | 'generate' | 'generate-env'
experimentalUploadTrace?: string
experimentalNextConfigStripTypes?: boolean
debugBuildPaths?: string
experimentalCpuProf?: boolean
internalTrace?: string | boolean
}Sources: packages/next/src/cli/next-build.ts:35-55
const bundler = parseBundlerArgs(options)
if ((analyze || experimentalAnalyze) && bundler !== Bundler.Turbopack) {
printAndExit('--analyze is only compatible with the Turbopack bundler.')
}Sources: packages/next/src/cli/next-build.ts:98-102
| Option Property | CLI Flag / Source | Default / Behavior |
|---|---|---|
analyze / experimentalAnalyze | --analyze / --experimental-analyze | Compatible only with Turbopack bundler |
mangling | --no-mangling | Emits a warning when disabled for debugging |
profile | --profile | Enables build performance profiling |
debugPrerender | --debug-prerender | Forces NODE_ENV='development' during prerendering |
experimentalDebugMemoryUsage | --experimental-debug-memory-usage | Enables memory debugging startup/shutdown routines |
experimentalBuildMode | --experimental-build-mode | Restricted to 'default', 'compile', 'generate', or 'generate-env' |
Sources: packages/next/src/cli/next-build.ts:35-55
Sources: packages/next/src/cli/next-build.ts:98-168
Tip
Debugger and profiler executions (such as --inspect-wait, --cpu-prof, --heap-prof, or NEXT_CPU_PROF) bypass child process nesting and run directly in the parent process to prevent port collisions or duplicate profiling outputs.
// Debugger and profiler runs stay in one process and skip the menu. Otherwise
// the debugger would attach to the wrong process, or there would be two
// profiles.
async function shouldBuildInChild() {
const nodeOptions = getParsedNodeOptions()
if (
process.env.NEXT_CPU_PROF ||
getNodeDebugType(nodeOptions) ||
['inspect-wait', 'cpu-prof', 'heap-prof', 'prof'].some(
(flag) => nodeOptions[flag]
)
) {
return false
}
const { shouldPromptForUpgrade } = await import('../lib/upgrade/nudge.js')
return shouldPromptForUpgrade()
}Sources: packages/next/src/cli/next-build.ts:196-212
Production server CLI initialization
Options Sanitization and Port Verification
The nextStart function coordinates initialization for production server execution. It validates project directories via getProjectDir, verifies missing React dependencies, and enforces reserved port checks using isPortIsReserved and getReservedPortExplanation.
const nextStart = async (options: NextStartOptions, directory?: string) => {
const dir = getProjectDir(directory)
warnMissingReactDependencies(dir)
const hostname = options.hostname
const inspect = options.inspect
const port = options.port
const keepAliveTimeout = options.keepAliveTimeout
if (isPortIsReserved(port)) {
printAndExit(getReservedPortExplanation(port), 1)
}Sources: packages/next/src/cli/next-start.ts:43-53
| Option Property | CLI Flag / Source | Description |
|---|---|---|
port | --port, -p | Target port number for the production server |
hostname | --hostname, -H | Hostname interface to bind the server against |
inspect | --inspect / --inspect-brk | Node.js inspector connection parameters |
keepAliveTimeout | --keep-alive-timeout | Server keep-alive timeout configuration |
experimentalCpuProf | --experimental-cpu-prof | Enables CPU profiling on exit signals |
Sources: packages/next/src/cli/next-start.ts:27-35
Caution
If a requested port is designated as reserved by system conventions, printAndExit terminates process execution with an exit code of 1 before any runtime server instance can be spun up.
if (isPortIsReserved(port)) {
printAndExit(getReservedPortExplanation(port), 1)
}Sources: packages/next/src/cli/next-start.ts:51-53
Runtime Server Handoff
When debugger inspection options are passed, nextStart queries the inspector module. If the debug port is already occupied, a warning is emitted; otherwise, inspector.open starts the debugger agent on the parsed address before passing configurations to startServer.
if (inspect) {
const inspector = await import('inspector')
const isInspecting = inspector.url() !== undefined
if (isInspecting) {
Log.warn(
`The Node.js debugger port is already open at ${process.debugPort}. Ignoring '--inspect${inspect === true ? '' : `="${formatDebugAddress(inspect)}"`}'.`
)
} else {
const inspectAddress: DebugAddress =
inspect === true ? getParsedDebugAddress(true) : inspect
// TODO: Implement --inspect-wait
const wait = false
try {
inspector.open(inspectAddress.port, inspectAddress.host, wait)
} catch (error) {
console.error(
`Failed to start the Node.js inspector with --inspect="${formatDebugAddress(inspectAddress)}":`,
error
)
return process.exit(1)
}
}
}Sources: packages/next/src/cli/next-start.ts:55-77
Important
The NextServer class acts as the wrapper server implementation for next start, holding internal references to NextNodeServer through asynchronous factory loaders while exposing standard request handlers, host properties, and port parameters.
/** The wrapper server used by `next start` */
export class NextServer implements NextWrapperServer {
private serverPromise?: Promise<NextNodeServer>
private server?: NextNodeServer
private reqHandler?: NodeRequestHandler
private reqHandlerPromise?: Promise<NodeRequestHandler>
private preparedAssetPrefix?: string
public options: NextServerOptions
constructor(options: NextServerOptions) {
this.options = options
}
get hostname() {
return this.options.hostname
}
get port() {
return this.options.port
}Sources: packages/next/src/server/next.ts:178-198
Diagnostic and deprecated utility commands
Diagnostic Execution with Next Info
The next info CLI command gathers diagnostic information about the host environment, installed packages, and runtime binaries. It supports a verbose option to collect deep environmental data. When executing verbose checks, it verifies that the operating system platform is one of the supported environments: win32, linux, or darwin.
export type NextInfoOptions = {
verbose?: boolean
}Sources: packages/next/src/cli/next-info.ts:14-16
async function printVerboseInfo() {
const fs = require('fs') as typeof import('fs')
const currentPlatform = os.platform()
if (
currentPlatform !== 'win32' &&
currentPlatform !== 'linux' &&
currentPlatform !== 'darwin'
) {
console.log(
'Unsupported platform, only win32, linux, darwin are supported.'
)
return
}Sources: packages/next/src/cli/next-info.ts:257-270
The verbose diagnostic routine executes a series of system and application verification tasks, outputting reports for host properties, package versions, and binary installations.
| Diagnostic Task Title | Target Platform | Description |
|---|---|---|
Host system information | Default (win32, linux, darwin) | Collects WSL status, Docker environment checks, and CI environment detection via ci-info. |
Next.js installation | Default (win32, linux, darwin) | Reports active Node, npm, Yarn, and pnpm versions alongside related package versions (next, eslint-config-next, react, react-dom, typescript) and config output fields. |
Node.js diagnostic report | Default (win32, linux, darwin) | Queries process.report?.getReport() while sanitizing sensitive header fields such as cwd, commandLine, host, cpus, and networkInterfaces. |
next-swc installation | Default (win32, linux, darwin) | Validates SWC native bindings via loadBindings target retrieval, falling back to resolving platform architecture triples or checking local next-swc-fallback directories. |
Sources: packages/next/src/cli/next-info.ts:273-462
Warning
If the Node.js diagnostic report API is unavailable on the current runtime, the diagnostic task flags a failure status and reports that the Node.js diagnostic report is missing rather than crashing the CLI process.
title: 'Node.js diagnostic report',
scripts: {
default: async () => {
const report = process.report?.getReport()
if (!report) {
return {
messages: 'Node.js diagnostic report is not available.',
result: 'fail',
}
}Sources: packages/next/src/cli/next-info.ts:335-345
Deprecation Flow of Next Export
The deprecated next export command has been fully removed from the active CLI runtime in favor of configuration-driven static exports. When invoked, it writes an error log instructing users to migrate to the output: 'export' property inside next.config.js and immediately terminates the process with an exit code of 1.
const nextExport = () => {
error(`
\`next export\` has been removed in favor of 'output: export' in next.config.js.\nLearn more: ${cyan(
'https://nextjs.org/docs/app/building-your-application/deploying/static-exports'
)}
`)
process.exit(1)
}Sources: packages/next/src/cli/next-export.ts:4-12
Related
On This Page
OverviewEntrypoint routing and command dispatchSubcommand definitions and action bindingsArgument and environment option parsingDevelopment server CLI bootstrappingBundler resolution and options parsingWorker handoff and watchpack managementProduction build invocation and child process managementChild process execution and output interceptionCall-chain execution walkthroughsBuild options and configuration validationProduction server CLI initializationOptions Sanitization and Port VerificationRuntime Server HandoffDiagnostic and deprecated utility commandsDiagnostic Execution with Next InfoDeprecation Flow of Next ExportRelated