Skip to main content
doc0blade47/next.jsdocumentation home
Sign in

CLI Commands

Last updated Oct 12, 2026100% grounded
Relevant source files

The following files were used as context for generating this wiki page:

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 / FlagEnvironment VariableTarget Bundler
--turbopack / --turboTURBOPACK, IS_TURBOPACK_TESTBundler.Turbopack
--webpack / --custom-webpackIS_WEBPACK_TESTBundler.Webpack
(None configured)NEXT_RSPACK, NEXT_TEST_USE_RSPACKBundler.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_000

Sources: 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 * 1024

Sources: 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:

  1. nextBuild invokes buildInChild when child execution is required.
  2. buildInChild executes showUpgradeMenu to display upgrade prompts alongside the running process.
  3. showUpgradeMenu initializes and invokes flush on 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

  1. nextBuild triggers buildInChild to fork execution.
  2. buildInChild calls createPromptOutput to instantiate output control buffers.
  3. createPromptOutput returns handlers including forward to 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

  1. nextBuild evaluates shouldBuildInChild.
  2. shouldBuildInChild calls shouldPromptForUpgrade.
  3. shouldPromptForUpgrade checks terminal capabilities and calls getAgentName.
} 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 PropertyCLI Flag / SourceDefault / Behavior
analyze / experimentalAnalyze--analyze / --experimental-analyzeCompatible only with Turbopack bundler
mangling--no-manglingEmits a warning when disabled for debugging
profile--profileEnables build performance profiling
debugPrerender--debug-prerenderForces NODE_ENV='development' during prerendering
experimentalDebugMemoryUsage--experimental-debug-memory-usageEnables memory debugging startup/shutdown routines
experimentalBuildMode--experimental-build-modeRestricted 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 PropertyCLI Flag / SourceDescription
port--port, -pTarget port number for the production server
hostname--hostname, -HHostname interface to bind the server against
inspect--inspect / --inspect-brkNode.js inspector connection parameters
keepAliveTimeout--keep-alive-timeoutServer keep-alive timeout configuration
experimentalCpuProf--experimental-cpu-profEnables 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 TitleTarget PlatformDescription
Host system informationDefault (win32, linux, darwin)Collects WSL status, Docker environment checks, and CI environment detection via ci-info.
Next.js installationDefault (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 reportDefault (win32, linux, darwin)Queries process.report?.getReport() while sanitizing sensitive header fields such as cwd, commandLine, host, cpus, and networkInterfaces.
next-swc installationDefault (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

Was this page helpful?