Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 43 additions & 0 deletions docs/01-app/03-api-reference/06-cli/next.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ The following commands are available:
| `--experimental-https-cert <path>` | Path to a HTTPS certificate file. |
| `--experimental-https-ca <path>` | Path to a HTTPS certificate authority file. |
| `--experimental-upload-trace <traceUrl>` | Reports a subset of the debugging trace to a remote HTTP URL. |
| `--experimental-cpu-prof` | Enables CPU profiling using V8's inspector. Profiles are saved to `.next/cpu-profiles/` on exit. |

### `next build` options

Expand Down Expand Up @@ -87,6 +88,7 @@ The following options are available for the `next build` command:
| `--experimental-build-mode [mode]` | Uses an experimental build mode. (choices: "compile", "generate", default: "default") |
| `--debug-prerender` | Debug prerender errors in development. |
| `--debug-build-paths=<patterns>` | Build only specific routes for debugging. |
| `--experimental-cpu-prof` | Enables CPU profiling using V8's inspector. Profiles are saved to `.next/cpu-profiles/` on exit. |

### `next start` options

Expand All @@ -101,6 +103,7 @@ The following options are available for the `next start` command:
| `-p` or `--port <port>` | Specify a port number on which to start the application. (default: 3000, env: PORT) |
| `-H` or `--hostname <hostname>` | Specify a hostname on which to start the application (default: 0.0.0.0). |
| `--keepAliveTimeout <keepAliveTimeout>` | Specify the maximum amount of milliseconds to wait before closing the inactive connections. |
| `--experimental-cpu-prof` | Enables CPU profiling using V8's inspector. Profiles are saved to `.next/cpu-profiles/` on exit. |

### `next info` options

Expand Down Expand Up @@ -334,6 +337,46 @@ NODE_OPTIONS='-r esm' next
NODE_OPTIONS='--inspect' next
```

### CPU profiling

You can capture CPU profiles to analyze performance bottlenecks in your Next.js application. The `--experimental-cpu-prof` flag enables V8's built-in CPU profiler and saves profiles to `.next/cpu-profiles/` when the process exits:

```bash filename="Terminal"
# Profile the build process
next build --experimental-cpu-prof

# Profile the dev server (profile saved on Ctrl+C or SIGTERM)
next dev --experimental-cpu-prof

