Build with dev runtimes when --debug-prerender is set#89834
Build with dev runtimes when --debug-prerender is set#89834unstubbable merged 18 commits intocanaryfrom
--debug-prerender is set#89834Conversation
Tests Passed |
Stats from current PR🟢 1 improvement
📊 All Metrics📖 Metrics GlossaryDev Server Metrics:
Build Metrics:
Change Thresholds:
⚡ Dev Server
📦 Dev Server (Webpack) (Legacy)📦 Dev Server (Webpack)
⚡ Production Builds
📦 Production Builds (Webpack) (Legacy)📦 Production Builds (Webpack)
📦 Bundle SizesBundle Sizes⚡ TurbopackClient Main Bundles: **437 kB** → **437 kB** ✅ -82 B81 files with content-based hashes (individual files not comparable between builds) Server Middleware
Build DetailsBuild Manifests
📦 WebpackClient Main Bundles
Polyfills
Pages
Server Edge SSR
Middleware
Build DetailsBuild Manifests
Build Cache
🔄 Shared (bundler-independent)Runtimes
📝 Changed Files (23 files)Files with changes:
View diffsapp-page-exp..ntime.dev.jsfailed to diffapp-page-exp..time.prod.jsfailed to diffapp-page-tur..ntime.dev.jsfailed to diffapp-page-tur..time.prod.jsfailed to diffapp-page-tur..ntime.dev.jsfailed to diffapp-page-tur..time.prod.jsDiff too large to display app-page.runtime.dev.jsfailed to diffapp-page.runtime.prod.jsDiff too large to display app-route-ex..ntime.dev.jsDiff too large to display app-route-ex..time.prod.jsDiff too large to display app-route-tu..ntime.dev.jsDiff too large to display app-route-tu..time.prod.jsDiff too large to display app-route-tu..ntime.dev.jsDiff too large to display app-route-tu..time.prod.jsDiff too large to display app-route.runtime.dev.jsDiff too large to display app-route.ru..time.prod.jsDiff too large to display pages-api-tu..ntime.dev.jsDiff too large to display pages-api.runtime.dev.jsDiff too large to display pages-turbo...ntime.dev.jsDiff too large to display pages-turbo...time.prod.jsDiff too large to display pages.runtime.dev.jsDiff too large to display pages.runtime.prod.jsDiff too large to display server.runtime.prod.jsDiff too large to display |
57e45d9 to
0e6d5ee
Compare
When running `next build --debug-prerender`, React owner stacks are now captured and displayed in prerender error output. This makes it much easier to trace which component triggered uncached I/O or accessed request data without Suspense, matching the error quality you'd get from `next dev`. This works by auto-enabling `allowDevelopmentBuild` and setting `NODE_ENV=development` when `--debug-prerender` is active, which switches to React development builds where `captureOwnerStack()` is available. Since this bakes `NODE_ENV=development` into the bundles at build time, dev-only features like HMR, WebSocket connections, and debug channels would incorrectly activate during `next start`. To prevent this, all dev-server-specific code paths now check `process.env.NEXT_PHASE` (which reflects the actual runtime phase) instead of `NODE_ENV` or `renderOpts.dev`. Key changes: - `config.ts` auto-enables `allowDevelopmentBuild` and sets `NODE_ENV=development` when `debugPrerender` is active - `define-env.ts` inlines `NEXT_PHASE` into client bundles so dev-server features (HMR, WebSocket, dev overlay, debug channels) are dead-code eliminated in `--debug-prerender` builds - `router-server.ts` sets `process.env.NEXT_PHASE` at startup so server-side code can distinguish `next dev` from `next start` - `route-module.ts` derives `isDev` from `NEXT_PHASE` instead of `NODE_ENV` to avoid baking the wrong value at build time - `renderOpts.dev` and `workStore.dev` are removed — all consumers now use `NEXT_PHASE` (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 owner stacks so they display as readable file paths - `static-paths/app.ts` uses `NEXT_PHASE` instead of `NODE_ENV` to determine build-time route pregeneration support
Avoids incompatibility with the Edge runtime, which doesn't support `process.features` and causes the entire `constants` module to fail loading when imported in that environment.
This reverts commit bc31035.
All callers either pass the result to `findSourceMap`/`nativeFindSourceMap` (needs encoded URL) or to display via `frameToString` which handles `file://` URLs through `url.fileURLToPath()` (decodes properly). Removing `decodeURI` should be safe for all callers.
fb8b80a to
daca900
Compare
Let's see if any tests still fail without those changes.
5aaa497 to
da40d0f
Compare
So that relative `sources` in the source map resolve against the real chunk URL, not the virtual one.
|
Notifying the following users due to files changed in this PR based on this repo's notify modifiers: @timneutkens, @ijjk, @shuding, @huozhi: |
eps1lon
left a comment
There was a problem hiding this comment.
I would adjust the PR title. This is way more invasive than just owner stacks. How does --debug-prerender interact with next start e.g. do we consider the built app runnable or do we not guarantee that it works?
next build --debug-prerender--debug-prerender is set
Follow-up for #89834. In `prerenderAndAbortInSequentialTasks`, React's `finishHalt` (scheduled via `setImmediate` from `abort()`) could race with the component's pending `setTimeout` callback. When both ended up in the same timer phase, the component timer would fire after `abort()` but before `finishHalt`, linking the async graph and producing the more precise `await` location (21:9). When `finishHalt` won the race, it read an unlinked graph and fell back to the function declaration (20:16). Adding `DANGEROUSLY_runPendingImmediatesAfterCurrentTask()` to the abort task captures `finishHalt` as a fast immediate, ensuring it runs right after `abort()` before any other timers. This makes the result deterministic at the cost of always producing the less precise stack frame (function declaration instead of `await` expression). The resolve step is split into a separate task because it needs to wait for the abort's fast immediates to complete. When using `--debug-prerender`, we're also re-adding the hint to run `next dev` for even better stack traces, since dev mode can produce the precise `await` location by running the render to completion and resolving the I/O promises. [Flakiness metric](https://app.datadoghq.com/ci/test/runs?query=test_level%3Atest%20%40git.repository.id%3A%22github.com%2Fvercel%2Fnext.js%22%20%40test.type%3A%22nextjs%22%20%40test.status%3A%22fail%22%20%40test.suite%3A%22Cache%20Components%20Errors%22%20%40git.branch%3Acanary%20-%40ci.pipeline.name%3Atest-e2e-deploy-release&agg_m=count&agg_m_source=base&agg_t=count¤tTab=overview&eventStack=&fromUser=false&index=citest&start=1770404554855&end=1771009354855&paused=false)
…ces (#89969) Follow-up for #89834. In `prerenderAndAbortInSequentialTasks`, React's `finishHalt` (scheduled via `setImmediate` from `abort()`) could race with the component's pending `setTimeout` callback. When both ended up in the same timer phase, the component timer would fire after `abort()` but before `finishHalt`, linking the async graph and producing the more precise `await` location (21:9). When `finishHalt` won the race, it read an unlinked graph and fell back to the function declaration (20:16). Adding `DANGEROUSLY_runPendingImmediatesAfterCurrentTask()` to the abort task captures `finishHalt` as a fast immediate, ensuring it runs right after `abort()` before any other timers. This makes the result deterministic at the cost of always producing the less precise stack frame (function declaration instead of `await` expression). The resolve step is split into a separate task because it needs to wait for the abort's fast immediates to complete. When using `--debug-prerender`, we're also re-adding the hint to run `next dev` for even better stack traces, since dev mode can produce the precise `await` location by running the render to completion and resolving the I/O promises. [Flakiness metric](https://app.datadoghq.com/ci/test/runs?query=test_level%3Atest%20%40git.repository.id%3A%22github.com%2Fvercel%2Fnext.js%22%20%40test.type%3A%22nextjs%22%20%40test.status%3A%22fail%22%20%40test.suite%3A%22Cache%20Components%20Errors%22%20%40git.branch%3Acanary%20-%40ci.pipeline.name%3Atest-e2e-deploy-release&agg_m=count&agg_m_source=base&agg_t=count¤tTab=overview&eventStack=&fromUser=false&index=citest&start=1770404554855&end=1771009354855&paused=false)

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-prerenderonly enabled source maps and disabled minification. Now it also auto-enablesallowDevelopmentBuildand setsNODE_ENV=development, which loads React development builds wherecaptureOwnerStack()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 usingnext start. To solve this, we introduceprocess.env.__NEXT_DEV_SERVER, an internal env var that is truthy only duringnext dev. In client bundles, it's inlined at build time ('1'fornext dev,''fornext 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 betweennext dev(where it's set) andnext build --debug-prerender(where it's not). Meanwhile,NODE_ENVcontinues 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.devpattern, which was unreliable becauseRouteModule.isDevwas derived fromNODE_ENVat compile time. WhenallowDevelopmentBuildsetNODE_ENV=development,isDevwould be compiled astrueand incorrectly activate all dev guards duringnext start.Key changes:
config.tsauto-enablesallowDevelopmentBuildand setsNODE_ENV=developmentwhen--debug-prerenderis activedefine-env.tsinlines__NEXT_DEV_SERVERinto all bundles (truthy for dev, falsy for build) so dev-server features are dead-code eliminated in production and--debug-prerenderbuildsnext-dev.tsandnext.tsset__NEXT_DEV_SERVERin the process environment for externalized server-side coderenderOpts.devandworkStore.devare removed — all consumers now use__NEXT_DEV_SERVER(for dev-server features) orNODE_ENV(for error formatting that should work in both dev and--debug-prerenderbuilds)patch-error-inspect.tsdevirtualizes React server URLs in source map URLs so they display as readable file paths