mirror of
https://github.com/vercel/next.js.git
synced 2026-09-20 02:25:18 +08:00
codex/fallback-root-cache
516 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
41ef17c645 |
Add experimental agent feedback workflow (#98582)
## Summary Adds an off-by-default `experimental.agentFeedback` workflow for collecting Next.js friction without interrupting the user’s task. - `next dev` writes a small managed block to the project agent-instructions file. - Agent entry points, including `next-dev-loop`, queue possible issues instead of opening duplicate forms. - At the final stopping point, an internal command checks the remote gate and returns the reporting protocol bundled with that Next.js version. The agent attempts to anonymize each qualifying issue and opens a separate review form once. If the browser does not open, it prints the URL for the user without troubleshooting the failure. - Report links no longer contain a public page token. Nothing is sent until the user submits the form, which remains rate-limited and uses a server-only ingest credential. - Disabling `agentFeedback` or `agentRules` removes only its managed block on the next `next dev`. Empty generated agent files are cleaned up; user-authored content is preserved. - Adds API references for both options and updates the AI agents guide. Bundling the protocol keeps the managed block small and allows the report format to evolve with each Next.js version. The receiving form is implemented in [vercel/front#85739](https://github.com/vercel/front/pull/85739) and should deploy before this workflow is enabled. ## Verification - `NEXT_SKIP_ISOLATE=1 pnpm test-dev-turbo test/development/app-dir/agent-rules-auto-generate/agent-rules-auto-generate.test.ts` - `pnpm jest packages/next/src/server/lib/generate-agent-files.test.ts packages/next/src/cli/internal` - `npx eslint --config eslint.config.mjs packages/create-next-app/helpers/generate-agent-files.ts packages/next/src/server/config-shared.ts packages/next/src/server/lib/generate-agent-files.ts test/development/app-dir/agent-rules-auto-generate/agent-rules-auto-generate.test.ts` <!-- NEXT_JS_LLM --> |
||
|
|
e6688470ef |
Properly disable laziness on next/dynamic (#98828)
next/dynamic assumes a eager semantic for css gathering, we are making sure that holds even when lazy dynamic imports are enabled |
||
|
|
7c366e61be |
[turbopack] Track webpack loader build dependency files (#98777)
Track exact existing files and warn when loaders register unsupported build dependency inputs. I'm going to take this bit by bit so it is nice and reviewable. Does add some noise. But the final fully supported thing was just too large. So I will keep removing error cases on each commit. |
||
|
|
bdbf63aef7 |
fix(next-custom-transforms): never place generated imports before directives (#98717)
## Summary
**What?** Fixes the error reported for a module that has a top-level
`"use client"` directive and an inline `"use server"` function
directive, e.g. `<form action={async () => { "use server" }} />` in a
client page.
**Why?** Under Turbopack this reported a bogus error — `The "use client"
directive must be placed before other expressions` — even though the
directive was already at the top of the file. The actual mistake (an
inline server action inside a Client Component) was never surfaced.
**How?** On Turbopack's RSC layer the server actions transform runs
*before* the React Server Components assert. When it hoists an inline
action it prepended its generated imports (`registerServerReference`,
action encryption, cache runtime) at index 0 of the module — *above* the
`"use client"` directive, which the transform does not consume. The RSC
assert then saw an import before the directive and reported it as
misplaced.
Co-authored-by: vercel-fleet-prod[bot] <318278635+vercel-fleet-prod[bot]@users.noreply.github.com>
|
||
|
|
c70b96b373 |
Fix addMissingDependency (#98588)
While before this change we had addMissingDependency, we didn't properly respond when the file for that missing dep was added. The way webpack does this internally is not the exact same shape as the IPC here, it has them as a separate field. I could not find any reason not to just add them to the filePaths here. So that's what I did to keep things simple. |
||
|
|
4d925698a8 |
Expose App Router runtime errors over HMR (#98438)
Ported from #98040 by @marcoshernanz. > [!TIP] > Best reviewed commit by commit. ### Why? External development tools need browser runtime-error state, not just build errors, to display application failures. Reporting remains opt-in so applications that do not need it avoid client serialization, HMR transport, server formatting, buffering, and rebroadcast overhead. ### How? Add `experimental.exposeRuntimeErrorsToHMR` for App Router development with Webpack and Turbopack. Internal integrations can also enable the same behavior without changing `next.config` by setting `__NEXT_EXPOSE_RUNTIME_ERRORS_TO_HMR` to any non-empty value. When enabled, the HMR WebSocket emits `runtimeErrors` snapshots containing: - The current pathname and browser client/document identifiers. - Error types, names, messages, and source-mapped stacks. - Fatality and optional catching-boundary details (`default-global`, `custom-global`, or `custom`). Snapshots update as errors and navigation change. New or reconnected observers receive the current state, and disconnecting a browser clears its reported errors. An empty snapshot means there are no currently reported errors; it does not guarantee application recovery. Reporting is disabled by default. Pages Router, MCP `get_errors`, and production behavior are unchanged. <!-- NEXT_JS_LLM --> Co-authored-by: Marcos Hernanz <96699542+marcoshernanz@users.noreply.github.com> |
||
|
|
d155ba9ebf |
[turbopack] Lazily compile dynamic imports in development (client side) (#97203)
## Summary Defer compiling client-side dynamic import targets in Turbopack development until the browser requests their manifest chunk. This avoids compiling untouched dynamic imports while preserving server-side imports, CSS loading, Server Actions, source maps, and Fast Refresh behavior. One caveat on this is next/dynamic still does some eagerness. I looked a bit at changing things. But it breaks some of the guarantees there and decided to leave it off the table. --------- Co-authored-by: Niklas Mischkulnig <4586894+mischnic@users.noreply.github.com> |
||
|
|
c5741d5230 |
fix(devtools): handle pointercancel when dragging the indicator (#98506)
Fixes #98468 ## Claude explanation of the fix `useDrag` listened for `pointermove` and `pointerup`, but never `pointercancel`. When a user agent cancels a gesture — a browser or system gesture takes over, a second finger arrives — it fires `pointercancel` and no `pointerup`, implicitly releasing pointer capture as it does. So `cancel()` never ran: the state machine stayed `{ state: 'drag' }` and `cleanup.current` was never invoked, leaving the `pointermove`/`pointerup` listeners on `window`. The next `pointerup` anywhere on the page then hit those orphaned listeners, reached `cancel()` with the state still `'drag'`, and called `releasePointerCapture()` on a pointer that no longer existed — the reported `NotFoundError`. Each cancelled drag also leaked another listener pair. This registers `pointercancel` alongside `pointerup` and removes it in the same cleanup, so the machine unwinds when a gesture is cancelled; and it releases pointer capture only when `hasPointerCapture()` says it is still held, which is preferable to `try/catch` swallowing genuine faults too. Note `touch-action: none` (#97723) removed the common touch trigger, but not the defect: on canary a forced `touchCancel` still throws. https://github.com/user-attachments/assets/45a9524d-9ffa-40b6-b59a-5af9d947f8c7 ## Fixed version https://github.com/user-attachments/assets/b5ada68c-7442-42ef-a256-bacc512fbeca ## Browser checks - [x] Chrome, as describe by the bug report - [x] FF works fine pre and post fix (with and without the pointer simulation) - [x] iOS safari simulator, before the fix, I can't see an error, but, the drag gets frozen, with this fix it works correctly https://github.com/user-attachments/assets/bcfdc786-bada-4462-b530-299523fdca6d 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
1a295a631c |
Align fallback parameter staging with shell validation (#98512)
`next dev` reported missing Suspense boundaries in layouts that the
build accepted. For `/[top]/items/[bottom]`, `generateStaticParams`
returned `[{ top: 't1' }]`, and the page wrapped its `bottom` access in
Suspense. A request for `/t2/items/b2` still reported the layout's
access to `top` as an error. Development treated both parameters as
unresolved because the requested `top` value was not generated, although
the required static shell only needed to defer `bottom`.
Production Cached Navigations used the same overly broad parameter set
and omitted eligible static content from repeat visits. Resumes also
reconstructed that set from the original build manifest, even after an
on-demand prerender had produced a more complete shell.
This replaces the alternative proposed in #98460. That proposal fixes
the development error by selecting a separate fallback parameter set for
validation while keeping response staging unchanged. The two sets are
not intended to differ for the same shell target. Correcting only
validation would preserve the incorrect staging decision and leave
production Cached Navigations without the eligible static content.
Staging and static-shell validation now use one `stagedFallbackParams`
set for each selected shell target. Required partial shells retain their
unresolved parameters, even when a later request can complete them.
Prerenders record their parameter set in postponed state, and resumes
use that recorded set rather than reconstructing it from the generic
source.
Dynamic RSC requests now read and revalidate the completed-shell cache
key, so they find partial artifacts that the fully resolved pathname
lookup missed. Request metadata and `RequestStore` both expose the set
as `stagedFallbackParams`. Action-only fallback detection checks actual
unresolved parameters instead of treating deferred values as missing.
|
||
|
|
6ba71046b0 |
[test] Move the harness off node-fetch (#98195)
Node.js ships with a built-in `fetch` now so `node-fetch` is no longer necessary. Mostly motivated by tracing Node.js deprecation warnings which originated from `node-fetch` by calling the deprecated `url.parse`. Call sites keep working through a compatibility type on `fetchViaHTTP` that translates node-fetch-only options: Instead of `agent` we pass to `http(s)` directly, `timeout` becomes `AbortSignal.timeout`, and Node.js readable streams are accepted as bodies with `duplex: 'half'` set automatically. The `abort-controller` polyfill is dropped since its signal type predates the current AbortSignal and undici would not honor it. `node-fetch` stays installed because `scripts/generate-release-log.mjs`, `scripts/reset-project.mjs`, and `scripts/update-google-fonts.js` still import it (follow-up material). Fixture apps will be migrated separately. |
||
|
|
b145ad6f3f |
Fix initial Turbopack HMR update handling (#98385)
### What? Prevent a real client HMR update from being discarded when it arrives as the first subscription emission. ### Why? Capturing the chunk baseline and installing the update subscription are separate asynchronous operations. A change between them turns the first emission into a real update, but the client handler previously assumed that emission was always initialization-only and dropped it after the server had advanced its version state. ### How? Inspect the first subscription result and preserve the existing issues-only initialization behavior while forwarding partial or restart updates through the normal issue and browser-update paths. This is the narrower option because it does not change handling of the usual initial issues payload. ### Verification - `pnpm --filter=next build` - `HEADLESS=true pnpm test-dev-turbo test/development/app-dir/hmr-dynamic-component/hmr-dynamic-component.test.ts` (including a locally controlled baseline-to-subscription race) - Prettier and ESLint on the changed source <!-- NEXT_JS_LLM --> <!-- fleet 3820fb8a-c662-4c74-8316-e2d7cd42bfbd --> --------- Co-authored-by: vercel-fleet-prod[bot] <318278635+vercel-fleet-prod[bot]@users.noreply.github.com> Co-authored-by: Tobias Koppers <1365881+sokra@users.noreply.github.com> |
||
|
|
ec107c16dd |
lazy server hmr (#96566)
## Summary Make Turbopack server HMR demand-driven. Server updates are now compiled and applied when the next relevant request writes an endpoint, instead of eagerly evaluating changed server modules after every file change. This replaces the aggregate server HMR subscription with an on-demand update API, while preserving incremental updates and falling back to full cache eviction when a restart is required. Test Plan: added an e2e test <!-- NEXT_JS_LLM --> --------- Co-authored-by: vercel-fleet-prod[bot] <318278635+vercel-fleet-prod[bot]@users.noreply.github.com> Co-authored-by: Will Binns-Smith <755844+wbinnssmith@users.noreply.github.com> |
||
|
|
8330e4c4cd |
test: Improve test cache handler implementations (#98098)
- Some of them were not forwarding `getExpiration` or `softTags` to `defaultCacheHandler` - use-cache-cross-deployment was ignoring softTags and expiration. (This patch was written by Sol) --------- Co-authored-by: Hendrik Liebau <mail@hendrik-liebau.de> |
||
|
|
1f52cb42ad |
Turbopack: shorten CSS module class names (#97944)
The class names were unnecessarily long. Use the same as the lightningcss default: `[hash]_[local]` which is `<hash of the full file path>_<original class name or identifier>` Keep the previous longer mechanism to aid in debugging in dev Co-authored-by: vercel-fleet-prod[bot] <318278635+vercel-fleet-prod[bot]@users.noreply.github.com> |
||
|
|
bc218dbb85 |
test: preserve server cache after compile error (#97724)
### What? Adds a focused Turbopack development regression test covering server HMR recovery from a compile error. ### Why? A server compile error must not force a full server module reevaluation when HMR recovers. Clearing the server module cache would discard stable state held by unchanged dependencies and undermine server Fast Refresh module preservation. ### How? The test reuses the existing module-preservation fixture and its unchanged server dependency evaluation timestamp. It introduces a deterministic syntax error, verifies the Turbopack build-error redbox, repairs the page, waits for the repaired server render, and then confirms the dependency still exposes its original timestamp. The patched fixture is restored in a `finally` block so failures cannot leak state into later tests. ### Testing - `pnpm test-dev-turbo test/development/app-dir/server-hmr/server-hmr.test.ts` - 17 tests passed <!-- NEXT_JS_LLM --> Co-authored-by: vercel-fleet-prod[bot] <318278635+vercel-fleet-prod[bot]@users.noreply.github.com> Co-authored-by: Will Binns-Smith <755844+wbinnssmith@users.noreply.github.com> |
||
|
|
27a8eb0999 |
[devtools] Fix indicator dragging on touch screens (#97723)
### What? - Restore `touch-action: none` on the draggable devtools indicator. - Add integration coverage for the draggable computed touch action. ### Why? PR #86816 made devtools content selectable, but also removed `touch-action: none` with the selection styles. On touch screens, that allows the browser to claim the gesture for viewport panning and suppress the pointer event stream, causing the indicator to stop moving. ### How? Keep the drag-lifecycle selection handling introduced by #86816 and restore only `touch-action: none` before the gesture begins. Fixes #97668 ### Testing - `pnpm build --filter next` - Targeted Prettier and ESLint checks - `NEXT_SKIP_ISOLATE=1 pnpm test-dev test/development/app-dir/devtools-position/default-position.test.ts` |
||
|
|
d7aa66c345 |
Remove generated error codes (#97687)
### Why? Should come up with better solution that does not block PRs with git conflict x-ref: https://vercel.slack.com/archives/C02CDC2ALJH/p1785263902728189?thread_ts=1785263687.502649&cid=C02CDC2ALJH ### How? - Delete `errors.json`, the error-code SWC plugin, generated WASM, merge driver, and validation/build tooling. - Stop attaching error codes to server-rendering digests, redboxes, and telemetry; native `Error.code` and `Error.name` remain available where applicable. - Remove the development-overlay error feedback UI, middleware, and telemetry event that depended on stable codes. - Update fixtures, snapshots, and guidance for code-free errors and numeric-only digests. <!-- NEXT_JS_LLM --> |
||
|
|
3cdb56c251 |
[PPF] unstable_prefetch() (#97622)
Implements `unstable_prefetch()`, which is intended for use in
`partialPrefetching`. `await unstable_prefetch()` excludes content from
the app shell -- it will only be available when using `prefetch={true}`
(speculative prefetch) or during navigations.
As a rule of thumb, `unstable_prefetch()` resolves whenever static
`params` would:
- in a static prerender
- but NOT the app shell extracted from it, which is param-less
- in a runtime prefetch (`prefetch={true}`)
- but NOT a runtime app shell, which is param-less
Note that `prefetch()` is URL data, so using it in an App Shell without
Suspense will trigger an instant insight.
### Implementation notes
`prefetch()` is treated like URL data, so it resolves in the
`PrefetchStatic/PrefetchRuntime` stages added in #96908. The
implementation is basically analogous to `unstable_navigation()` except
using different stages. i've considered abstracting them into one
implementation, but decided against that for now, we can deduplicate
later.
Error messages about URL data have not been updated to mention it yet --
we will do that as a follow up, along with docs.
`await prefetch()` does not count as a runtime data access, meaning that
it won't affect the static prefetch hint for a route. however `await
prefetch(); await cookies()` does deopt the route, because using a
speculative runtime prefetch would reveal more content. Note that this
may cause us to unnecessarily deopt a shell to runtime even if only the
speculative part of the content would be improved by a runtime request;
this is not a new issue, but it's something we should optimize.
|
||
|
|
1be0ab80c4 |
[PPF] unstable_navigation() (#96908)
`navigation()` is a new API that allows omitting contents from runtime
shells and runtime prefetches. Conceptually, the point is to express
that something is expensive to compute, so we shouldn't do it for
requests that may not get used (shells and prefetches). Notably, this
means that it's fine to include it in a static prerender -- it'll be
computed once and used for many requests, so it doesn't make sense to
exclude it.
## Implementation
The split in behavior across static and runtime prerenders is a
departure from how most of our APIs behave -- usually, if something
resolves statically, then it also resolves in "more complete" prerender.
Departing from this leads to some implementation complexity.
We include three new stages, used by two facets of the implementation:
```diff
export enum RenderStage {
Before = 1,
//
ShellStatic = 10,
+ PrefetchStatic = 11, <------- params, prefetch() [static prerenders]
+ NavigationStatic = 12 <------navigation() [static prerenders]
Static = 13, <--------------- finish accumulators [static prerenders]
//
ShellRuntime = 20,
Runtime = 21, <-------------- params, prefetch() [runtime prerenders]
+ NavigationRuntime = 22, <---- navigation() [runtime prerenders]
//
Dynamic = 30,
Abandoned = 40,
}
```
### NavigationRuntime
In runtime prerenders (or dev renders that simulate them),
`navigation()` resolves in `NavigationRuntime`
We only reach this stage in 1. the embedded runtime prerender produced
for Cached Navigations and 2. during dev/prod full staged renders --
runtime shells end in `ShellRuntime`, and runtime prefetches end in
`Runtime`.
Notably, this means that content gated behind `navigation()` is included
in the embedded runtime prefetch stream.
### PrefetchStatic & NavigationStatic
This is a helper stage added before `Static`. Static prefetches still
use the `Static` stage for their output. This new stage exists so that
we can resolve static `params` (and `prefetch()` when we implement it)
which the stage is named after) separately from `navigation()`, which
resolves in `NavigationStatic`, after which the prerender ends in
`Static`. This separation is important, because during static prerenders
we track whether or not runtime APIs are used (see
`trackRuntimeDataAccessed`) to determine if a runtime shell (or runtime
prefetch) might give us more content than the static ones. However, a
runtime shell/prefethc **would not resolve navigation()**, so `await
navigation(); await cookies()` would not reveal more content, and thus
shouldn't count as a usage that prevents static optimization.
We achieve this by checking the stage inside
`trackRuntimeDataAccessedImpl` and not tracking anything if we reached
the `NavigationStatic` stage.
### Behavior of shells and validation
As noted before, `navigation()` has an incompatible resolution order
between static and runtime prerenders. In #97040, we did some groundwork
to deal with this in validation.
Static prerenders resolve `navigation()`, which means that static shells
include content gated behind navigation(). This means that Static Shell
Validation allows them.
On the other hand, App shells **do not** resolve `navigation()`. This
leads to an inconsistency for Instant Validation -- a `await
navigation()` might be fine if a page is prefetched statically, but
would become blocking as soon as the page starts using runtime data and
switches to a runtime shell. To avoid this pitfall, we pessimistically
assume that any `navigation()` _might_ be part of a runtime
shell/prefetch, so any `navigation()` unguarded by Suspense will error
in IV.
In practice, this is handled analogously to static params: we do a dev
render with `needsAppShell: true`, which makes `navigation()` resolve in
`NavigationRuntime`, and then we use the `ShellRuntime` stage when
validating, which means that `navigation()` will be a hole. Note that
the discriminated error message logic currently only retries errors
using the `Runtime` stage, which won't have `navigation()` resolved
either, so it will be incorrectly reported as dynamic data. This will be
improved in a follow up.
|
||
|
|
55e7e1903a |
[test] Cover the prerender worker-thread backend with an addon we control (#97543)
`experimental.workerThreads` decides whether static generation runs in real worker threads or forked child processes, and a native addon declared with `NODE_MODULE` can only be loaded once per process. That is why the flag defaults to false (#9199) and why the static export worker was fixed to respect it rather than hardcoding threads on (#25063). Nothing tested it: the only suite that tried, `firebase-grpc`, had its assertion skipped since 2019, and modern `firebase` ships no native module at all, so its remaining test asserted only that a build succeeds. This adds a `single-context-addon` fixture, deliberately declared with `NODE_MODULE`, and a production suite asserting both directions: the build succeeds by default and fails with "Module did not self-register" once worker threads are enabled. The fixture loads the addon from `next.config.js` as well as from the page, because that failure only happens on a second `dlopen` within one process, so an addon loaded only inside the worker would register there and the build would pass. A third case documents a bug rather than intended behaviour. `next build` runs Turbopack in a worker thread and that worker re-evaluates `next.config.js`, so requiring a non-context-aware addon from the config breaks the build even with `experimental.workerThreads` off. Webpack and rspack are unaffected, since their build workers are forked child processes. Isolated with an unguarded `require` and default flags, Turbopack exits 1 with "Module did not self-register" where webpack exits 0. It is the same class of failure #9199 and #25063 fixed, in a worker those PRs did not touch. The assertion is branched on the bundler and carries a note to drop the branch once Turbopack stops evaluating the config on a worker thread. The `isMainThread` guard in the fixture's config keeps the first two cases pointed at the static generation worker instead. A development suite covers the other direction, asserting that evaluating a route does not put such an addon on one of the threads `next dev` uses regardless of the flag, so it would catch a change that moved route evaluation onto the dev validation pool. The expectation is the same with and without Cache Components; both were checked by logging the thread id from the page's module scope, and the route is evaluated on the main thread either way. `firebase-grpc` is removed, since it covered the same flag with a skipped assertion and a vacuous one. |
||
|
|
da4888c8df |
test: better isolate concurrent-install suite (#97546)
these were failing with 50% probability since today, for some reason: https://app.datadoghq.com/ci/test/runs?query=test_level%3Asuite%20%40test.service%3Anextjs%20%40test.type%3Aturbopack%20%40test.suite%3Aconcurrent-install&agg_m=count&agg_m_source=base&agg_t=count&fromUser=false&start=1786528015972&end=1787132815972&paused=false |
||
|
|
5817bd1def | Anchor the async local storage instances to global symbols (#97255) | ||
|
|
c18acf5cef |
test: deflake use-cache-size-zero warm reload (#97421)
## Summary Keep the first warm reload assertion focused on browser-visible stale-while-revalidate behavior, then poll the route with independent HTTP requests until a later response observes the fresh value. After convergence, perform one final browser reload to confirm the user-visible path also receives an updated value. This avoids fixed delays, cache debug logging, and repeated browser navigations that can cancel or outpace background regeneration. The convergence poll uses the standard 3-second `retry()` window and returns as soon as freshness is observed; the browser only reloads again after that condition is satisfied. ## Verification - `__NEXT_EXPERIMENTAL_STRICT_ROUTE_TYPES=true NEXT_TEST_CI=true HEADLESS=true pnpm test-dev-webpack test/development/app-dir/use-cache-size-zero/use-cache-size-zero.test.ts` - `__NEXT_EXPERIMENTAL_STRICT_ROUTE_TYPES=true NEXT_TEST_CI=true HEADLESS=true pnpm test-dev-turbo test/development/app-dir/use-cache-size-zero/use-cache-size-zero.test.ts` <!-- NEXT_JS_LLM --> |
||
|
|
6b9001934d |
Remove server route matcher stack (#94157)
### What? Remove the legacy server route matcher managers, route matcher providers, and server route matcher classes. Move route definition and params metadata into fsChecker output and request metadata instead. ### Why? The filesystem checker already owns the normalized route lookup path in production. Keeping a separate matcher manager stack duplicates route inventory, adds reload plumbing, and makes dev and production behavior harder to keep aligned. ### How? - Build pages and app route definitions in fsChecker from production manifests and from the dev route inventory. - Thread matched route definitions and params through resolve-routes and router-server request metadata into base-server and next-server. - Remove reloadMatchers propagation and delete the legacy matcher manager, provider, and matcher implementations and tests. - Preserve dev ensurePage behavior, including legacy custom-server render methods, and Pages API loading by using matched route definitions while loading compiled dev modules where needed. ### Verification - `pnpm test-start-webpack test/e2e/custom-routes/custom-routes.test.ts` - `pnpm test-start-turbo test/e2e/custom-routes/custom-routes.test.ts` - `pnpm test-dev-webpack test/e2e/custom-routes/custom-routes.test.ts` - `pnpm test-dev-turbo test/e2e/custom-routes/custom-routes.test.ts` - `pnpm test-dev-webpack test/e2e/on-request-error/dynamic-routes/dynamic-routes.test.ts` - `pnpm test-dev-turbo test/e2e/on-request-error/dynamic-routes/dynamic-routes.test.ts` - `pnpm test-start-webpack test/e2e/app-dir/app-routes/app-custom-routes.test.ts` - `pnpm test-start-webpack test/e2e/app-dir/edge-route-rewrite/edge-route-rewrite.test.ts` - `pnpm test-start-webpack test/e2e/middleware-rewrites/test/index.test.ts` - `pnpm test-start-webpack test/e2e/middleware-matcher/index.test.ts test/e2e/middleware-custom-matchers-basepath/test/index.test.ts test/e2e/app-dir/rewrites-redirects/rewrites-redirects.test.ts` - `pnpm test-start-webpack test/e2e/api-catch-all/api-catch-all.test.ts test/e2e/basepath-root-catch-all/basepath-root-catch-all.test.ts test/production/root-catchall-cache/root-catchall-cache.test.ts` - `pnpm test-start-webpack test/e2e/i18n-beforefiles-rewrite/i18n-beforefiles-rewrite.test.ts` - `pnpm test-start-webpack test/e2e/basepath/redirect-and-rewrite.test.ts` - `pnpm test-dev-webpack test/e2e/custom-server/custom-server.test.ts` - `pnpm test-start-webpack test/e2e/custom-server/custom-server.test.ts` - `pnpm --filter=next types` - `pnpm --filter=next build` - `git diff --check` <!-- NEXT_JS_LLM_PR --> |
||
|
|
ae0e0a5006 |
Turbopack: remove stale manifests for deleted routes (#97333)
## What? - remove manifest fragments when Turbopack's development entrypoint cleanup removes a deleted page - add an end-to-end regression for replacing concrete App Router pages with an optional catch-all without restarting the dev server ## Why? Turbopack already removes deleted entry keys from its asset mapper, subscriptions, issues, and client state. It did not remove the corresponding records from `TurbopackManifestLoader`, so the next manifest write continued to serialize deleted App Router routes. When a deleted concrete route is replaced by an optional catch-all, those stale records conflict with the new route at the same specificity. The dev server reports the route conflict and responds with 404 until it is restarted. This is independently consistent with the cleanup identified in #97194. This draft adds end-to-end coverage that fails on current canary by observing both the 404 and stale manifest records, then passes with the lifecycle fix. ## How? Pass the existing manifest loader to `handleEntrypointsDevCleanup` and call its existing `delete(key)` method in the same branch that deletes the stale asset mapping. This keeps all per-entry manifest maps synchronized with the authoritative current entrypoint set without special-casing any route shape. The regression test: 1. compiles two concrete localized routes 2. deletes both pages and adds an optional catch-all 3. waits for three affected paths to return the new catch-all content 4. verifies the development app-paths manifest contains the catch-all and no deleted entries ## Verification - reproduced the report on `16.3.1-canary.10`: all four reported paths returned 404 after the live route replacement and returned 200 after a restart - confirmed the regression fails on current canary before the source change with the same-specificity route error and a 404 response - `pnpm test-dev-turbo test/development/app-dir/hmr-deleted-page/hmr-deleted-page.test.ts` - `pnpm test-dev-webpack test/development/app-dir/hmr-deleted-page/hmr-deleted-page.test.ts` - `pnpm test-dev-rspack test/development/app-dir/hmr-deleted-page/hmr-deleted-page.test.ts` - `CI=1 pnpm build-all` - `pnpm --filter=next build` - formatting, ESLint, staged-diff, and signed-commit checks Fixes #97035 |
||
|
|
44103a8702 |
Trace route module preparation (#97295)
## Summary - trace the full route-module preparation boundary as `RouteModule.prepare` - record manifest loading as one aggregate `RouteModule.loadManifests` child span - keep route preparation visible by default while exposing manifest detail only with verbose tracing - cover normal and direct-entrypoint server lifecycles without relying on test ordering ## Why Route preparation is the stable boundary between loading a route module and executing it. The parent span shows the total setup cost; the aggregate manifest child explains manifest work without creating per-file spans or exposing paths. ## Verification - `pnpm build-all` - `pnpm --filter=next build` - `pnpm --filter=next types` - `pnpm exec jest packages/next/src/server/lib/trace/tracer.test.ts --runInBand` - focused OpenTelemetry E2E in development and production with Turbopack and Webpack - Request Insights route-preparation E2E with Turbopack and Webpack - Prettier, ESLint, and `git diff --check` ## Stack Depends on **Trace route module loading**. |
||
|
|
529ddc3c8d |
[test] Unflake two cache-components-dev-streaming assertions (#97246)
The streaming assertion in `should stream suspense boundaries while filling caches in the background` polled a bare `<p>` through `retry()` with its 3000ms default. Because `retry` gives up as soon as `waited + interval > duration`, the effective budget is about 2.6s, and the fixture's cache fill alone takes 2000ms, so the assertion had roughly 600ms of headroom. That budget also had to absorb one browser round trip per attempt, because a selector that matches the fallback as well as the content cannot wait for anything: `waitForSelector` returns the fallback immediately, so the waiting had to happen in the test process at a 500ms granularity. This change gives the two paragraphs the ids `#cached-fallback` and `#cached`, in line with every other route in this fixture, which is what lets the wait move into the browser. Playwright now resolves the moment the content is revealed, in a single round trip, and #95466 had already moved the rest of the suite to that pattern. The two assertions that check what the shell itself delivers still need the original "whichever element arrives first" probe, and they now express it as the selector list `#cached, #cached-fallback` instead of relying on `p` to match both. The budget for the reveal is 10s rather than the 5s that `elementByCss` narrows the harness default to, because the wait has to cover more than the fill. In the linked failure the content bytes had arrived one second after the shell committed, and the reveal was still at least 2.4s away, since the browser was busy evaluating the dev bundle. A shorter fill would not help with that, as it does not shorten the part of the wait that CI actually spends. The convergence assertion in `serves a short-expire cache warm on reload and converges to a fresh value` ran out of the same budget for a different reason: every attempt performed a full dev page reload on top of the 1.5s regeneration, and measured runs needed 1.8s to 3.4s. It now reads the value over HTTP with `next.render$`, the way #97187 does, which observes the same server-side cache state without downloading and evaluating the dev bundle. Under the same contention those reads converge in 1.1s to 1.9s, and they stay there when the load is quadrupled, because only the 1.5s regeneration gates the loop. The default budget therefore covers it, and the test drops from 15.5s to 3.6s. Both fixes were verified by raising the fill to 3.2s to emulate the CI-side delay: the previous assertions then fail deterministically with the CI signature, and the new ones pass. [Flakiness metrics](https://app.datadoghq.com/ci/test/runs?query=test_level%3Atest%20%40git.repository.id%3A%22github.com%2Fvercel%2Fnext.js%22%20%40test.name%3A%22cache-components-dev-streaming%20should%20stream%20suspense%20boundaries%20while%20filling%20caches%20in%20the%20background%22%20%40test.type%3A%22nextjs%22%20%40test.status%3A%22fail%22&agg_m=count&agg_m_source=base&agg_t=count&citest_explorer_sort=timestamp%2Casc&cols=%40test.status%2Ctimestamp%2C%40test.suite%2C%40test.name%2C%40duration%2C%40test.service%2C%40git.branch¤tTab=overview&eventStack=&fromUser=true&index=citest&start=1783948256248&end=1786540256248&paused=false) |
||
|
|
04e9933b33 |
[turbopack] Fix HMR for dynamic imports evaluated from layouts (#97213)
Tbh, I don't really understand this code path but this appears to fix the bug. This includes `referenced_output_assets` in `async_chunks`. The bug report went away when `experimental.turbopackServerFastRefresh` was disabled. The agent's explanation (more for entertainment): <img width="967" height="290" alt="Screenshot 2026-08-11 at 5 07 46 pm" src="https://github.com/user-attachments/assets/e57425be-e4e9-44b8-a6eb-8e93d04b37f0" /> Closes https://github.com/vercel/next.js/issues/97206 |
||
|
|
b93ed4fa0f |
[test] Unflake use-cache-custom-handler-dev tests (#97187)
|
||
|
|
f70564f742 |
Keep the dev validation worker alive across HMR updates (#96988)
Cache Components dev validation reported stack frames that pointed at
build output whenever a module had been updated while the dev server
ran. This affected both the static shell validation and the
instant-navigation validation, since both run on the same worker. The
overlay showed a raw `file:` URL and the terminal named the chunk rather
than the page, and because the frame never resolved to a source position
there was no code frame either, so nothing indicated which line caused
the error.
Turbopack's server HMR evaluates an updated module as a script of its
own, named `<chunk>?<module id>` and carrying its source map inline
rather than on disk, so only the isolate that ran that `eval` can
resolve a frame in it. The validation worker never ran it, and the map
beside the chunk describes the chunk's lines, not the running module's,
so nothing the worker could reach described the frame. React then wrote
the frame in its form for scripts without a source map, which encodes an
already-encoded URL a second time, leaving a frame no reader reverses.
The worker now mirrors what the dev server does to its own module state
rather than being dropped whenever that state changes. The dev server
reports each applied update, the manifest cache entries it cleared, and
the paths it evicted, and the worker replays them in the same order, so
its module state is the dev server's module state by construction. That
leaves each updated module's inline source map in the worker's own
Node.js cache, which is what makes the frame resolvable there.
The worker needs no coordination around a validation in flight. It runs
one call at a time, in the order the calls were made, so an update is
replayed before any validation requested after it, and never in the
middle of one. The dev server does not hold its own updates back for a
validation running in process either. Where it gives up and re-evaluates
every module from disk the worker is dropped, so that case keeps the
behaviour it had.
Not dropping the worker helps beyond the frames. Dropping it meant the
next validation had to spawn a worker thread and run `loadComponents`
again before it could start, and it paid that on every edit, which
delayed the insight at exactly the moment the user is waiting for it.
The case in the test suite that covers this went from around 870ms to
around 240ms.
The simpler fix was to revive the transported errors on the main thread
and print them there, where the scripts already are. It works, and it is
why this PR also touches the benchmark: the fixture produced no
validation errors, so nothing in the benchmark reached the error
reporting at all, and the cost of moving it was invisible. With insights
generated, the cost showed plainly. Printing an error costs around 218ms
the first time a source map is read and about a millisecond after that,
and moving it to the main thread cut the worker's p95 advantage on the
heaviest route from around 15ms to between 2ms and 5ms. Mirroring the
updates keeps the printing on the worker and leaves that advantage
intact.
The three commits are worth reading in order. The first adds the test
with the broken output snapshotted, so its snapshots deliberately record
what a user saw, a frame naming the chunk with no code frame beneath it.
The second is the benchmark change above. The third is the fix, and its
diff turns those snapshots into resolved frames, adds cases that edit
the same module twice, edit a module the page imports, and validate a
route that another route's update did not touch, and rewrites the
suite's header comment, which described the mechanism this replaces.
Verified on both bundlers, since the worker is gated on Turbopack and
Webpack validates in process, along with
`instant-validation-scheduling`,
`instant-validation/{server-errors,parallel-slots}`,
`instant-validation-causes`, `instant-validation-level-default` and
`hmr-rsc-cancellation`. Run with `BENCH_DEV_VALIDATION_INSIGHTS=1`, the
benchmark shows no steady-state regression: the worker column matches
canary at 106ms sprite p95 against 110ms and 109ms, and keeps its margin
over in-process.
Two things are deliberately left out. The benchmark still cannot measure
the edit case, because it never edits, so the timing above comes from a
test's wall clock rather than a purpose-built measurement. And
`use-cache-probe-pool` subscribes to the same invalidation and tears
down the same way, which is the obvious follow-up if this holds up.
One known gap remains. A worker dropped by its own failure, rather than
by the dev server giving up, cannot obtain the scripts the dev server
evaluated from earlier updates, so frames naming them stay unresolved
until those modules change again. The validation itself is unaffected,
because the worker loads the current code from disk.
|
||
|
|
5dc3ae1fe7 |
Fix Nav Inspector request loop on repeat captures (#97050)
## Summary Repro (from #96692): enable the Nav Inspector, click a `<Link prefetch={true}>`, close the inspector, navigate home, re-enable it, and click the same link. The app hung in a pending "Compiling..." state while firing prefetch requests in an infinite loop (~30/sec). The Instant Navigation Testing lock restricted navigation reads to entries created within the current lock scope (`ownedEntries`), enforced as a post-hoc filter after the cache lookup. But the segment cache resolves lookups by most-specific-match, so a previous scope's runtime-prefetch entries at concrete param keypaths kept winning the lookup, the filter kept rejecting them, and the locked prefetch created its replacement at a more generic keypath that could never win — every scheduler pass discarded and refetched forever. Rather than patch the filter, this replaces the `ownedEntries` mechanism: - Each lock scope owns a private segment `CacheMap` that starts empty and is discarded at release, so a captured navigation structurally observes only data fetched under the lock — cross-scope shadowing becomes impossible by construction. - The map is an explicit capability bound when work is created: a prefetch task captures its map when scheduled (`PrefetchTask.segmentCacheMap` — the single place that consults lock state), a locked navigation inherits its driving task's map, and everything else — unlocked navigations, hydration, refreshes, traversals, server actions and patches — binds to the shared map. Reads and response writes receive the map explicitly, so a request that straddles a scope boundary still writes into the map its entries live in. In production builds without the testing API this compiles down to the previous single-map behavior. Includes the failing test from #96692 (thanks @samselikoff), hardened to use the retry-based panel-reopen helper the sibling tests use. ## Verification - `pnpm test-dev-turbo test/development/app-dir/instant-navs-devtools/instant-navs-devtools.test.ts` (32/32, includes the new regression test) - `pnpm test-dev-webpack test/development/app-dir/instant-navs-devtools/instant-navs-devtools.test.ts -t "repeat clicks"` - `pnpm test-dev-turbo test/e2e/app-dir/instant-navigation-testing-api/instant-navigation-testing-api.test.ts` - `pnpm test-start-turbo test/e2e/app-dir/instant-navigation-testing-api/instant-navigation-testing-api.test.ts` - `pnpm test-start-turbo test/e2e/app-dir/segment-cache/basic/segment-cache-basic.test.ts` (production paths unregressed) <!-- NEXT_JS_LLM --> --------- Co-authored-by: Sam Selikoff <sam.selikoff@gmail.com> |
||
|
|
ec8b5435bf |
Trace development route compilation (#96454)
## Summary - Trace development route compilation through `DevBundlerService.ensurePage` after route preparation selects a route. - Present the existing development bundler work as `compile route` in Request Insights so cold compilation can be distinguished from route matching. - Keep the span internal by default without expanding the default public OpenTelemetry allowlist. - Preserve compilation error propagation and the existing route-preparation parentage. Warm requests can still record the span when `ensurePage` runs, but the work is normally negligible after the route is compiled. ## Verification - `HEADLESS=true pnpm test-dev-turbo test/development/app-dir/request-insights-route-preparation/request-insights-route-preparation.test.ts` - `HEADLESS=true pnpm test-dev-webpack test/development/app-dir/request-insights-route-preparation/request-insights-route-preparation.test.ts` - Both bundlers passed 2/2 tests at the final stack head. - `pnpm --filter=next build` |
||
|
|
2966db4458 |
Trace development route preparation (#96453)
## Summary This PR introduces the first bounded route-preparation phases used by Request Insights. It instruments existing matcher and bundler boundaries without changing their behavior or making the spans default-public OpenTelemetry data. - trace development route matching and route preparation while a route is ensured - keep production matcher reload as a separate sibling phase so reload work is not charged to matching - surface the internal phases as `match route`, `prepare route`, and `reload route matchers` in Request Insights - cover cold and warm page, App Route/API, and dynamic-route behavior in both development bundlers - preserve the existing matcher results, errors, and public OTel allowlist ## Verification - `pnpm test-dev-turbo test/development/app-dir/request-insights-route-preparation/request-insights-route-preparation.test.ts` - `pnpm test-dev-webpack test/development/app-dir/request-insights-route-preparation/request-insights-route-preparation.test.ts` - `pnpm --filter=next types` |
||
|
|
d792fcf5e7 |
Fix use cache over- and under-invalidation in dev (#96235)
In dev, `"use cache"` entries are keyed by an HMR refresh hash so that edits invalidate cached data. This hash was managed wrong in both directions: - webpack refreshed the hash at moments when no code changed: on dev server startup, and when a page file was added or removed. Each refresh threw away all cached data for nothing. - The hash never distinguished one dev server run from another. That matters with a custom cache handler that persists entries: a restarted server would serve data cached by the previous run, whose code may have changed while the server was down. To fix this: - Adding or removing a page no longer refreshes webpack's hash. Edits and env file changes still do. - The hash now includes a random per-run value. This fixes the `cache-components-dev-warmup` flakes. ## How to reproduce Here's the first problem in action, on webpack. Render a cached `Math.random()` on a page, then create an unrelated page file: ``` value: 0.199426 value: 0.199426 $ echo '...' > app/unrelated/page.tsx # never visited value: 0.990973 # cached entry gone a second later ``` Adding the page threw the cached data away. Starting the dev server throws it away too, once, shortly after boot. That one is a race: normally the cache is still empty when it fires, so nobody notices — but if the first page request gets in before it, everything that request cached is thrown away and the next reload is silently cold. That race is the cause of the `dev-warmup` flakes on loaded machines, and it's how I found this. The hash refresh is specific to webpack, so Turbopack doesn't lose cached data this way. On Turbopack, these events instead clear the server's module cache. Whether the next render picks up a fresh module instance is a race, so module-scope state can reset. Turbopack also has a separate bug in this area — it advances its hash once per change-subscription emission, and an added or removed page can produce an emission without any edit — which #96248, stacked on this PR, fixes by deriving the hash from compiled output. ## Underlying cause Next computes a "client router filter" (a bloom filter over the app's routes, used by the client-side router), and a filter change is treated like an env file change. An env change means cached data might be stale, so the reaction is strong: the browser is told to re-fetch, and on webpack the hash is refreshed, which discards every cached entry. But the filter is derived from the routes, so it "changes" whenever you add or remove a page — and once right after startup, because the first computation has nothing to compare against. Neither makes cached data stale. The second problem is the opposite: dev server runs share cache keys. The hash starts from the same value on every run (unset on webpack, `"0"` on Turbopack), so a restarted server generates the same keys as the old one. With the default in-memory handler that's moot, but a custom cache handler that persists entries will serve entries written by the previous run. webpack can also hit this after edits: its hash is derived from compiled output, so re-applying an edit that a previous run also made lands on that run's keys. Judging by the code (?), dev cache reuse was only ever meant to last a single run: every dev cache mechanism is in-memory, and #75474 takes it for granted that restarting the dev server clears cached content. So I treat cross-run reuse as a bug. Not sure I got this constraint right — in particular, #75474 deliberately made reverting an edit reuse previously cached data, and this PR keeps that working within a run but stops it from working across runs. ## Fix Two changes: - A filter change is no longer treated as an env change. It still updates the compiled-in filter value (the part that's actually needed), but it no longer refreshes the hash, and on Turbopack it no longer wipes the server's module cache. Real env file changes still trigger the full reaction. - The hash now starts from a random per-run value on both bundlers. So it exists before the first request and never matches another run's keys. The per-run value is deliberately conservative: it also prevents reuse across restarts where *nothing* changed. Allowing exactly that reuse safely needs keys derived from the cached function's implementation (the existing TODO in `use-cache-wrapper.ts`), which would replace the per-run value. ## Test plan The first commit adds five tests (`test/development/app-dir/cache-components-spurious-cache-invalidation`). They run on both bundlers, except the module-state test, which is Turbopack-only: on webpack, adding a page recompiles the server bundle, which replaces the module instances regardless. So Turbopack runs five and webpack four. Before the fix, these fail: ``` webpack: ✕ discards use cache entries when a previous run made the same edit ✕ discards use cache entries across dev server restarts (most runs — see below) Turbopack: ✕ keeps module state when an unrelated page is added ✕ discards use cache entries across dev server restarts ``` The webpack restart test does not fail every run. The start-time refresh replaces the unset hash with a content-derived one at an unpredictable moment. When one server run caches before its refresh and the other reads after its own, the keys don't collide, so there is no cross-run reuse for the test to catch that run. The webpack side of the over-invalidation fix is covered by the refetch count below rather than by a cached-value test: the equivalent test on Turbopack depends on emission timing, so it lands with #96248, which removes that dependence. The same commit also tightens the refetch count in the test from #96250 below: since a page add is no longer an env change, adding a page refetches an open tab once instead of twice, on both bundlers. Before the fix, both bundlers fail that expectation (`Expected: 1, Received: 2`). To reproduce the flake this PR fixes, run the dev-warmup suites under CPU load (`for i in $(seq 1 12); do yes > /dev/null & done`). Before the fix, 6–8 of the 11 tests fail per run on webpack (`Prerender` flips to `Server` on warm reloads). After the fix, all 11 pass. Turbopack passes before and after. Also still green: `typed-env` (env definitions were previously only written at boot via the false positive this removes; now they're written on the initial scan explicitly), `env-config`, `use-cache-custom-handler`, and `pages-to-app-routing` (it exercises the router filter update path). |
||
|
|
f58c669ab2 |
Fix which pages the dev server announces, and when (#96250)
The dev server tells open tabs when a page is added or removed, so a tab showing a 404 picks up a page you just created, and a tab showing a page you just deleted falls back to the 404. Both bundlers computed the list of changed pages wrong, in opposite directions. **Turbopack announced pages that hadn't changed.** To find what changed, it compared the new route list against the entry maps in `currentEntrypoints`. Those maps key App Router entries by page name (`/blog/page`), but the route list is keyed by route (`/blog`), so no App Router route ever matched: every update announced every existing app route as added and every page name as removed, and starting the server announced every route once. Each announcement makes every connected tab refetch. Fixed by comparing the new route list to the previous route list, which uses the same naming. Starting the server now announces nothing on either bundler, since the first route list is not a change. **webpack didn't announce pages that had changed.** It only announced when `prevSortedRoutes.every((val, idx) => val === sortedRoutes[idx])` was false. That is a prefix comparison: when a new route sorts after all existing ones, the old list is a prefix of the new one, and nothing is announced. In App Router this mostly went unnoticed, because adding a page was also (wrongly) treated as an env change, which refreshed tabs anyway; the next PR in this stack removes that. In Pages Router nothing hid it: the route manifest was never refetched, so client-side navigation to the new page kept returning 404 until a manual reload. Fixed by also comparing the lengths. **What this PR does not fix:** on Turbopack there is a window right after a page is added where the dev server has already announced it to tabs, but can't serve it yet (the route isn't in the dev router's route table until the watcher pass in `setup-dev-bundler` runs). A tab that reacts inside that window gets the 404 again, and since each page is only announced once, it stays on the 404 until you reload it by hand. That bug predates this PR and is being fixed separately. So that the new App Router tests don't hit this window, each one edits the added page again once it's confirmed servable. The edit makes the server announce again, and this time the tab can only get the page. The trade-off is that these tests don't prove the first announcement alone updates the tab — the refetch-count test covers how often changes are announced, and its tab never sits on the added page's 404, so it can't hit the window. A TODO in the tests says to drop the extra edits once the underlying bug is fixed. The Pages Router test needs no such edit: Turbopack updates the route matchers before it announces, and a Pages Router tab reloads the document instead of refetching. ## Test plan New tests cover adds, removals, dynamic routes, route groups, and a dynamic route being shadowed by a more specific page and then unshadowed. One more counts how often an open tab refetches when a page is added: it wraps the tab's `fetch` and counts requests carrying the `next-hmr-refresh` header, which distinguishes a refetch the dev server asked for from a navigation or a prefetch. It runs on a dev server no other test shares, because the count depends on what has been compiled so far. Before the fix, adding one page makes an open tab refetch: ``` Turbopack: 12 times (every app route re-announced; grows with the number of routes) webpack: 1 time (the announcement is never sent; only the env-change refetch happens) ``` The correct count is currently 2: one refetch for the announcement, one for the env-change false alarm. The next PR removes the false alarm and tightens the expectation to 1. Before the fix, webpack also fails the Pages Router manifest test (the manifest update is sent from the same skipped branch). With the fix, both bundlers pass all ten tests. |
||
|
|
ff3a2cfaa9 |
[turbopack] Strip leading BOM before parsing CSS (#96678)
Fork PR #96379 by @lazerg, re-opened as a branch PR so the "when deployed" CI jobs can run — those require Vercel deployment secrets that GitHub does not expose to pull requests from forks, so they can never pass on the original. **The fix commits are unchanged and still authored by @lazerg.** This PR only adds tests on top. Please credit them; #96379 should be closed in favor of this one. ### What? A CSS file beginning with a UTF-8 BOM (`EF BB BF`) is mishandled by Turbopack. Lightning CSS does not skip the BOM, so it is tokenized as content and the first token is misparsed: ``` ./app/bom.css:1:2 Error: Parsing CSS source code failed Unexpected token AtKeyword("layer") ``` The user-visible symptom is broader than a failed build. Turbopack parses with `error_recovery: true`, and under that setting a leading BOM makes Lightning CSS return `Ok` with **zero rules** — so a BOM-prefixed stylesheet could silently drop all of its styles instead of erroring. dart-sass (compressed style) and PostCSS >= 8.5.24 both emit or round-trip such a BOM, so real projects hit this. Fixes #96374 ### How? Strip a leading `U+FEFF` in `parse_css_stylesheet` before handing the source to Lightning CSS, covering both `StyleSheet::parse` call sites while leaving `ParseCssResult.code` as the original bytes that code frames are rendered from. That split makes parser positions relative to the stripped copy while code frames still render the original line, so first-line positions need compensating. `source_pos_for_loc` adds the stripped character back for line 0 of BOM files. Only line 0 is affected, because the BOM contains no newline. ### Tests `test/e2e/app-dir/css-bom` — a BOM-prefixed stylesheet compiles and its rules reach the page. Verified failing without the fix with the exact error above, and passing with it, in dev-turbo, start-turbo and start-webpack. `test/development/app-dir/css-bom-code-frame` — covers the position correction. Two fixtures hold the same invalid `@media (min-width: {})` on line 1 and differ only by the leading BOM; the test asserts the BOM file's reported column is exactly one greater: | | `no-bom` | `bom` | | |---|---|---|---| | without `source_pos_for_loc` | 18 | 18 | fails | | with it | 18 | 19 | passes | Asserting the relationship rather than a literal column keeps this robust if Lightning CSS changes its absolute column convention. It is kept separate from the e2e suite because the fixtures are intentionally invalid CSS, and scoped to Turbopack in dev, where the warning reaches the CLI as the page is requested. --------- Co-authored-by: lazerg <lazerg2@gmail.com> Co-authored-by: vercel-gh-bot-3[bot] <282332853+vercel-gh-bot-3[bot]@users.noreply.github.com> |
||
|
|
39b7da2ee8 |
Turbopack: terminate failed plugin worker threads (#96592)
## What Terminate a worker-thread plugin runtime when an evaluation or IPC error marks its `WorkerOperation` non-reusable. `disallow_reuse()` already removed the worker from pool statistics and disabled the callback that returns it to the pool, but it did not stop the underlying Node.js `Worker`. The worker then stayed strongly owned by `loaderWorkers`, kept its V8 isolate/module graph alive, and waited forever for another task that could never arrive. This calls the existing worker terminator used by `wait_or_kill`, scale-down, and scale-to-zero. It removes the routed native channel and dispatches the existing JavaScript `Worker.terminate()` callback, which also deletes the map entry. ## Why this matters for v0 v0's planned worker-thread runtime turns transient loader/PostCSS errors during agent edits into a deterministic lifecycle leak. On the real v0 `/button` route, each unique Tailwind/PostCSS failure retained one complete worker runtime on the baseline. The balanced eight-process A/B panel (80 unique failures per lane) measured: | Lane | `WorkerThread` slope | Private-memory slope | Median failure | Median recovery | >5 s failures | |---|---:|---:|---:|---:|---:| | baseline | **+1.000/error** | **+49.81 MiB/error** | 108.04 ms | 329.26 ms | 3 / 80 | | candidate | **0.000/error** | **+5.12 MiB/error** | 108.15 ms | 317.65 ms | 2 / 80 | That is a **100% elimination of leaked-worker growth** and an **89.7% reduction in retained private-memory slope** (exact process-bootstrap 95% interval: 85.7%–99.5%). Every candidate process was below every baseline process; exact 4-vs-4 process-label permutation `p=0.0143` one-sided / `0.0286` two-sided. Median failure latency changed by only +0.11 ms. The independent 100-cycle-per-lane endurance confirmation showed the same causal result: | Lane | `WorkerThread` cycle 1→100 | Private memory cycle 1→100 | Private slope | Median failure | Median recovery | |---|---:|---:|---:|---:|---:| | baseline | **38→137** | **4.28→7.34 GiB** | **+27.02 MiB/error** | 104.45 ms | 331.32 ms | | candidate | **32→32** | **3.93→4.10 GiB** | **+0.98 MiB/error** | 102.94 ms | 297.24 ms | That long run eliminated all observed worker growth and reduced the fitted private-memory slope 96.4%. Every one of the 200 unique failures recovered to the complete real route, and the source hash was restored after every cycle. The benchmark used fresh `.next` state per process, five error/recovery warmups, a five-minute quiescence window, unique uncached errors, exact source restoration after every cycle, and within-process slopes. Baseline and candidate native binaries were built from the same Next.js base, with only this lifecycle change affecting candidate native code, and selected by SHA-verified `NEXT_TEST_NATIVE_DIR` paths. This is independent of #96433. That PR coordinates explicit multi-file edit transactions; this PR fixes the lifecycle of a worker that has already failed. It also does not change idle-worker scale-down policy: failed workers are absent from the idle pool, so scale-down cannot see them. ## Regression test The new development test schedules a one-second filesystem marker inside a custom loader, then throws an evaluation error under `turbopackPluginRuntimeStrategy: 'workerThreads'`: - baseline native binary: fails because the orphan worker remains alive and writes the marker; - candidate native binary: passes because the worker is actually terminated; - candidate then renders a recovered value, proving replacement/recovery works. ## Validation - `pnpm build-all` - `cargo fmt --all -- --check` - `cargo check -p turbopack-node --all-targets` - `cargo test -p turbopack-node --lib` - `cargo clippy -p turbopack-node --all-targets -- -D warnings` - focused Prettier and ESLint checks for the new test - candidate integration test pass; exact baseline integration test fails at the intended liveness assertion - GPT-5.6 Sol xhigh + Claude Opus 5 xhigh autoreview panel: zero actionable findings, patch correct at 0.96 confidence Exact optimized native SHA-256 values used by the benchmark: - baseline: `609eeeed41e1425f06136b54b7997e5b1b1add992169b17a797889dfb830dd7f` - candidate: `141211112ae47b8bd814aaab437060114e92f60a1bf6f23e1b0e153b1e4837cc` Both stripped binaries were 205,863,784 bytes. Raw per-process JSON, `/proc` samples, server logs, harness source, analysis output, and A/B plus endurance graphs are retained in the investigation archive. Co-authored-by: vercel[bot] <35613825+vercel[bot]@users.noreply.github.com> |
||
|
|
466cdfaa27 |
Turbopack server hmr: avoid complete clear on graph changes (#95546)
Currently, importing a new module causes a complete eviction (`clear()`) and re-evaluation of server chunks, both in the Turbopack runtime's module cache and Node's `require.cache`. Here's what happens today: - A new module is imported into a chunk's graph - This changes its `availability_info`, which in dev for non-entry chunks is encoded into the chunk's filepath - `VersionedContentMap` works entirely on chunk paths. When we construct instructions to transition a chunk into its new state, we fail to find the prior state and fall back to `clear()` as described above. An ideal version of this is a refactor that implements `VersionedContentMap` on a per-module basis, not a per-chunk one. This PR doesn't do that, but achieves consistent module-level updates when a chunk's availability info changes. It does this by implementing chunk lists for server entry chunks the same way the client hmr implementation does: an entry chunk's version aggregates the versions of its dependent chunks (including dynamically imported ones), keyed by merger rather than path. Entry chunks do not encode availability info in their paths, so they are insulated from missing versions. Updates are applied in Node.js through the same shared merged-update machinery in the unified hmr runtime that the client already uses, plus a new `ChunkListUpdate` branch in the server hmr client to unwrap the merged updates. Details: - The merged-update wire format (`EcmascriptMergedUpdate` etc.) moves from turbopack-browser into shared `turbopack_ecmascript::chunk_list`, along with runtime-agnostic `ChunkListVersion` and `update_chunk_list` implementations. - `turbopack-nodejs` gains a chunk content merger mirroring the browser one. `EcmascriptBuildNodeEntryChunk`'s versioned content is now a chunk-list content over its sync and async chunks. It is version-only and never emitted; the entry chunk still inlines its loader calls. - The aggregate server hmr subscription now tracks only entry chunks. Shared chunks under `server/chunks/` ride the entry's `ChunkListUpdate` as module deltas, so their content-hash paths no longer matter. - The Node hmr client applies `ChunkListUpdate` by feeding each nested merged update through the shared apply path. Modules that appear in an "added" chunk but already exist in the module cache were moved by a chunk rename and are treated as modified. - On a successful partial apply, the hot reloader clears the manifest cache for updated chunks and notifies browsers to refetch RSC, without clearing `require.cache`. ### Test Plan Adds a series of e2e tests to catch the described manual test plan in both entry and dynamic chunks. |
||
|
|
925edc3dd6 |
Classify Instant Insights as internal in Request Insights (#96147)
## Summary - Classifies Instant Insights records as internal and hides them by default. - Adds a persisted “Internal activity” toggle. - Nests internal records below their matching foreground request. - Preserves orphaned internal records exactly once. - Signals hidden internal errors without littering the main request list. - Raw endpoint and CLI data remain complete; filtering is UI-only. ## Verification ```bash pnpm build-all pnpm --filter=next types pnpm --filter=next build pnpm test-unit --runTestsByPath packages/next/src/next-devtools/dev-overlay/components/request-insights/trace-viewer.test.ts pnpm test-dev-turbo test/development/app-dir/request-insights/request-insights.test.ts pnpm test-dev-webpack test/development/app-dir/request-insights/request-insights.test.ts ``` <!-- NEXT_JS_LLM --> |
||
|
|
c294cb7539 |
Persist Request Insights display settings (#96277)
## Summary - Replaces the per-request verbose checkbox with a global Request Insights settings menu. - Persists `requestInsights.verbose` through the existing DevTools configuration path. - Restores the setting across reloads. - Does not include the unrelated global `pagehide` flush behavior. ## Verification ```bash pnpm build-all pnpm --filter=next types pnpm --filter=next build pnpm test-dev-turbo test/development/app-dir/request-insights/request-insights.test.ts pnpm test-dev-webpack test/development/app-dir/request-insights/request-insights.test.ts ``` <!-- NEXT_JS_LLM --> |
||
|
|
efcbb27450 |
Add Instant Insights pipeline spans (#95961)
## Summary - Adds a detached `Instant Insights` root span. - Surfaces `Prepare validation inputs` and `Run validation` as child phases. - Covers both the validation worker and Webpack’s in-process path. - Stores these spans separately from the foreground request. - Preserves scheduling, cancellation, and concurrent-document behavior. ## Verification ```bash pnpm build-all pnpm --filter=next types pnpm --filter=next build pnpm test-unit --runTestsByPath packages/next/src/next-devtools/dev-overlay/components/request-insights/trace-viewer.test.ts pnpm test-dev-turbo test/development/app-dir/request-insights/request-insights.test.ts pnpm test-dev-webpack test/development/app-dir/request-insights/request-insights.test.ts pnpm test-dev-turbo test/development/app-dir/instant-validation-scheduling/instant-validation-scheduling.test.ts ``` <!-- NEXT_JS_LLM --> --------- Co-authored-by: David Ilie <david@davidilie.com> |
||
|
|
3de2d1a213 |
Unify allow-runtime with Partial Prefetching (#96106)
Removes the "allow-runtime" prefetch config, and turns its behavior on implicitly wherever Partial Prefetching is enabled. The original motivation for "allow-runtime" was to give apps more control over server costs triggered by prefetches. Until a route explicitly opts in, prefetches would only be served from the CDN, not from the server. The problem, though, was it was very confusing to know when to add or remove this configuration. The incentive for many apps was to add it everywhere, with no clear signal for when to remove it. Our updated thinking is that Partial Prefetching itself already provides sufficient protection against runaway prefetching costs: per-link prefetches only happen on Link components that explicitly opt in with the prefetch prop. The optimizations landed earlier in this stack also make allow-runtime less necessary: on pages where all the content is statically renderable, prefetches are served from the static cache and no runtime request is ever issued; only a page that accesses non-static data is prefetched at runtime. The upshot of this decision is that runtime versus static becomes an internal optimization; the same content gets prefetched regardless of whether or how Next.js is able to optimize it. |
||
|
|
a619237b1c |
Use a safe clock for Request Insights bookkeeping (#96274)
Draft replacement for #96267 after restructuring the Request Insights stack. ## Summary - Request Insights bookkeeping used `Date.now()`, which Cache Components patches to detect unstable application wall-clock reads. - Framework bookkeeping could therefore be incorrectly attributed to user code. - Implicit timestamps now use `performance.timeOrigin + performance.now()`. - Explicit OpenTelemetry timestamp inputs remain unchanged. - Tests distinguish framework bookkeeping from a genuine application `Date.now()` call. ## Verification ```bash pnpm build-all pnpm --filter=next types pnpm --filter=next build pnpm test-unit --runTestsByPath packages/next/src/server/lib/trace/local-span-recorder.test.ts packages/next/src/server/lib/trace/span-store.test.ts packages/next/src/server/lib/trace/request-insights.test.ts pnpm test-dev-turbo test/development/app-dir/request-insights/request-insights.test.ts pnpm test-dev-webpack test/development/app-dir/request-insights/request-insights.test.ts ``` <!-- NEXT_JS_LLM --> |
||
|
|
31b78bbed9 |
Add Instant Insights request timing (#95958)
## Summary - record deferred Instant Insights work as a separate typed Request Insights item - keep validation spans, fetches, timing, and live updates separate from the foreground request - label Instant Insights consistently in DevTools and CLI output - stack this observability change on #95939, which moves validation off the navigation response path ## Verification - `pnpm jest --runInBand packages/next/src/server/lib/trace/request-insights.test.ts packages/next/src/server/lib/trace/local-span-recorder.test.ts packages/next/src/next-devtools/dev-overlay/components/request-insights/trace-viewer.test.ts packages/next/src/next-devtools/dev-overlay/shared.test.ts` - `NEXT_SKIP_ISOLATE=1 NEXT_TEST_PREFER_OFFLINE=1 pnpm test-dev-turbo test/development/app-dir/request-insights/request-insights.test.ts` - `NEXT_SKIP_ISOLATE=1 NEXT_TEST_PREFER_OFFLINE=1 pnpm test-dev-webpack test/development/app-dir/request-insights/request-insights.test.ts` - `pnpm --filter=next types` <!-- NEXT_JS_LLM --> --------- Co-authored-by: David Ilie <david@davidilie.com> |
||
|
|
b51ca60fd4 |
Fix dev overlay symbolication for project paths needing percent-encoding (#96221)
In a project whose absolute path contains characters that percent-encode
in URLs — e.g. a space — the dev overlay showed no code frame and raw
`file://` URLs instead of source locations for server frames.
Two encoding mismatches caused this:
- Turbopack's `trace_source` percent-decoded the source map's original
file URL before comparing it against the still-encoded project root URI,
so the containment check failed ("Original file ... outside project")
for any project path with such characters. The comparisons now stay in
the encoded domain and only the outputs are decoded back into paths.
- React synthesizes stack frame `file:` URLs by prepending the scheme to
a filesystem path, so the overlay receives URLs with e.g. raw spaces.
These aren't well-formed, and they don't match the `url.pathToFileURL`
form Node.js keys its source map cache by. The overlay middleware now
re-encodes them through WHATWG URL parsing, which tolerates such input.
## Test Plan
New tests.
Known cases that remain broken are commented out with a TODO.
These could be resolved if https://github.com/react/react/pull/37105 or
equivalent lands.
## Before
<img width="825" height="732" alt="Screenshot 2026-07-25 at 14 39 18"
src="https://github.com/user-attachments/assets/0f119e52-ec86-495a-bf5c-5339faa1d606"
/>
## After
<img width="1126" height="733" alt="Screenshot 2026-07-25 at 14 58 14"
src="https://github.com/user-attachments/assets/a263ee8a-4cfe-4358-9a8e-2e58b4b16aa5"
/>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
|
||
|
|
e1fbb1800f |
Run dev validation in process when using Webpack (#96219)
Node.js caches source maps per isolate, so the dev validation worker only has maps for the chunk files loaded on its own thread. For chunks it never loads, such as a module behind a dynamic `import()`, it reads the `.map` that Turbopack writes next to the chunk. Webpack keeps its dev source maps in the compiler instead, so the worker has nothing to read and reports those frames with the position they have in the compiled output rather than in the source. This stops using the worker when the bundler is Webpack, so validation runs in process again, where those frames resolve, and `experimental.devValidationWorker` now has no effect there. Dev performance under Webpack goes back to what it was before validation moved to a worker, which is the better trade: the worker exists to keep the event loop responsive during rapid navigation, and that is not worth losing the source location on validation errors. |
||
|
|
4bfd5c7170 |
Read chunk source maps from disk in the dev validation worker (#96218)
Node.js caches source maps per isolate, so the dev validation worker only has maps for the chunk files loaded on its own thread. It never renders server components, and the built entry reaches segment modules through lazy getters, so it loads whatever `loadComponents` pulls in and nothing else. A module the bundler splits into a chunk of its own, as Turbopack does for a dynamic `import()`, is therefore missing from that cache entirely, and the frames pointing into it are logged without a source location. In-process validation resolves them, so this is a regression from moving validation onto a worker. On the main thread the same miss is covered by asking Turbopack for the map through the `Project` handle, which cannot cross a thread boundary. The worker instead reads the `.map` that Turbopack already wrote next to the chunk, which needs no project handle and, unlike Node's cache, does not depend on the chunk having been evaluated on this thread. Lookups are restricted to `distDir` so a stack frame cannot point the reader at an arbitrary file, and both hits and misses are memoised. Installing that lookup also required moving `bundlerFindSourceMapPayload` onto a `globalThis` symbol. `patch-error-inspect` is bundled into several runtimes that each get their own copy, and the copy that registers the implementation, here the worker bundle, is not the copy that symbolicates the frame, which is the app-page bundle's. The code frame renderer in the same file is already shared this way for the same reason. Webpack keeps its dev source maps in the compiler rather than writing them next to the chunks, so there is nothing for the worker to read and its frames are unchanged. A follow-up turns the worker off for Webpack so that validation runs in process, where those frames still resolve. |
||
|
|
c1617dedc8 |
Retry the source map lookup with a plain path (#96215)
Running `test/e2e/app-dir/instant-validation/suspense-boundaries.test.ts` locally on Node.js 24 currently fails in the two "missing suspense around search params" cases: stacks that should read `app/…/page.tsx:40:18` are logged as raw `about://React/Prefetch/file:///…/%5Broot-of-the-server%5D…` URLs, so the validation errors printed to the terminal no longer point at any source. CI is green on the same commit, because it runs Node.js 20. The dev validation worker (#96153) and the switch to `file:` source maps for Turbopack (#95946) landed hours apart, and the worker's branch predated the source map change, so the two first met on canary, where the older Node.js hid the result. Symbolicating a frame that React spliced in from another environment means decoding the chunk path out of its `about://React/…` URL and asking Node.js for that chunk's source map. Decoding returns the path as written, while Node.js keys its cache by the `pathToFileURL` encoding of the same path, and from Node.js 22 on that encoding escapes the brackets in Turbopack's `[root-of-the-server]` chunk names, so the lookup misses. Node.js 20 leaves brackets alone, which is why the same code resolves there. PR #95946 anticipated the mismatch and left it as a follow-up, because on the main thread a miss is only wasteful: Turbopack hands back the chunk's map instead, which means parsing one the process had already parsed and cached. The validation worker runs on its own thread with no access to the Turbopack project, so there the miss is final. Node.js also accepts the plain path as a cache key, unambiguously for both a CommonJS absolute path and an ESM `file:` URL, so we retry with it before giving up. Making React's fake frame URLs reversible, as proposed in https://github.com/react/react/pull/37105, would let the first lookup succeed on its own and retire the retry. The test added here pins down what the worker can symbolicate at all. It caches maps per chunk file in its own isolate and never renders server components, so a statically imported module sits in a chunk it has already loaded and resolves, while a dynamically imported one gets a chunk of its own that is never loaded and does not. The snapshots for that second route record broken output on purpose: in-process validation resolves the frame, so losing it is a regression from moving validation onto a worker, and it affects Turbopack (no location) and Webpack (compiled positions) alike. Follow-ups address each. The snapshots are recorded with the retry applied rather than before it. Without it Turbopack's output differs between Node.js 20 and 22, and the broken URLs embed the test's temporary install directory, which changes on every run, so no stable recording exists. One consequence is that CI cannot fail on a regression of the retry itself; that was checked by running the new suite under Node.js 24. |
||
|
|
3c4afc1a3a |
Run Cache Components dev validation on a worker thread (#96153)
This moves the dev-mode Cache Components validation renders off the dev server's main thread onto a worker thread, so rapid navigation no longer starves the event loop. The worker crosses into the app-page bundle exactly once, calling a new `ComponentMod.routeModule.runValidationInDev` entry that rebuilds the render context, work store, and request store from a serializable snapshot and runs the whole validation there, so the client prerender and the user's client components resolve the single app-page React instance rather than a second copy. A thin worker shell (`dev-validation-worker.ts`) plus a single-worker pool (`dev-validation-worker-pool.ts`) load the user bundle via `loadComponents`, install code-frame support for CLI output, and forward the validation errors back as Flight bytes; the main thread only delivers them to the overlay through `sendErrorsToBrowser`. The snapshot, the globalThis-symbol handoff, and the shared error-delivery helpers live in their own modules. The dev server installs the worker when `experimental.devValidationWorker` is not `false`, and `runDevValidationInBackground` uses it when present, falling back to the in-process path otherwise. A one-slot `SharedArrayBuffer` propagates a supersede abort into the worker so a newer navigation cancels an in-flight validation. The runtime bundle gains an `app-worker` entry for the worker with the `build/swc` boundary externalized for the code-frame native binding, and `patch-error-inspect.ts` now backs its code-frame renderer with a globalThis symbol so all copies of the module share it across the thread. The synthetic `bench/dev-validation` benchmark navigates back-to-back, so every click lands inside the validation window — the worst case for main-thread contention. Browser-observed navigation TTFB in that window, worker vs in-process: | Route | Worker (p50/p95/max) | In-process (p50/p95/max) | | ------ | -------------------- | ------------------------ | | client | 19 / 24 / 27 ms | 40 / 66 / 7762 ms | | server | 42 / 45 / 46 ms | 122 / 158 / 252 ms | | sprite | 109 / 117 / 169 ms | 196 / 208 / 299 ms | The steady-state difference is only tens of milliseconds; the effect that matters is the tail. In-process, a navigation that collides with an in-flight validation render can stall for seconds (this run peaked at ~7.8s on the client route, and the peak varies run to run) because a staged render does not yield until it finishes; off-thread the main thread stays free and that stall disappears. Read these as an upper bound, not a speedup that generalizes. The work moved off-thread is the validation render's CPU — bounded, route-dependent, and free of IO — so it does not grow with the main render's cost. In an app whose main render is dominated by IO the same absolute saving is a small fraction of the request, and it only appears when a navigation lands in the brief validation window; at ordinary click speed it is largely invisible. This is a dev-only responsiveness improvement whose benefit varies widely with the app and the navigation pattern. As a follow-up, the validation work that still runs on the main thread could move to the worker as well. When the main render can't be reused for validation (for example after a cache miss), the validation re-renders on the main thread to produce its inputs, resuming from the Resume Data Cache (RDC) the main render already filled, and only then hands the resulting Flight chunks to the worker. A later iteration could run those renders inside the worker too, transporting the (serializable) RDC so the worker can resume from the filled caches rather than reading them back on the main thread. closes NAR-895 |
||
|
|
62084e3fbd |
[test] Unflake the enabled-features-trace test suite (#96175)
<img width="428" height="179" alt="Screenshot 2026-07-24 at 22 42 04" src="https://github.com/user-attachments/assets/38478940-d23e-4faa-bd88-5758190be158" /> [Flakiness metrics](https://app.datadoghq.com/ci/test/runs?query=test_level%3Atest%20%40git.repository.id%3A%22github.com%2Fvercel%2Fnext.js%22%20%40test.name%3A%22enabled%20features%20in%20trace%20should%20denormalize%20inherited%20enabled%20features%20during%20upload%22%20%40test.type%3A%22nextjs%22%20%40test.status%3A%28%22fail%22%20OR%20pass%29&agg_m=count&agg_m_source=base&agg_t=count&fromUser=true&index=citest&start=1784320894753&end=1784925694753&paused=false) The `render-path` span is recorded when a request's response closes, which is too late for any flush other than the one the dev server performs while shutting down. The parent `next dev` process escalates to SIGKILL 100ms after signalling the child, and on a machine running eight test files at once the child does not reliably get scheduled to run its cleanup within that window, so the span never reached the trace file and the upload assertions failed. This change raises the budget for the test through `NEXT_EXIT_TIMEOUT_MS`, which was added alongside that timeout in #67165 so that it can be increased when the child's exit work matters more than a fast exit. The same approach is already used in `test/e2e/filesystem-cache/warm-restart-task-stats.test.ts`, where the timeout would otherwise cut off a Rust `on_exit` handler before it writes its task statistics. Both test cases previously guarded their request with a check for the existence of the trace file, which the dev server creates on its own once the first compile finishes. When that happened before the first test body ran, neither case issued a request and the trace file contained no `compile-path` or `render-path` span at all. The request and the shutdown now happen once in `beforeAll`, and the fixed 500ms sleep that followed the shutdown is replaced by a `retry` that waits for the spans the assertions depend on. --- <sub>Stack created with <a href="https://github.com/github/gh-stack">GitHub Stacks CLI</a> • <a href="https://gh.io/stacks-feedback">Give Feedback 💬</a></sub> |