# Profile the production server
next start --experimental-cpu-prof
```

The generated `.cpuprofile` files can be opened in Chrome DevTools (Performance tab → Load profile) or other V8-compatible profiling tools.

> **Good to know**: Profile files are named with a descriptive prefix and timestamp. The profiles generated depend on the command:
>
> **`next dev`:**
>
> - `dev-main-*` - Parent process (dev server orchestration)
> - `dev-server-*` - Child server process (request handling and rendering) - this is typically what you want to analyze
>
> **`next build` (Turbopack):**
>
> - `build-main-*` - Main build orchestration process
> - `build-turbopack-*` - Turbopack compilation worker
>
> **`next build` (Webpack):**
>
> - `build-main-*` - Main build orchestration process
> - `build-webpack-client-*` - Client bundle compilation worker
> - `build-webpack-server-*` - Server bundle compilation worker
> - `build-webpack-edge-server-*` - Edge runtime compilation worker
>
> **`next start`:**
>
> - `start-main-*` - Production server process

| Version | Changes |
| --------- | ------------------------------------------------------------------------------- |
| `v16.1.0` | Add the `next experimental-analyze` command |
Expand Down
41 changes: 40 additions & 1 deletion packages/next/src/bin/next.ts
Original file line number Diff line number Diff line change
Expand Up @@ -186,15 +186,32 @@ program
'--debug-build-paths <patterns>',
'Comma-separated glob patterns or explicit paths for selective builds. Examples: "app/*", "app/page.tsx", "app/**/page.tsx"'
)
.option(
'--experimental-cpu-prof',
'Enable CPU profiling. Profile is saved to .next/cpu-profiles/ on completion.'
)
.action((directory: string, options: NextBuildOptions) => {
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 = 'build-main'
const { join } = require('path') as typeof import('path')
const dir = directory || process.cwd()
process.env.NEXT_CPU_PROF_DIR = join(dir, '.next', 'cpu-profiles')
}

// ensure process exits after build completes so open handles/connections
// don't cause process to hang
return import('../cli/next-build.js').then((mod) =>
mod.nextBuild(options, directory).then(() => process.exit(0))
mod.nextBuild(options, directory).then(async () => {
// Save CPU profile before exiting if enabled
if (options.experimentalCpuProf) {
await mod.saveCpuProfile()
}
process.exit(0)
})
)
})
.usage('[directory] [options]')
Expand Down Expand Up @@ -297,11 +314,22 @@ program
'--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/cpu-profiles/ on exit.'
)
.action(
(directory: string, options: NextDevOptions, { _optionValueSources }) => {
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 = 'dev-main'
const { join } = require('path') as typeof import('path')
const dir = directory || process.cwd()
process.env.NEXT_CPU_PROF_DIR = join(dir, '.next', 'cpu-profiles')
}
const portSource = _optionValueSources.port
import('../cli/next-dev.js').then((mod) =>
mod.nextDev(options, portSource, directory)
Expand Down Expand Up @@ -363,10 +391,21 @@ program
'--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/cpu-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'
const { join } = require('path') as typeof import('path')
const dir = directory || process.cwd()
process.env.NEXT_CPU_PROF_DIR = join(dir, '.next', 'cpu-profiles')
}
return import('../cli/next-start.js').then((mod) =>
mod.nextStart(options, directory)
)
Expand Down
2 changes: 2 additions & 0 deletions packages/next/src/build/collect-build-traces.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
// Import cpu-profile to start profiling early if enabled
import '../server/lib/cpu-profile'
import { Span } from '../trace'
import type { NextConfigComplete } from '../server/config-shared'

Expand Down
18 changes: 18 additions & 0 deletions packages/next/src/build/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -831,6 +831,15 @@ export function createStaticWorker(
isolatedMemory: true,
enableWorkerThreads: config.experimental.workerThreads,
exposedMethods: staticWorkerExposedMethods,
forkOptions: process.env.NEXT_CPU_PROF
? {
env: {
NEXT_CPU_PROF: '1',
NEXT_CPU_PROF_DIR: process.env.NEXT_CPU_PROF_DIR,
__NEXT_PRIVATE_CPU_PROFILE: 'build-static-worker',
},
}
: undefined,
}) as StaticWorker
}

Expand Down Expand Up @@ -1735,6 +1744,15 @@ export default async function build(
isolatedMemory: false,
numWorkers: 1,
exposedMethods: ['collectBuildTraces'],
forkOptions: process.env.NEXT_CPU_PROF
? {
env: {
NEXT_CPU_PROF: '1',
NEXT_CPU_PROF_DIR: process.env.NEXT_CPU_PROF_DIR,
__NEXT_PRIVATE_CPU_PROFILE: 'build-trace-worker',
},
}
: undefined,
}
) as Worker & typeof import('./collect-build-traces')

Expand Down
4 changes: 4 additions & 0 deletions packages/next/src/build/turbopack-build/impl.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
// Import cpu-profile first to start profiling early if enabled
import { saveCpuProfile } from '../../server/lib/cpu-profile'
import path from 'path'
import { validateTurboNextConfig } from '../../lib/turbopack-warning'
import { isFileSystemCacheEnabledForBuild } from '../../shared/lib/turbopack/utils'
Expand Down Expand Up @@ -261,6 +263,8 @@ export async function workerMain(workerData: {
} finally {
// Always flush telemetry before worker exits (waits for async operations like setTimeout in debug mode)
await telemetry.flush()
// Save CPU profile before worker exits
await saveCpuProfile()
}
}

Expand Down
7 changes: 7 additions & 0 deletions packages/next/src/build/turbopack-build/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,13 @@ async function turbopackBuildWithWorker(): ReturnType<
forkOptions: {
env: {
NEXT_PRIVATE_BUILD_WORKER: '1',
...(process.env.NEXT_CPU_PROF
? {
NEXT_CPU_PROF: '1',
NEXT_CPU_PROF_DIR: process.env.NEXT_CPU_PROF_DIR,
__NEXT_PRIVATE_CPU_PROFILE: 'build-turbopack',
}
: undefined),
},
},
}) as Worker & typeof import('./impl')
Expand Down
5 changes: 5 additions & 0 deletions packages/next/src/build/webpack-build/impl.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
// Import cpu-profile first to start profiling early if enabled
import { saveCpuProfile } from '../../server/lib/cpu-profile'
import type { webpack } from 'next/dist/compiled/webpack/webpack'
import { stringBufferUtils } from 'next/dist/compiled/webpack-sources3'
import { red } from '../../lib/picocolors'
Expand Down Expand Up @@ -416,5 +418,8 @@ export async function workerMain(workerData: {
NextBuildContext.nextBuildSpan.stop()
await telemetry.flush()

// Save CPU profile before worker exits
await saveCpuProfile()

return { ...result, debugTraceEvents: getTraceEvents() }
}
7 changes: 7 additions & 0 deletions packages/next/src/build/webpack-build/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,13 @@ async function webpackBuildWithWorker(
forkOptions: {
env: {
NEXT_PRIVATE_BUILD_WORKER: '1',
...(process.env.NEXT_CPU_PROF
? {
NEXT_CPU_PROF: '1',
NEXT_CPU_PROF_DIR: process.env.NEXT_CPU_PROF_DIR,
__NEXT_PRIVATE_CPU_PROFILE: `build-webpack-${compilerName}`,
}
: undefined),
},
},
}) as Worker & typeof import('./impl')
Expand Down
2 changes: 2 additions & 0 deletions packages/next/src/build/worker.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import '../server/require-hook'
// Import cpu-profile to start profiling early if enabled
import '../server/lib/cpu-profile'

export {
getDefinedNamedExports,
Expand Down
11 changes: 9 additions & 2 deletions packages/next/src/cli/next-analyze.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#!/usr/bin/env node

import '../server/lib/cpu-profile'
import { saveCpuProfile } from '../server/lib/cpu-profile'
import { existsSync } from 'fs'
import { italic } from '../lib/picocolors'
import analyze from '../build/analyze'
Expand All @@ -18,8 +19,14 @@ export type NextAnalyzeOptions = {
}

const nextAnalyze = async (options: NextAnalyzeOptions, directory?: string) => {
process.on('SIGTERM', () => process.exit(143))
process.on('SIGINT', () => process.exit(130))
process.on('SIGTERM', () => {
saveCpuProfile()
process.exit(143)
})
process.on('SIGINT', () => {
saveCpuProfile()
process.exit(130)
})

const { profile, mangling, experimentalAppOnly, output, port } = options

Expand Down
15 changes: 11 additions & 4 deletions packages/next/src/cli/next-build.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#!/usr/bin/env node

import '../server/lib/cpu-profile'
import { saveCpuProfile } from '../server/lib/cpu-profile'
import { existsSync } from 'fs'
import { italic } from '../lib/picocolors'
import build from '../build'
Expand Down Expand Up @@ -32,11 +32,18 @@ export type NextBuildOptions = {
experimentalUploadTrace?: string
experimentalNextConfigStripTypes?: boolean
debugBuildPaths?: string
experimentalCpuProf?: boolean
}

const nextBuild = async (options: NextBuildOptions, directory?: string) => {
process.on('SIGTERM', () => process.exit(143))
process.on('SIGINT', () => process.exit(130))
process.on('SIGTERM', () => {
saveCpuProfile()
process.exit(143)
})
process.on('SIGINT', () => {
saveCpuProfile()
process.exit(130)
})

const {
experimentalAnalyze,
Expand Down Expand Up @@ -157,4 +164,4 @@ const nextBuild = async (options: NextBuildOptions, directory?: string) => {
})
}

export { nextBuild }
export { nextBuild, saveCpuProfile }
19 changes: 19 additions & 0 deletions packages/next/src/cli/next-dev.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#!/usr/bin/env node

import '../server/lib/cpu-profile'
import { saveCpuProfile } from '../server/lib/cpu-profile'
import type { StartServerOptions } from '../server/lib/start-server'
import {
RESTART_EXIT_CODE,
Expand Down Expand Up @@ -60,6 +61,7 @@ export type NextDevOptions = {
experimentalHttpsCa?: string
experimentalUploadTrace?: string
experimentalNextConfigStripTypes?: boolean
experimentalCpuProf?: boolean
}

type PortSource = 'cli' | 'default' | 'env'
Expand Down Expand Up @@ -162,6 +164,9 @@ const handleSessionStop = async (signal: NodeJS.Signals | number | null) => {
})
}

// Save CPU profile if it was enabled (before exiting)
saveCpuProfile()

// ensure we re-enable the terminal cursor before exiting
// the program, or the cursor could remain hidden
process.stdout.write('\x1B[?25h')
Expand Down Expand Up @@ -189,6 +194,12 @@ const nextDev = async (
printAndExit(`> No such directory exists as the project root: ${dir}`)
}

if (options.experimentalCpuProf) {
Log.info(
`CPU profiling enabled. Profile will be saved to .next/cpu-profiles/ on exit (Ctrl+C).`
)
}

async function preflight(skipOnReboot: boolean) {
const { getPackageVersion, getDependencies } = (await Promise.resolve(
require('../lib/get-package-version') as typeof import('../lib/get-package-version')
Expand Down Expand Up @@ -318,6 +329,14 @@ const nextDev = async (
// https://github.com/nodejs/node/issues/29949
WATCHPACK_WATCHER_LIMIT:
os.platform() === 'darwin' ? '20' : undefined,
// Enable CPU profiling if requested
...(options.experimentalCpuProf
? {
NEXT_CPU_PROF: '1',
NEXT_CPU_PROF_DIR: path.join(dir, '.next', 'cpu-profiles'),
__NEXT_PRIVATE_CPU_PROFILE: 'dev-server',
}
: undefined),
},
})

Expand Down
Loading
Loading