Skip to content

Commit e9b96b4

Browse files
authored
Build with dev runtimes when --debug-prerender is set (#89834)
When running `next build --debug-prerender`, React owner stacks are now captured and displayed in prerender error output. This makes it much easier to diagnose which component triggered uncached I/O or accessed request data without Suspense. Previously, `--debug-prerender` only enabled source maps and disabled minification. Now it also auto-enables `allowDevelopmentBuild` and sets `NODE_ENV=development`, which loads React development builds where `captureOwnerStack()` is available. The main challenge is that with `NODE_ENV=development`, both server and client bundles include dev-only code paths (HMR, WebSocket connections, dev overlay, debug channel, etc.) that expect a running dev server. We don't want these when using `next start`. To solve this, we introduce `process.env.__NEXT_DEV_SERVER`, an internal env var that is truthy only during `next dev`. In client bundles, it's inlined at build time (`'1'` for `next dev`, `''` for `next build`). In production server runtime bundles, it's inlined as `''` for dead-code elimination. In development server runtime bundles, it's left as a runtime check because those bundles are shared between `next dev` (where it's set) and `next build --debug-prerender` (where it's not). Meanwhile, `NODE_ENV` continues to control React's dev/prod mode and error formatting, which is exactly what we want for `--debug-prerender`. This also replaces the previous `renderOpts.dev` / `workStore.dev` pattern, which was unreliable because `RouteModule.isDev` was derived from `NODE_ENV` at compile time. When `allowDevelopmentBuild` set `NODE_ENV=development`, `isDev` would be compiled as `true` and incorrectly activate all dev guards during `next start`. Key changes: - `config.ts` auto-enables `allowDevelopmentBuild` and sets `NODE_ENV=development` when `--debug-prerender` is active - `define-env.ts` inlines `__NEXT_DEV_SERVER` into all bundles (truthy for dev, falsy for build) so dev-server features are dead-code eliminated in production and `--debug-prerender` builds - `next-dev.ts` and `next.ts` set `__NEXT_DEV_SERVER` in the process environment for externalized server-side code - `renderOpts.dev` and `workStore.dev` are removed — all consumers now use `__NEXT_DEV_SERVER` (for dev-server features) or `NODE_ENV` (for error formatting that should work in both dev and `--debug-prerender` builds) - `patch-error-inspect.ts` devirtualizes React server URLs in source map URLs so they display as readable file paths
1 parent b737b04 commit e9b96b4

37 files changed

+483
-404
lines changed

AGENTS.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -246,6 +246,13 @@ See [Codebase structure](#codebase-structure) above for detailed explanations.
246246
- Use `DEBUG=next:*` for debug logging
247247
- Use `NEXT_TELEMETRY_DISABLED=1` when testing locally
248248

249+
### `NODE_ENV` vs `__NEXT_DEV_SERVER`
250+
251+
Both `next dev` and `next build --debug-prerender` produce bundles with `NODE_ENV=development`. Use `process.env.__NEXT_DEV_SERVER` to distinguish between them:
252+
253+
- `process.env.NODE_ENV !== 'production'` — code that should exist in dev bundles but be eliminated from prod bundles. This is a build-time check.
254+
- `process.env.__NEXT_DEV_SERVER` — code that should only run with the dev server (`next dev`), not during `next build --debug-prerender` or `next start`.
255+
249256
## Commit and PR Style
250257

251258
- Do NOT add "Generated with Claude Code" or co-author footers to commits or PRs

contributing/core/developing.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,13 @@ $ pnpm unpack-next path/to/project
100100

101101
The dev overlay is a feature of Next.js that allows you to see the internal state of the app including the errors. To learn more about contributing to the dev overlay, see the [Dev Overlay README.md](../../packages/next/src/client/components/react-dev-overlay/README.md).
102102

103+
## `NODE_ENV` vs `__NEXT_DEV_SERVER`
104+
105+
Both `next dev` and `next build --debug-prerender` produce bundles with `NODE_ENV=development`. Use `process.env.__NEXT_DEV_SERVER` to distinguish between them:
106+
107+
- `process.env.NODE_ENV !== 'production'` — code that should exist in dev bundles but be eliminated from prod bundles. This is a build-time check.
108+
- `process.env.__NEXT_DEV_SERVER` — code that should only run with the dev server (`next dev`), not during `next build --debug-prerender` or `next start`.
109+
103110
## Recover disk space
104111

105112
Rust builds quickly add up to a lot of disk space, you can clean up old artifacts with this command:

packages/next/next-runtime.webpack-config.js

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -209,8 +209,11 @@ module.exports = ({ dev, turbo, bundleType, experimental, ...rest }) => {
209209
'process.env.NEXT_MINIMAL': JSON.stringify('true'),
210210
'this.serverOptions.experimentalTestProxy': JSON.stringify(false),
211211
'this.minimalMode': JSON.stringify(true),
212-
'this.renderOpts.dev': JSON.stringify(dev),
213-
'renderOpts.dev': JSON.stringify(dev),
212+
// Only inline __NEXT_DEV_SERVER in prod bundles (for dead-code
213+
// elimination). Dev bundles must keep it as a runtime check because
214+
// they're shared between `next dev` (where it's set) and `next build
215+
// --debug-prerender` (where it's not).
216+
...(dev ? {} : { 'process.env.__NEXT_DEV_SERVER': JSON.stringify('') }),
214217
'process.env.NODE_ENV': JSON.stringify(
215218
dev ? 'development' : 'production'
216219
),

packages/next/src/bin/next.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,10 @@ program
194194
'Enable CPU profiling. Profile is saved to .next/cpu-profiles/ on completion.'
195195
)
196196
.action((directory: string, options: NextBuildOptions) => {
197+
if (options.debugPrerender) {
198+
// @ts-expect-error not readonly
199+
process.env.NODE_ENV = 'development'
200+
}
197201
if (options.experimentalNextConfigStripTypes) {
198202
process.env.__NEXT_NODE_NATIVE_TS_LOADER_ENABLED = 'true'
199203
}

packages/next/src/build/define-env.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,7 @@ export function getDefineEnv({
157157
dev || config.experimental.allowDevelopmentBuild
158158
? 'development'
159159
: 'production',
160+
'process.env.__NEXT_DEV_SERVER': dev ? '1' : '',
160161
'process.env.NEXT_RUNTIME': isEdgeServer
161162
? 'edge'
162163
: isNodeServer

packages/next/src/build/static-paths/app.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -836,7 +836,7 @@ export async function buildAppStaticPaths({
836836
)
837837

838838
const supportsRoutePreGeneration =
839-
hadAllParamsGenerated || process.env.NODE_ENV === 'production'
839+
hadAllParamsGenerated || !process.env.__NEXT_DEV_SERVER
840840

841841
const fallbackMode = dynamicParams
842842
? supportsRoutePreGeneration

packages/next/src/build/templates/app-page.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -762,7 +762,6 @@ export async function handler(
762762
routerServerContext
763763
),
764764
err: getRequestMeta(req, 'invokeError'),
765-
dev: routeModule.isDev,
766765
},
767766
}
768767

packages/next/src/build/templates/edge-ssr-app.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -197,7 +197,6 @@ async function requestHandler(
197197
silenceLog,
198198
routerServerContext
199199
),
200-
dev: pageRouteModule.isDev,
201200
},
202201
}
203202
let finalStatus = 200

packages/next/src/cli/next-build.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -85,8 +85,8 @@ const nextBuild = async (options: NextBuildOptions, directory?: string) => {
8585

8686
if (debugPrerender) {
8787
warn(
88-
`Prerendering is running in debug mode. ${italic(
89-
'Note: This may affect performance and should not be used for production.'
88+
`Prerendering is running in debug mode with NODE_ENV='development'. ${italic(
89+
'This will affect performance and should not be used for production.'
9090
)}`
9191
)
9292
}

packages/next/src/cli/next-dev.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -328,6 +328,7 @@ const nextDev = async (
328328
env: {
329329
...defaultEnv,
330330
...(isTurbopack ? { TURBOPACK: process.env.TURBOPACK } : undefined),
331+
__NEXT_DEV_SERVER: '1',
331332
NEXT_PRIVATE_START_TIME: process.env.NEXT_PRIVATE_START_TIME,
332333
NEXT_PRIVATE_WORKER: '1',
333334
NEXT_PRIVATE_TRACE_ID: traceId,

0 commit comments

Comments
 (0)