Commit Graph

1310 Commits

Author SHA1 Message Date
Benjamin Woodruff ‮ 7b58e5880c Turbopack: Add support for specifying additional roots (#98003)
Full motivation and plan here:
https://app.notion.com/p/vercel/Turbopack-pnpm-Global-Virtual-Store-383e06b059c480579403ddfd71cc2d40?source=copy_link

The goal is to allow `DiskFileSystem` to traverse outside of it's own
root to other configured `DiskFileSystem`s when following symlinks. We
may allow traversal in other situations in the future, but this is
limited to symlink resolution for now.

## Global Virtual Store

The motivation for this is to enable [pnpm's Global Virtual Store
feature](https://pnpm.io/global-virtual-store) (and there are other
package managers doing this, including nub and bun).

We'd expose the ability to manually configure this in `next.config.js`,
but we should also auto-configure ourselves for popular package managers
(or at least make a best effort to do so, the `PNPM_HOME` semantics can
be complicated). The `ignoreIfMissing` option is provided for this
situation: We can configure a bunch of roots automatically, and they
only actually get set up if they exist, the check for directory
existence is cheap.

## NFT changes

This requires a couple extensions to the `*.nft.json` file format:
https://github.com/vercel/next.js/pull/98469

## Related Issues

- #93556
- https://github.com/pnpm/pnpm/issues/14972
2026-09-18 17:08:40 -07:00
Aurora Scharff 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 -->
2026-09-18 18:18:20 +02:00
Jimmy Miller 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
2026-09-18 06:59:28 -07:00
Jimmy Miller 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.
2026-09-17 11:53:21 -07:00
Niklas Mischkulnig 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>
2026-09-16 17:09:32 +02:00
Jimmy Miller 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.
2026-09-15 07:21:32 -07:00
Jiwon Choi 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>
2026-09-14 16:15:39 +02:00
Will Binns-Smith d5276f04a1 Make TurbopackResult payloads disjoint (#98575)
### What?

Refactors the JavaScript-facing `TurbopackResult<T>` into a stable
wrapper whose payload lives under `value` and whose issues remain
top-level. Native API consumers, entrypoint conversion,
development-server paths, HMR handling, and direct API tests now follow
the disjoint shape.

Two event subscriptions that had inaccurate wrapper declarations now
expose their existing plain runtime payloads explicitly: update-info
events remain `UpdateMessage`, and compilation events remain
`CompilationEvent`. Nullable native entrypoint payloads are also
declared accurately and normalized at the JavaScript API boundary.

### Why?

The previous intersection-based representation merged payload fields
with result metadata. That allowed fields such as `issues` to overwrite
one another and caused non-object payloads to be discarded, making the
result shape depend on `T`. A dedicated payload property avoids those
collisions and preserves every payload type, including `null`.

Keeping plain event streams distinct from result wrappers also ensures
`TurbopackResult<T>` consistently means the native API actually provides
wrapper metadata.

### How?

The N-API serializer now always creates a fresh wrapper rather than
mutating an object payload. The shared TypeScript type models that
wrapper directly and requires an explicit payload type. Conversion
layers replace only the nested payload while explicitly preserving
wrapper issues. Call sites continue to process wrapper issues while
reading domain data through `value`.

The update-info and compilation-event declarations were aligned with
their native callback types instead of introducing new runtime wrappers
for streams that do not collect issues. Rust `Option<NapiEntrypoints>`
payloads are declared as nullable, then normalized to the existing
empty-entrypoints representation after a null-safe route check.

### Verification

- `pnpm build-all`
- `pnpm --filter=next types`
- `pnpm swc-build-native`
- `cargo check -p next-napi-bindings`
- `cargo fmt --all -- --check`
- ESLint and Prettier on changed files
- `pnpm test-dev-experimental-turbo
test/development/app-aspath/app-aspath.test.ts`
- `pnpm test-dev-turbo test/development/basic/next-rs-api.test.ts` — 26
passed, 1 skipped, 15 snapshots; Jest reported lingering open handles
after the green summary
- `pnpm test-dev-turbo
test/development/app-dir/concurrent-install/concurrent-install.test.ts`
- `pnpm test-dev-experimental-turbo
test/development/app-dir/concurrent-install/concurrent-install.test.ts`

<!-- NEXT_JS_LLM -->

<!-- fleet 74688340-f99a-462e-8f75-88d12c88000a -->

---------

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>
2026-09-11 16:25:09 -07:00
Jimmy Miller 30d6d8f497 Adds this.mode for webpack loaders (#98532)
Pretty straightforward. But a number of loaders depend on it including

- postcss-loader
- sass-loader
- stylus-loader
- vue-loader
- nunjucks-loader
- thread-loader
2026-09-11 12:05:03 -07:00
Jimmy Miller 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>
2026-09-11 07:29:43 -07:00
Joseph 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>
2026-09-11 16:01:14 +02:00
Hendrik Liebau 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.
2026-09-11 10:46:54 +02:00
Sebastian "Sebbie" Silbermann 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.
2026-09-10 13:50:35 +02:00
Tobias Koppers 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>
2026-09-09 08:07:34 -07:00
Tobias Koppers 56b95f41a2 test: stabilize error recovery snapshot timing (#98414)
### What?

Stabilizes the pages-router `syntax > runtime error` acceptance test
without weakening its redbox assertions.

### Why?

The fixture emitted runtime errors every second and waited exactly one
second before capturing the first redbox. On slower release runners, a
second error could arrive while the asynchronous snapshot was being
collected, changing the expected single error into an array and causing
repeated retries.

The broader canary assertion cluster has several independent signatures;
this PR intentionally addresses only the timing race with a
reproducible, test-local cause.

### How?

The redbox matcher now performs the initial wait itself, while the
fixture uses a longer interval to leave a stable capture window. The
later wait remains long enough for another runtime error to occur,
preserving the test’s core assertion that a subsequent runtime error
does not replace the syntax/build error.

### Verification

- `pnpm build-all`
- `HEADLESS=true NEXT_TELEMETRY_DISABLED=1 NEXT_TEST_CI=true pnpm
test-dev-webpack test/development/acceptance/error-recovery.test.ts` (6
tests and 10 snapshots passed)
- Prettier and ESLint on the changed file

<!-- NEXT_JS_LLM -->

<!-- fleet ee9dd3ec-a271-4d0d-8165-d7872e226a6a -->

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>
2026-09-09 15:03:46 +02:00
Spyros Ioakeimidis 2b11d01a8c Fix: Turbopack CSS HMR error when editing an unmounted merged CSS chunk (#94551)
### Fixing a bug

- [x] Related issues linked using `fixes #74749`.
- [x] Tests added (`test/development/css-hmr-unmounted-chunk`)
- [x] N/A — removes an erroneous thrown error

### What?

In `next dev` with Turbopack, editing a `.module.css` whose component
had unmounted threw `Error: No link element found for chunk
.../components_<hash>._.css` as an unhandled rejection. The edit
**silently failed to apply** — you had to reload the page manually to
see it.

### Why?

`applyChunkListUpdate` handles chunk updates asymmetrically: `'added'`
calls `loadChunkCached` (loads if missing), but `'total'` calls
`reloadChunk`, which is rejected when no `<link>` exists. When a lazy /
`dynamic(ssr:false)` component unmounts, `unloadChunk` removes its
merged-aggregate `<link>` but the chunk list stays subscribed; a later
CSS edit emits a `'total'` update for the now-unlinked aggregate →
`reloadChunk` rejects → the `'total'` branch discards the promise →
unhandled rejection, and the edit never applies.

### How?

`reloadChunk` now loads the fresh stylesheet when no `<link>` is present
(mirroring the `'added'` branch) instead of rejecting, so the edit
applies without a manual reload. Regression test reproduces with a
minimal `dynamic(ssr:false)` + 3 `React.lazy` routes fixture (red before
— the error is thrown and the edit doesn't apply; green after).

Fixes #74749

<!-- NEXT_JS_LLM_PR -->

---------

Co-authored-by: Benjamin Woodruff <benjamin.woodruff@vercel.com>
2026-09-09 01:37:30 +00:00
Sebastian "Sebbie" Silbermann 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:*`.
2026-09-08 18:17:29 +02:00
Tobias Koppers 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>
2026-09-07 15:27:50 +02:00
Will Binns-Smith 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>
2026-08-31 09:12:51 -07:00
Will Binns-Smith 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 -->
2026-08-31 09:06:58 -07:00
Niklas Mischkulnig 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>
2026-08-31 13:59:14 +02:00
Niklas Mischkulnig 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>
2026-08-27 21:22:53 +00:00
Hendrik Liebau 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.
2026-08-25 13:38:47 +02:00
Tobias Koppers 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>
2026-08-25 10:25:07 +02:00
Will Binns-Smith 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>
2026-08-24 17:25:47 -07:00
Marcos Hernanz 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`
2026-08-23 11:56:26 -07:00
Jiwon Choi 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 -->
2026-08-21 22:45:12 +02:00
Tobias Koppers 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>
2026-08-21 14:53:12 +02:00
Janka Uryga 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.
2026-08-21 13:50:23 +02:00
zoomdong 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
2026-08-21 11:19:16 +02:00
Benjamin Woodruff ‮ 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.
2026-08-20 16:59:56 -07:00
Janka Uryga 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.
2026-08-20 15:27:11 +02:00
Sebastian "Sebbie" Silbermann 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.
2026-08-20 11:57:04 +02:00
Will Binns-Smith 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.
2026-08-19 17:22:47 -07:00
Niklas Mischkulnig 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
2026-08-19 13:15:13 +02:00
Benjamin Woodruff ‮ 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.
2026-08-18 17:05:14 -07:00
Hendrik Liebau 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`.
2026-08-18 16:54:46 +02:00
Hendrik Liebau 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
2026-08-18 16:54:45 +02:00
Hendrik Liebau 5817bd1def Anchor the async local storage instances to global symbols (#97255) 2026-08-16 23:15:51 +02:00
Josh Story 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 -->
2026-08-16 14:40:01 +00:00
Tim Neutkens b5538511ed test: update React 18 redbox snapshot (#97415) 2026-08-15 15:11:41 +02:00
Tim Neutkens 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 -->
2026-08-15 13:07:22 +02:00
Marcos Hernanz 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
2026-08-14 14:17:29 -07:00
David Alexandru Ilie 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**.
2026-08-14 12:39:01 +01:00
Hendrik Liebau 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&currentTab=overview&eventStack=&fromUser=true&index=citest&start=1783948256248&end=1786540256248&paused=false)
2026-08-12 21:53:40 +02:00
Sam Poder 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
2026-08-12 09:35:54 -07:00
Hendrik Liebau b93ed4fa0f [test] Unflake use-cache-custom-handler-dev tests (#97187) 2026-08-12 10:58:16 +02:00
Hendrik Liebau 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.
2026-08-10 23:39:13 +02:00
Andrew Clark 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>
2026-08-10 20:39:29 +00:00
David Alexandru Ilie 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`
2026-08-10 16:15:56 +01:00