mirror of
https://github.com/vercel/next.js.git
synced 2026-09-20 02:25:18 +08:00
v16.4.0-canary.21
1294 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
662b0355eb |
[test] Resolve workspace:* packages to the version under test (#98330)
Test fixtures that depend on monorepo packages (for example `@next/third-parties` and `@next/mdx`) declared them at the `canary` dist-tag, which installs the published npm canary instead of the build under test. In deploy tests this also broke the remote install entirely: the published canary's `peerDependencies` ranges (for example `^16.0.0-beta.0`) reject the prerelease preview versions that `NEXT_TEST_VERSION` installs for `next` (for example `16.4.0-preview-<sha>-<date>`), so `npm install` failed with ERESOLVE. Test dependencies can now be declared as `workspace:*`, which resolves to the build from the current checkout in every test mode: - dev/start: the locally packed tarball from `pack-for-isolated-tests`, with a descriptive error when the package has no pack task or is not part of the repository. - deploy: the preview tarball of the tested commit when `NEXT_TEST_VERSION` points at a preview build (preview tarballs already rewrite their monorepo peer dependencies to the same preview URLs, so peer resolution succeeds), falling back to the version in the worktree otherwise. Private packages without published preview tarballs fail with a descriptive error. All existing tests that used `canary` for monorepo packages are migrated to `workspace:*`. |
||
|
|
e9c16094f2 |
Fix flaky successive HMR changes test (#97610)
### What? Correct the fixture marker used to construct the second invalid source state in the successive App Router HMR test. ### Why? The test was replacing a marker that does not exist in its MDX fixture. Because string replacement silently returned the original content, supposedly successive patches sometimes wrote byte-identical source while the test harness waited for an HMR completion callback. A no-op write does not have to produce an HMR update, so the callback could remain pending until the harness reported a misleading timeout. CI logs showed Fast Refresh itself completing quickly, which ruled out an insufficient timeout budget and made extending the timeout the wrong fix. ### How? Match the replacement to the fixture's actual marker. This restores four genuinely distinct source states in every error/recovery cycle, preserving the intended overlay coverage without changing Turbopack, the shared HMR helper, or its timeout. ### Verification - `pnpm test-dev-turbo test/development/acceptance-app/app-hmr-changes.test.ts` - Repeated the focused test three times under 14 CPU-contention workers - Prettier and ESLint on the changed test file <!-- NEXT_JS_LLM --> --------- 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> |
||
|
|
724641c6a2 |
Fix Turbopack HMR recovery after dropped connections (#97966)
### What? Track the latest applied Turbopack HMR 'hash' in the browser runtime and include it when subscriptions are restored after reconnecting. Reload the page when the client's hash differs from the server's current hash. Add an App Router development test that interrupts HMR traffic, misses an update, restores the connection, and verifies the page reloads into the current revision. ### Why? A browser that temporarily loses its HMR connection can miss updates and remain out of sync after reconnecting. ### How? Attach the current HMR hash to Turbopack connection and update messages. The runtime records the last hash it processed and sends it with each subscription. The development server compares that value against its current hash and requests a full reload on mismatch. <!-- NEXT_JS_LLM --> |
||
|
|
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> |
||
|
|
dae438dc0d |
Stop printing a stack frame for error message text (#97829)
Next.js printed a stack frame that does not exist for an error whose message ends with text that looks like a file location. A logged connection failure read `at <unknown> (Error: connect ECONNREFUSED ::1:45999)` in front of its real frames. `stacktrace-parser` tries five patterns on every line, and the pattern for JavaScriptCore makes the `<name>@` prefix optional. That pattern `:<line>:<column>`, and a line of an error message can end that way. A JavaScriptCore frame always holds `@`, also at the top level, so the prefix is not optional in a real stack. The patch for `stacktrace-parser` now requires that prefix. A line of a message no longer becomes a frame, and every JavaScriptCore frame still parses, including one whose path holds a space. A Turbopack build error puts the file and the position of the error in its message, and the parser read that line as a frame as well. That frame is gone now, and the location stays in the message. The snapshots of the build errors record the change. |
||
|
|
76e24273c7 |
test: stabilize read-only page recreation (#97674)
### What? Stabilizes the read-only source HMR page deletion and recreation test in webpack development mode. ### Why? The test reloads after restoring a deleted page because webpack does not automatically refresh when the route reappears. In polling watch mode, that reload could wait for the full page load or be aborted by a concurrent Fast Refresh navigation. Either error escaped the retry callback, allowing one transient navigation race to fail the test before it could retry. ### How? The webpack-only reload now waits only until the document is available instead of waiting on potentially slow subresources. Transient reload failures are handled inside the retry callback, while the existing page-content assertion remains the success condition. This preserves the test's intent and ensures a route that does not recover still fails. ### Verification - Fresh `pnpm build` followed by a cold headless webpack run: 3 passed - Three additional headless webpack runs: 3 passed each - Headless Turbopack run: 2 passed, 1 expected skip - Focused Prettier and ESLint checks <!-- NEXT_JS_LLM --> 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> |
||
|
|
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 --> |
||
|
|
c0e749f609 |
test: stabilize poisoned proxy error overlay (#97609)
### What? Stabilizes the RSC poisoned-import error-overlay test for `proxy.js` without weakening or retrying its redbox assertion. ### Why? An initially broken proxy can emit the expected build error and then force a startup full reload. That reload can clear the overlay before the test begins observing it, causing an intermittent `Expected Redbox but found no visible one` failure even though validation worked. ### How? The proxy parameter now starts from a valid module and introduces the poisoned import only after the sandbox page has hydrated. This moves the assertion onto the live HMR path and removes the startup-reload race. Middleware and instrumentation retain their initial-compile setup because their error overlays are reliable there and instrumentation does not reliably surface this edit through Turbopack HMR. ### Verification - `pnpm test-dev-webpack test/development/acceptance-app/rsc-build-errors-poisoned-imports.test.ts` - `pnpm test-dev-turbo test/development/acceptance-app/rsc-build-errors-poisoned-imports.test.ts` - Proxy case repeated 5 times with Webpack and 3 times with Turbopack - Prettier, ESLint, and `git diff --check` <!-- NEXT_JS_LLM --> --------- 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> |
||
|
|
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.
|
||
|
|
d820350579 |
feat(turbopack): isolate HMR listeners across microfrontends (#95997)
## Summary When two Next.js microfrontend child applications run in development mode at the same time, their HMR clients conflict because they share the same global chunk-update listener registry. Since turbopack added support for the chunkloadingglobal configuration, I think this configuration can also consume HMR's global object simultaneously: PR: https://github.com/vercel/next.js/pull/88790 and https://github.com/vercel/next.js/pull/93488 This change scopes the listener registry to each runtime chunk-loading global so the applications can receive HMR updates independently. ## Test Update snapshot test case |
||
|
|
ee6909213f |
Turbopack: Show last modified file when waiting for the filesystem to settle (#97648)
This is a follow-up to https://github.com/vercel/next.js/pull/96116 If we're waiting a long time for your filesystem to settle, we should show you the last modified path, so at least you can have an idea of why this is happening. |
||
|
|
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. |
||
|
|
e2fb664ceb |
Remove HmrTarget (#97253)
With #94948 we intended to move the client over to the firehose feed of HMR events with the intent of unifying the code paths for maintenance. However, now that Server HMR is moving to a pull-based model (which client HMR will not be able to implement), let's keep the split. There's no need to encode the HmrTarget into each surface, and we can just use the function name to indicate which mode of HMR it's for. |
||
|
|
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 |
||
|
|
b677feb02f |
Turbopack: More aggressively debounce filesystem watch events if we detected changes to node_modules (#96116)
Previously, we were debouncing update by sleeping 1ms at a time on macos and windows, and 10ms at a time on Linux. During a slow `pnpm install`, or a `git checkout`, this could cause us to do a bunch of extra throwaway work. Changes: - Increase the debounce interval to a consistent 10ms everywhere. This should still be small enough that it's not noticable on macos or windows. - If an event touches `node_modules`, there's a good chance that a package manager is running and many other files will be modified, so extend the batch deadline by 200ms instead of 10ms. - Because there's a chance that the batch deadline could get extended indefinitely (this was always possible, just more likely now) include a compilation event that gets logged after 5 seconds. |
||
|
|
b18acf6712 |
Remove the development debug channel persistence (#97510)
Documents are now served with `no-store` in development, so a browser never restores one from its HTTP cache and the page scripts never re-execute against a debug channel that has already delivered its data. The persistence and restore machinery that existed for that case has no remaining trigger, so this removes it: the `IndexedDB` write scheduled on every page load, the cache-restore detection across `PerformanceNavigationTiming` fields and `deliveryType`, the `pageshow` deferral for browsers that populate those fields late, and the `location.reload()` fallback for a missing entry. It was built up over #92892, #93486, #94128, #94317 and #94243, and takes `debug-channel.ts` from 535 lines to 121. The per-consumer `tee()` and the LRU-bounded pair map stay. They were added for an unrelated reason, namely that one response can be decoded more than once, so this is not a revert to the state before the persistence landed. The rejection handler on `writer.closed` also stays, because an errored stream would otherwise surface as an unhandled rejection now that nothing else observes it. `bfcache-regression` keeps the original regression test, which loads a page, navigates away, comes back and asserts that the counter is still interactive. That case now fails if the development `Cache-Control` value ever goes back to `no-cache`, because the restored document would block hydration with no reload to recover, so it is worth keeping as is. The other three tests lose their premise and are deleted along with the routes only they used: the pruning case that was skipped when the header changed, the recovery case that needs a restore path to recover into, and the streaming case that guarded the detection against treating an in-flight response as a restore. The `large-debug-data` route goes too. It existed only to make the persistence write expensive enough to profile by hand when it moved to `IndexedDB`. |
||
|
|
2839982a03 |
Stop the browser from restoring stale pages in development (#97505)
Development responses used `no-store, must-revalidate` until #88182 tried `no-cache, must-revalidate` behind `experimental.devCacheControlNoCache`, and #91503 removed that option and hard-coded the `no-cache` value everywhere. That was right for static assets and wrong for documents. A browser may reuse a stored response for a history navigation without revalidating it, and development documents are streamed without an `ETag`, so there is nothing to revalidate against. Going back therefore restored the document the browser had stored earlier and showed output from before the latest edit, and it is also what forced the debug channel persistence workarounds in #92892, #93486 and #94243. Documents and RSC or data responses now use `no-store` again, set in `app-page-runtime.ts` for app pages, in `pages-handler.ts` for pages, and in the legacy render pipe in `base-server.ts` so that the three do not drift apart. None of them ever serves a static asset, so assets keep `no-cache, must-revalidate` from the `nextStaticFolder` branch in `router-server.ts` and stay cacheable: they are revalidated against the `ETag` that `serveStatic` adds and reused from a `304` instead of being downloaded again on every page load. `must-revalidate` is left off the document value, because it only governs reuse of an already stale stored response and nothing is stored any more. A back navigation is no longer instant, since the document is fetched again instead of being restored locally. `test/development/dev-cache-control` covers both sides of that trade-off: an edit that is visible after a back navigation, and unchanged assets that still come back as `304`. It replaces `dev-cache-control-no-cache` and asserts the header for both routers as well, so there is one suite instead of two with nearly the same name. A development document is now never restored from the HTTP cache, so the debug channel persistence has no remaining trigger and its `IndexedDB` write on every page load is no longer needed. Removing it is a follow-up on top of this change. The pruning and recovery test in `bfcache-regression` is the one case whose premise disappears entirely, and it is skipped here with a note to delete it along with the persistence. closes #96503 |
||
|
|
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 --> |
||
|
|
b5538511ed | test: update React 18 redbox snapshot (#97415) | ||
|
|
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` |
||
|
|
17f6f135e2 |
[fragment-scroll] Rename ScrollAndMaybeFocusHandler to ScrollHandler (#96828)
As of https://github.com/vercel/next.js/pull/95602, the old scroll-and-focus handler doesn't exist anymore As of https://github.com/vercel/next.js/pull/96113, the new handler no longer handles focus. This updates the relevant variable names to reflect that we no longer handle focus. |
||
|
|
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` |
||
|
|
da8fc4fea3 |
fix(turbopack): point at the glob that matched a file with no module type (#96561)
A build error for a glob-matched file with no module type named only the file, with no route, call site or import trace — the file never becomes a module, so it has no place in the module graph. `import.meta.glob` now also reports an error at the call site with the pattern and matched key. Also fixed the stale `Read more` link (308s to a page whose anchor changed). <!-- NEXT_JS_LLM --> Co-authored-by: vercel-fleet[bot] <308483924+vercel-fleet[bot]@users.noreply.github.com> Co-authored-by: Tobias Koppers <1365881+sokra@users.noreply.github.com> |
||
|
|
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. |
||
|
|
2c04735944 |
[fragment-scroll] Remove config.experimental.appNewScrollHandler (#95602)
|
||
|
|
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> |
||
|
|
cbf0cef687 |
Enable TypeScript CLI by default (#96497)
## Summary Enable the project-local TypeScript CLI checker by default during `next build`, while preserving `experimental.useTypeScriptCli: false` as an opt-out to the TypeScript compiler API. Update the TypeScript 7 guidance, diagnostics, tests, and documentation to match the new default. ## Verification - `pnpm exec jest --runTestsByPath test/unit/isolated/config.test.ts` - Pre-commit lint-staged checks passed (Prettier and ESLint) - Not run: `pnpm test-start-turbo test/production/app-dir/typescript-cli/typescript-cli.test.ts` (isolated fixture dependency installation was blocked by unavailable npm registry access) - Not run: `pnpm --filter=next types` (repository has pre-existing unrelated TypeScript errors) <!-- NEXT_JS_LLM --> |
||
|
|
7612eaeda1 |
Consolidate unhandled rejection logging into a single listener (#95999)
An unhandled rejection was previously logged by up to three independent
process listeners at once: the render runtime's crash-prevention handler
in `process-error-handlers.ts` (a bare `console.error`), the router
server's `Log.error('unhandledRejection: ', err)`, and the dev server's
`logErrorWithOriginalStack`. The runtime handler must exist on every
deployment target (#77997), but on self-hosted `next start`/`next dev`
it shares a process with the router server's and dev server's listeners,
so a single rejection was logged multiple times in different formats.
THe first commits adds a test showing the current behavior where we log
multiple times.
The second commit introduces `registerUnhandledRejectionListener` and
`isUnhandledRejectionListenerRegistered` in `process-error-handlers.ts`,
and converts the router server and dev server to check-then-register
instead of installing their own rejection loggers:
- The listener function is shared via a `Symbol.for` key on
`globalThis`, so multiple copies of the module (e.g. in the pre-compiled
server bundle and in a route module bundle) register and detect a single
listener instance.
- The registration check queries
`process.listeners('unhandledRejection')` instead of a module-global
flag, so it stays accurate even after external code calls
`process.removeAllListeners`. `installProcessErrorHandlers` therefore
calls the register function unconditionally.
The `uncaughtException` handlers are left as they are; they have the
same duplication and could be consolidated the same way in a follow-up.
|
||
|
|
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 --> |