1255 Commits

Author SHA1 Message Date
Andrew Clark 75548c9a2b [backport] Fix Nav Inspector request loop on repeat captures (#97326)
Backports #97050 ("Fix Nav Inspector request loop on repeat captures")
to `next-16-3`.

With the Nav Inspector enabled, clicking a `<Link prefetch={true}>`,
closing the inspector, navigating home, re-enabling it, and clicking the
same link left the app hung in a pending "Compiling…" state while it
fired 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. Because the segment cache resolves
lookups by most-specific-match, 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 — so every scheduler pass
discarded and refetched forever.

Rather than patch the filter, the fix replaces the `ownedEntries`
mechanism with a per-scope segment `CacheMap`. Each lock scope owns a
private map that starts empty and is discarded at release, so a captured
navigation structurally observes only data fetched under the lock. The
map is bound to a unit of work when it 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, history-traversal restores, 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, so the non-testing paths are unchanged.

### Verification on this branch

Cherry-picked from `5dc3ae1fe7`. Three files conflicted; each was
resolved by keeping the release branch's existing code shape and
applying only the fix's actual change — threading the segment `CacheMap`
through as an explicit parameter.

- **`refresh-reducer.ts`** and **`restore-reducer.ts`** — the conflicts
were confined to the import block. Canary imports
`convertServerPatchToFullTree` from
`./segment-cache/decode-server-response`, a module that doesn't exist on
this branch; `next-16-3` still imports it from
`./segment-cache/navigation`. I kept the release branch's import source
and added the new `segmentCacheMap` import alongside it. The
function-body changes (passing `segmentCacheMap` as the shared map)
applied cleanly, and the `startPPRNavigation`/`spawnDynamicRequests`
argument order lines up with this branch's signatures.

- **`cache.ts`** — the substantive one. `next-16-3` predates the
post-16.3.0 segment-cache refactors, so three areas differ from canary
and had to be adapted rather than taken verbatim from the patch: (1)
`RouteTree` is still non-generic here (`RouteTree`, not
`RouteTree<RSCSegmentData | null>` — this branch has no `RSCSegmentData`
and the tree doesn't carry render data), so the three signatures the
patch retyped were kept as plain `RouteTree`; (2)
`writeSegmentBundleResponse` still has the older `StaticShell`/`else`
re-key split guarded by `__NEXT_VARY_PARAMS`, versus canary's single
`upsertSegmentEntry` — I kept the release-branch logic and threaded
`map` into both of its `upsertSegmentEntry` calls; (3)
`writeDynamicRenderResponseIntoCache` and `writeSeedDataIntoCache` still
use the `flightDatas` + `CacheNodeSeedData` write model rather than
canary's route-tree-carries-data model — I kept the release-branch
traversal and threaded `map` into each `writeSeedDataIntoCache` /
`fulfillEntrySpawnedByRuntimePrefetch` call. Everything else in
`cache.ts` — the `ownedEntries` removal, the `segmentCacheMap` export,
`insertEmptySegmentCacheEntry`, and the `map` parameter added to every
read/write helper — applied cleanly and is identical to canary. I
confirmed no stale references to the removed `ownedEntries` /
`readSegmentCacheEntry` / `navigationLockPrefetch`-param APIs remain,
and that every call site of the retyped helpers passes a map.

The remaining files applied without conflict. `server-patch-reducer.ts`,
`navigation-testing-lock.ts`, `navigation-testing-lock.disabled.ts`, and
the `instant-navs-devtools` test are byte-identical to canary.
`create-initial-router-state.ts`, `ppr-navigations.ts`,
`server-action-reducer.ts`, `navigation.ts`, and `scheduler.ts` differ
from canary only in pre-existing `next-16-3` divergence (the older
transport-data / route-tree models); the fix's own hunks (the
`segmentCacheMap` threading) applied to them cleanly. No local typecheck
was run — this fresh worktree has no `node_modules` and installing it
carries the known corepack/SWC-binary gotchas — so behavioral and type
verification is left to CI on this branch, which carries the
`instant-navs-devtools` regression test from #96692.

**Reviewer, focus here:** the `writeSegmentBundleResponse` resolution in
`cache.ts` (area 2 above) is the hunk I'd most like a second pair of
eyes on. I preserved this branch's `StaticShell`/`else` re-key branch
structure and only inserted the `map` argument into its two
`upsertSegmentEntry` calls; please confirm that routing both branches'
writes through the task's bound map (rather than the module-level
`segmentCacheMap`) is the correct behavior for the older re-key logic.

<!-- NEXT_JS_LLM -->

Co-authored-by: Sam Selikoff <sam.selikoff@gmail.com>
2026-08-13 17:17:27 -04:00
Hendrik Liebau 6d67f46a4d [backport] Keep the dev validation worker alive across HMR updates (#97315)
Backports #96988 ("Keep the dev validation worker alive across HMR
updates") to `next-16-3`.

Cache Components dev validation reported stack frames that pointed at
build output whenever a module had been updated while the dev server
ran. The overlay showed a raw `file:` URL, 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. This affected both the static shell validation and the
instant-navigation validation, since both run on the same worker.

Turbopack's server HMR evaluates an updated module as a script of its
own, 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. The worker now replays the applied updates, the
cleared manifest cache entries, and the evicted paths that the dev
server reports, in the same order the dev server applied them, which
leaves each updated module's inline source map in the worker's own
Node.js cache.

Not dropping the worker on every edit also removes a worker respawn and
a `loadComponents` call from each validation that follows an edit, which
is paid at exactly the moment the user is waiting for the insight. The
case in the test suite that covers this went from around 870ms to around
240ms.

This affects Turbopack dev with Cache Components, which is on by default
unless `experimental.devValidationWorker` is set to `false`.

### Verification on this branch

Cherry-picked from `f70564f742` with no conflicts. All source files are
byte-identical to canary. Behavioral verification is left to CI on this
branch; the change carries the `dev-validation-worker-source-maps`
suite, whose snapshots turn from a frame naming the chunk into resolved
frames.

<!-- NEXT_JS_LLM -->
2026-08-13 15:58:43 -04:00
Luke Sandberg 98b487c2d3 [backport] [turbopack] Fix HMR for dynamic imports evaluated from layouts (#97317)
Backports #97213 ("[turbopack] Fix HMR for dynamic imports evaluated
from layouts") to `next-16-3`.

Async chunks that the caller had already expanded (e.g. those reachable
from concatenated chunk
groups) were not tracked in the chunk list, so an edit inside one of
them produced no HMR update at
all. This shows up with the common `await
import(\`../../messages/${locale}.json\`)` pattern in a
layout.

Cherry-picked from 04e9933b33 with no
conflicts. All 8 files are
byte-identical to canary.

### Verification on this branch

- `cargo fmt --check -p turbopack-nodejs` — clean
- `cargo check -p next-api`, `cargo clippy -p next-api` — clean (these
build `turbopack-nodejs`
  with its real feature set)
- `pnpm build-all` — 18/18 tasks
- `test/development/app-dir/layout-dynamic-import-hmr` — passes with
Turbopack and with webpack
- `test/development/acceptance-app/{app-hmr-changes,ReactRefreshModule}`
— 2/2 pass

The new test was confirmed to actually cover the fix: reverting only the
two Rust files (keeping the
fixture) makes it fail with `Expected: "updated" / Received:
"original"`, and restoring them makes it
pass again.

Note: `cargo check -p turbopack-nodejs` in isolation fails on
`turbopack-node/src/pool_stats.rs`
with an unresolved `tokio`. That reproduces identically on unmodified
`next-16-3` and is present on
canary too (`mod pool_stats;` is ungated while `tokio` is optional
behind `process_pool`/
`worker_pool`), so it is pre-existing and unrelated to this backport.

<!-- NEXT_JS_LLM -->

Co-authored-by: Sam Poder <sampoder@berkeley.edu>
2026-08-13 19:09:25 +00:00
Tim Neutkens 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 -->
2026-08-03 18:10:51 +02:00
Sebastian "Sebbie" Silbermann 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.
2026-07-31 11:36:40 -04:00
Will Binns-Smith 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.
2026-07-28 13:49:30 -07:00
David Alexandru Ilie 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 -->
2026-07-28 14:15:21 -04:00
David Alexandru Ilie 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 -->
2026-07-28 14:15:21 -04:00
Tim Neutkens 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>
2026-07-28 14:15:21 -04:00
Andrew Clark 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.
2026-07-28 11:51:29 -04:00
David Alexandru Ilie 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 -->
2026-07-27 18:58:54 -04:00
Tim Neutkens 31b78bbed9 Add Instant Insights request timing (#95958)
## Summary

- record deferred Instant Insights work as a separate typed Request
Insights item
- keep validation spans, fetches, timing, and live updates separate from
the foreground request
- label Instant Insights consistently in DevTools and CLI output
- stack this observability change on #95939, which moves validation off
the navigation response path

## Verification

- `pnpm jest --runInBand
packages/next/src/server/lib/trace/request-insights.test.ts
packages/next/src/server/lib/trace/local-span-recorder.test.ts
packages/next/src/next-devtools/dev-overlay/components/request-insights/trace-viewer.test.ts
packages/next/src/next-devtools/dev-overlay/shared.test.ts`
- `NEXT_SKIP_ISOLATE=1 NEXT_TEST_PREFER_OFFLINE=1 pnpm test-dev-turbo
test/development/app-dir/request-insights/request-insights.test.ts`
- `NEXT_SKIP_ISOLATE=1 NEXT_TEST_PREFER_OFFLINE=1 pnpm test-dev-webpack
test/development/app-dir/request-insights/request-insights.test.ts`
- `pnpm --filter=next types`

<!-- NEXT_JS_LLM -->

---------

Co-authored-by: David Ilie <david@davidilie.com>
2026-07-27 07:27:19 -04:00
dan b51ca60fd4 Fix dev overlay symbolication for project paths needing percent-encoding (#96221)
In a project whose absolute path contains characters that percent-encode
in URLs — e.g. a space — the dev overlay showed no code frame and raw
`file://` URLs instead of source locations for server frames.

Two encoding mismatches caused this:

- Turbopack's `trace_source` percent-decoded the source map's original
file URL before comparing it against the still-encoded project root URI,
so the containment check failed ("Original file ... outside project")
for any project path with such characters. The comparisons now stay in
the encoded domain and only the outputs are decoded back into paths.

- React synthesizes stack frame `file:` URLs by prepending the scheme to
a filesystem path, so the overlay receives URLs with e.g. raw spaces.
These aren't well-formed, and they don't match the `url.pathToFileURL`
form Node.js keys its source map cache by. The overlay middleware now
re-encodes them through WHATWG URL parsing, which tolerates such input.

## Test Plan

New tests.

Known cases that remain broken are commented out with a TODO.
These could be resolved if https://github.com/react/react/pull/37105 or
equivalent lands.

## Before

<img width="825" height="732" alt="Screenshot 2026-07-25 at 14 39 18"
src="https://github.com/user-attachments/assets/0f119e52-ec86-495a-bf5c-5339faa1d606"
/>


## After

<img width="1126" height="733" alt="Screenshot 2026-07-25 at 14 58 14"
src="https://github.com/user-attachments/assets/a263ee8a-4cfe-4358-9a8e-2e58b4b16aa5"
/>

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-25 18:36:45 +01:00
Hendrik Liebau e1fbb1800f Run dev validation in process when using Webpack (#96219)
Node.js caches source maps per isolate, so the dev validation worker
only has maps for the chunk files loaded on its own thread. For chunks
it never loads, such as a module behind a dynamic `import()`, it reads
the `.map` that Turbopack writes next to the chunk. Webpack keeps its
dev source maps in the compiler instead, so the worker has nothing to
read and reports those frames with the position they have in the
compiled output rather than in the source.

This stops using the worker when the bundler is Webpack, so validation
runs in process again, where those frames resolve, and
`experimental.devValidationWorker` now has no effect there. Dev
performance under Webpack goes back to what it was before validation
moved to a worker, which is the better trade: the worker exists to keep
the event loop responsive during rapid navigation, and that is not worth
losing the source location on validation errors.
2026-07-25 16:30:39 +02:00
Hendrik Liebau 4bfd5c7170 Read chunk source maps from disk in the dev validation worker (#96218)
Node.js caches source maps per isolate, so the dev validation worker
only has maps for the chunk files loaded on its own thread. It never
renders server components, and the built entry reaches segment modules
through lazy getters, so it loads whatever `loadComponents` pulls in and
nothing else. A module the bundler splits into a chunk of its own, as
Turbopack does for a dynamic `import()`, is therefore missing from that
cache entirely, and the frames pointing into it are logged without a
source location. In-process validation resolves them, so this is a
regression from moving validation onto a worker.

On the main thread the same miss is covered by asking Turbopack for the
map through the `Project` handle, which cannot cross a thread boundary.
The worker instead reads the `.map` that Turbopack already wrote next to
the chunk, which needs no project handle and, unlike Node's cache, does
not depend on the chunk having been evaluated on this thread. Lookups
are restricted to `distDir` so a stack frame cannot point the reader at
an arbitrary file, and both hits and misses are memoised.

Installing that lookup also required moving
`bundlerFindSourceMapPayload` onto a `globalThis` symbol.
`patch-error-inspect` is bundled into several runtimes that each get
their own copy, and the copy that registers the implementation, here the
worker bundle, is not the copy that symbolicates the frame, which is the
app-page bundle's. The code frame renderer in the same file is already
shared this way for the same reason.

Webpack keeps its dev source maps in the compiler rather than writing
them next to the chunks, so there is nothing for the worker to read and
its frames are unchanged. A follow-up turns the worker off for Webpack
so that validation runs in process, where those frames still resolve.
2026-07-25 16:30:38 +02:00
Hendrik Liebau c1617dedc8 Retry the source map lookup with a plain path (#96215)
Running
`test/e2e/app-dir/instant-validation/suspense-boundaries.test.ts`
locally on Node.js 24 currently fails in the two "missing suspense
around search params" cases: stacks that should read
`app/…/page.tsx:40:18` are logged as raw
`about://React/Prefetch/file:///…/%5Broot-of-the-server%5D…` URLs, so
the validation errors printed to the terminal no longer point at any
source. CI is green on the same commit, because it runs Node.js 20. The
dev validation worker (#96153) and the switch to `file:` source maps for
Turbopack (#95946) landed hours apart, and the worker's branch predated
the source map change, so the two first met on canary, where the older
Node.js hid the result.

Symbolicating a frame that React spliced in from another environment
means decoding the chunk path out of its `about://React/…` URL and
asking Node.js for that chunk's source map. Decoding returns the path as
written, while Node.js keys its cache by the `pathToFileURL` encoding of
the same path, and from Node.js 22 on that encoding escapes the brackets
in Turbopack's `[root-of-the-server]` chunk names, so the lookup misses.
Node.js 20 leaves brackets alone, which is why the same code resolves
there.

PR #95946 anticipated the mismatch and left it as a follow-up, because
on the main thread a miss is only wasteful: Turbopack hands back the
chunk's map instead, which means parsing one the process had already
parsed and cached. The validation worker runs on its own thread with no
access to the Turbopack project, so there the miss is final. Node.js
also accepts the plain path as a cache key, unambiguously for both a
CommonJS absolute path and an ESM `file:` URL, so we retry with it
before giving up. Making React's fake frame URLs reversible, as proposed
in https://github.com/react/react/pull/37105, would let the first lookup
succeed on its own and retire the retry.

The test added here pins down what the worker can symbolicate at all. It
caches maps per chunk file in its own isolate and never renders server
components, so a statically imported module sits in a chunk it has
already loaded and resolves, while a dynamically imported one gets a
chunk of its own that is never loaded and does not. The snapshots for
that second route record broken output on purpose: in-process validation
resolves the frame, so losing it is a regression from moving validation
onto a worker, and it affects Turbopack (no location) and Webpack
(compiled positions) alike. Follow-ups address each.

The snapshots are recorded with the retry applied rather than before it.
Without it Turbopack's output differs between Node.js 20 and 22, and the
broken URLs embed the test's temporary install directory, which changes
on every run, so no stable recording exists. One consequence is that CI
cannot fail on a regression of the retry itself; that was checked by
running the new suite under Node.js 24.
2026-07-25 15:15:50 +02:00
Hendrik Liebau 3c4afc1a3a Run Cache Components dev validation on a worker thread (#96153)
This moves the dev-mode Cache Components validation renders off the dev
server's main thread onto a worker thread, so rapid navigation no longer
starves the event loop. The worker crosses into the app-page bundle
exactly once, calling a new
`ComponentMod.routeModule.runValidationInDev` entry that rebuilds the
render context, work store, and request store from a serializable
snapshot and runs the whole validation there, so the client prerender
and the user's client components resolve the single app-page React
instance rather than a second copy.

A thin worker shell (`dev-validation-worker.ts`) plus a single-worker
pool (`dev-validation-worker-pool.ts`) load the user bundle via
`loadComponents`, install code-frame support for CLI output, and forward
the validation errors back as Flight bytes; the main thread only
delivers them to the overlay through `sendErrorsToBrowser`. The
snapshot, the globalThis-symbol handoff, and the shared error-delivery
helpers live in their own modules. The dev server installs the worker
when `experimental.devValidationWorker` is not `false`, and
`runDevValidationInBackground` uses it when present, falling back to the
in-process path otherwise. A one-slot `SharedArrayBuffer` propagates a
supersede abort into the worker so a newer navigation cancels an
in-flight validation.

The runtime bundle gains an `app-worker` entry for the worker with the
`build/swc` boundary externalized for the code-frame native binding, and
`patch-error-inspect.ts` now backs its code-frame renderer with a
globalThis symbol so all copies of the module share it across the
thread.

The synthetic `bench/dev-validation` benchmark navigates back-to-back,
so every click lands inside the validation window — the worst case for
main-thread contention. Browser-observed navigation TTFB in that window,
worker vs in-process:

| Route  | Worker (p50/p95/max) | In-process (p50/p95/max) |
| ------ | -------------------- | ------------------------ |
| client | 19 / 24 / 27 ms      | 40 / 66 / 7762 ms        |
| server | 42 / 45 / 46 ms      | 122 / 158 / 252 ms       |
| sprite | 109 / 117 / 169 ms   | 196 / 208 / 299 ms       |

The steady-state difference is only tens of milliseconds; the effect
that matters is the tail. In-process, a navigation that collides with an
in-flight validation render can stall for seconds (this run peaked at
~7.8s on the client route, and the peak varies run to run) because a
staged render does not yield until it finishes; off-thread the main
thread stays free and that stall disappears.

Read these as an upper bound, not a speedup that generalizes. The work
moved off-thread is the validation render's CPU — bounded,
route-dependent, and free of IO — so it does not grow with the main
render's cost. In an app whose main render is dominated by IO the same
absolute saving is a small fraction of the request, and it only appears
when a navigation lands in the brief validation window; at ordinary
click speed it is largely invisible. This is a dev-only responsiveness
improvement whose benefit varies widely with the app and the navigation
pattern.

As a follow-up, the validation work that still runs on the main thread
could move to the worker as well. When the main render can't be reused
for validation (for example after a cache miss), the validation
re-renders on the main thread to produce its inputs, resuming from the
Resume Data Cache (RDC) the main render already filled, and only then
hands the resulting Flight chunks to the worker. A later iteration could
run those renders inside the worker too, transporting the (serializable)
RDC so the worker can resume from the filled caches rather than reading
them back on the main thread.

closes NAR-895
2026-07-25 07:21:03 +02:00
Hendrik Liebau 62084e3fbd [test] Unflake the enabled-features-trace test suite (#96175)
<img width="428" height="179" alt="Screenshot 2026-07-24 at 22 42 04"
src="https://github.com/user-attachments/assets/38478940-d23e-4faa-bd88-5758190be158"
/>

[Flakiness
metrics](https://app.datadoghq.com/ci/test/runs?query=test_level%3Atest%20%40git.repository.id%3A%22github.com%2Fvercel%2Fnext.js%22%20%40test.name%3A%22enabled%20features%20in%20trace%20should%20denormalize%20inherited%20enabled%20features%20during%20upload%22%20%40test.type%3A%22nextjs%22%20%40test.status%3A%28%22fail%22%20OR%20pass%29&agg_m=count&agg_m_source=base&agg_t=count&fromUser=true&index=citest&start=1784320894753&end=1784925694753&paused=false)

The `render-path` span is recorded when a request's response closes,
which is too late for any flush other than the one the dev server
performs while shutting down. The parent `next dev` process escalates to
SIGKILL 100ms after signalling the child, and on a machine running eight
test files at once the child does not reliably get scheduled to run its
cleanup within that window, so the span never reached the trace file and
the upload assertions failed. This change raises the budget for the test
through `NEXT_EXIT_TIMEOUT_MS`, which was added alongside that timeout
in #67165 so that it can be increased when the child's exit work matters
more than a fast exit. The same approach is already used in
`test/e2e/filesystem-cache/warm-restart-task-stats.test.ts`, where the
timeout would otherwise cut off a Rust `on_exit` handler before it
writes its task statistics.

Both test cases previously guarded their request with a check for the
existence of the trace file, which the dev server creates on its own
once the first compile finishes. When that happened before the first
test body ran, neither case issued a request and the trace file
contained no `compile-path` or `render-path` span at all. The request
and the shutdown now happen once in `beforeAll`, and the fixed 500ms
sleep that followed the shutdown is replaced by a `retry` that waits for
the spans the assertions depend on.


---

<sub>Stack created with <a
href="https://github.com/github/gh-stack">GitHub Stacks CLI</a> • <a
href="https://gh.io/stacks-feedback">Give Feedback 💬</a></sub>
2026-07-25 07:21:02 +02:00
Hendrik Liebau 954eabec68 [refactor] Prepare dev validation for running on a worker thread (#96151)
This is a behavior-preserving refactor of the dev-mode Cache Components
validation, ahead of the change that moves it onto a worker thread.
In-process behavior is unchanged.

It narrows the context the validation functions require.
`runValidationInDev` and its helpers (`validateStaticShell`,
`warmupClientModulesForStagedValidation`, `validateStagedShell`,
`validateInstantConfigs`) now take a `ValidationRenderContext` (a `Pick`
of `AppRenderContext` plus the two `renderOpts` fields and the
debug-channel flag they actually read) instead of the full render
context, with `toValidationRenderContext` mapping the in-process and
build-time callers. The worker cannot reconstruct a full
`AppRenderContext` from a serialized snapshot, so narrowing the contract
to what validation genuinely consumes is what lets the same code run
there.

It also separates computing the validation errors from delivering them.
`runValidationInDev` now returns the errors instead of sending them to
the dev overlay inline, and the caller `runDevValidationInBackground`
delivers them via `logMessagesAndSendErrorsToBrowser`. The test-mode
lifecycle markers and delay move into `runWithDevValidationLogging`,
which brackets both the render and the delivery. When validation runs on
the worker, the worker computes the errors but the main thread still
delivers them (delivery needs the response object), so splitting the two
halves is a prerequisite.

Finally, it extracts the test-only validation lifecycle markers into a
shared `dev-validation-events` module. The `<VALIDATION_MESSAGE>`
wrapping and the event shape had been hand-rolled at each emission site
in `app-render.tsx` and duplicated again in the test harness; they now
live in one place as `formatValidationEvent` and an exported
`ValidationEvent` union that the in-process emitters call and the test
utilities import rather than redeclare. The worker emits the same
markers through the same helper once it is added, so a single definition
keeps every producer and the test parser in sync. Build validation's
marker `requestId`, previously the numeric `Date.now()`, is stringified
to fit the shared `string` type; it appears only in the logged marker
payload, which nothing matches on, so this too is unobservable.
2026-07-25 07:21:02 +02:00
Tim Neutkens 3a48dd6a6c Optimize app page entry analysis (#96058)
## Summary

- Extract the invariant app-page handler into a shared runtime module
while keeping each generated route entry as a small wrapper.
- Keep the `interopDefault` binding in the route wrapper because the
injected Turbopack metadata loader tree references it, and pass the same
binding into the shared runtime.
- Add a focused 14-route navigation benchmark with static metadata
coverage.

## Why

Turbopack gives every generated app-page entry a route-specific identity
because its loader tree, page name, pathname, and module hooks are
injected into the template. This caused the full app-page handler to be
analyzed independently for every route.

A clean native trace of the benchmark shows app-page analysis CPU
falling from 1,619.39 ms across 14 route copies to 153.92 ms total
across 14 small wrappers plus one shared runtime, a 90.5% reduction.
Concurrency-corrected analysis time falls from 181.63 ms to 97.87 ms.
The post-change fixture also includes a static icon to cover the
injected metadata loader binding.

## Verification

- `pnpm test-dev-turbo
test/e2e/app-dir/metadata-icons/metadata-icons.test.ts`
- `pnpm test-start-turbo
test/production/adapter-config/adapter-config-export.test.ts`
- `pnpm test-dev-turbo
test/development/app-dir/app-page-entry-prefetch-performance/app-page-entry-prefetch-performance.test.ts`
- `pnpm test-dev-webpack
test/development/app-dir/app-page-entry-prefetch-performance/app-page-entry-prefetch-performance.test.ts`
- Clean native reproduction: the metadata suite failed with
`ReferenceError: interopDefault is not defined` before the binding fix
and passed all 9 cases afterward.

<!-- NEXT_JS_LLM -->
2026-07-24 11:55:49 +02:00
Jiwon Choi 26824378a6 [ts-plugin] Preserve TypeScript quick info metadata (#95863)
### Why?

VS Code passes a maximum hover length and verbosity level to
TypeScript's `getQuickInfoAtPosition` API when a user expands a hover.
The Next.js TypeScript plugin intercepted that call and forwarded the
file name and position, dropping the remaining arguments. The plugin
also replaced TypeScript's `QuickInfo` object when it added config
documentation, which discarded native fields such as
`canIncreaseVerbosityLevel`. As a result, hovers for Next.js config
exports could not expand and lost native type information.

This supersedes #86290 with a narrower behavior change. The plugin
preserves native `QuickInfo` when it adds documentation to valid config
values and config identifiers. Invalid config values keep the existing
synthesized override, and hovers inside function initializers retain
TypeScript's existing behavior.

x-ref:
https://github.com/microsoft/vscode/issues/232685#issuecomment-3484167019

| Before | After |
|--------|-------|
| <img width="1656" height="390" alt="Config hover without expandable
metadata"
src="https://github.com/user-attachments/assets/ed39a851-7096-448b-bffe-55c5320c1713"
/> | <img width="1668" height="384" alt="Config hover with expandable
metadata"
src="https://github.com/user-attachments/assets/0a3a0cbc-5c75-4b6f-90fc-bbcef028d06b"
/> |

### How?

The plugin forwards all QuickInfo arguments to TypeScript and passes the
native result into the config hover rules. For valid config values and
identifiers, those rules copy the native `QuickInfo` fields and append
the Next.js documentation. They synthesize the existing fallback when
TypeScript returns no QuickInfo.

Focused tests cover argument forwarding, metadata preservation, missing
native QuickInfo, invalid config values, and function config boundaries.

### Test plan

Open a minimal Next.js app in Cursor with workspace TypeScript 6.0.2 and
the package built from this PR. Hover `export const metadata: Metadata`,
then select the expansion control. The hover expands from `Metadata` to
its full property type while the Next.js config documentation and link
remain visible.

<!-- NEXT_JS_LLM -->
2026-07-24 11:54:15 +02:00
dan b1713c8517 [RSC HMR] Fix a flurry of refetches when a editing component imported from many routes (#96102)
Fixes a flurry of requests when doing server HMR.

To reproduce the issue, create a server component imported from multiple
routes:

- Route `/a` imports that component
- Route `/b` imports that component
- Route `/c` imports that component
- (...and so on...)

Then edit that server component.

Expected: one request in terminal per open tab.
Actual: a flurry of requests.

There's two separate causes for the flurry of requests:

- The "server component updated" messages were emitted once per
already-compiled route, so a single tab would receive a flurry of
messages one right after another, and process them all.
- Also, the `debounce` for these messages is broken (it always fires
instead of delaying) due to a wrong condition inside it. So if we tried
coalescing messages by key, we'd still see multiple requests per tab.
- We should probably fix `debounce` but that's more risky because I
don't know if either existing caller could rely on the bug. Luckily, for
the fix below this doesn't matter.

Anyway, to get the desired behavior (one refresh per page), we switch to
expressing server component changes as a final message that gets sent at
the end of the update using a dirty flag. Then we don't need to worry
about how they get deduped or how many of them gets received.

There are probably other ways to factor this but this one seemed fine to
me.

## Before

A flurry of requests.


https://github.com/user-attachments/assets/337183e9-6fe2-4633-858d-6c1db6779618

## After

One request per open tab.


https://github.com/user-attachments/assets/3543619e-7ab3-4b74-bbdf-2151e16e2c2b

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-24 03:59:27 +01:00
Hendrik Liebau c3d5b92778 [test] Move the dev-only use cache test suite to test/development (#96023)
The `use-cache-dev` suite only covers dev-mode HMR behavior of `use
cache`, so it belongs under `test/development/app-dir` alongside the
other `cache-components-dev-*` suites rather than in `test/e2e`. Because
`test/development` runs only in dev mode and is never deployed, the
production-mode placeholder guard, the `skipDeployment` option, and the
`if (skipped)` early return are all dropped. The three test manifests
that referenced the old path are updated.


---

<sub>Stack created with <a
href="https://github.com/github/gh-stack">GitHub Stacks CLI</a> • <a
href="https://gh.io/stacks-feedback">Give Feedback 💬</a></sub>
2026-07-22 15:50:22 +02:00
dan 2549514abd [test] Add source-mapping coverage of React's fake stack frame scripts in use cache (#95945)
Adds tests verifying that`use cache` stacks get symbolicated.

We noticed insufficient coverage in
https://github.com/vercel/next.js/pull/95936, which should have failed
the tests (it broke symbolication) but tests passed. So this restores
the missing stack assertions. This also adds some new coverage that
specifically verifies that symbolicating via CDP also works.
2026-07-22 11:52:11 +01:00
Sam Poder 3ed60553da Revert "[turbopack] Don't SSR on pages only navigated to through a soft nav (#95539)" (#96028)
Causes issues with `use client` it seems.
2026-07-21 21:03:04 +00:00
Aurora Scharff 18d2e2da5f Insights: use a single Learn more link in console errors (#95967)
## Summary

The instant / blocking-prerender insight console errors printed a docs
URL under each fix option (two or three anchors per message). This
collapses them to a single `Learn more:` link at the end of each
message, while keeping the `[stream]`/`[cache]`/`[block]` fix-option
labels. It restores the single-link format these builders originally
shipped with.

**Before:**

```
Ways to fix this:
  - [stream] Provide a placeholder with `<Suspense fallback={...}>` around the data access
    https://nextjs.org/docs/messages/blocking-prerender-runtime#wrap-in-or-move-into-suspense
  - [block] Set `export const instant = false` to allow a blocking route
    https://nextjs.org/docs/messages/blocking-prerender-runtime#allow-blocking-route
```

**After:**

```
Ways to fix this:
  - [stream] Provide a placeholder with `<Suspense fallback={...}>` around the data access
  - [block] Set `export const instant = false` to allow a blocking route

Learn more: https://nextjs.org/docs/messages/blocking-prerender-runtime
```

Covers all 16 insight-kind errors, across `blocking-route-messages.ts`,
`sync-io-messages.ts`, `dynamic-rendering-utils.ts`, and
`instant-messages.ts`.

Because the dev overlay classified these errors by the `#`-anchored docs
URL, `getBlockingRouteErrorDetails` is updated to match the anchor-less
`Learn more:` URL. Console-message snapshots and the guidance-data
extraction test are updated to the new format. The dev-overlay fix-card
data (`instant-guidance-data.ts`) keeps its per-card links, since those
are the overlay UI rather than the console message.

## Verification

- `pnpm --filter=next types`
- Snapshots regenerated with `jest -u` for the affected suites

<!-- NEXT_JS_LLM -->
2026-07-21 12:53:17 +02:00
Tim Neutkens 81d0e8519d Fix instant validation blocking navigations (#95939)
### What?

- Run development instant validation only after the navigation response
has finished.
- Let a newer request supersede stale validation work and yield between
validation render attempts.
- Forward the cancellation signal into React validation prerenders so
asynchronous validation work can stop promptly.
- Add focused scheduler/response tests and assert that development
validation starts after the response finishes.

### Why?

Instant validation was detached from the response promise, but its
additional React renders still ran on the same Node.js event loop. A
subsequent navigation could therefore wait hundreds of milliseconds for
stale diagnostic work from the previous request.

Instant validation still runs when the server is idle. When another
navigation arrives, foreground request work now takes priority and the
obsolete validation is discarded.

### How?

Each Cache Components development request receives a validation
generation signal. Starting a newer request aborts the previous
generation. Validation waits for the Node response `finish` event,
yields through the event-loop poll phase between attempts, and combines
the generation signal with React's existing prerender abort signals.

Tests:

- `pnpm --filter=next build`
- `pnpm --filter=next types`
- `pnpm exec jest --runTestsByPath
packages/next/src/server/app-render/dev-validation-scheduler.test.ts
packages/next/src/server/app-render/wait-for-response.test.ts`
- `NEXT_SKIP_ISOLATE=1 NEXT_TEST_PREFER_OFFLINE=1 pnpm test-dev-turbo
test/e2e/app-dir/instant-validation/suspense-boundaries.test.ts -t
"valid - static prefetch - suspense around runtime and dynamic"`

<!-- NEXT_JS_LLM_PR -->
2026-07-21 09:53:43 +02:00
Sam Poder 20f5809320 [turbopack] Don't SSR on pages only navigated to through a soft nav (#95539)
This PR essentially implements:
https://github.com/vercel/next.js/pull/95539#discussion_r3555050929.
Will add more a description soon.
2026-07-17 13:10:54 -07:00
Andrew Clark 583ce21786 Back/forward set the Nav Inspector back to pending (#95865)
A back/forward (history traversal) navigation should not be captured by
the Nav Inspector, because it's not the same as a regular navigation: it
reads the entire navigation from cache, rather than fetching new data.
It might be the case that data in the bfcache is missing for some
reason, but in the normal case all the data will be available and the
navigation will be instant. So that's how we should model it in the Nav
Inspector.

In the future we may want to update the Nav Inspector UI to show that
the current screen was the result of a back/forward navigation, but for
now we will just reset the Nav Inspector back to the pending ("Waiting
for navigation...") state.

Includes the regression tests from #95793.

Co-authored-by: Dan Abramov <dan.abramov@gmail.com>
2026-07-17 11:44:05 -04:00
Andrew Clark 4395d7c807 Fix repeated navigations while the Instant Navigation lock is held (#95864)
With the Instant Navigation Testing lock held (the Navigation Inspector
paused), repeated client navigations between pages that share a layout
could sometimes get stuck without ever resolving.

The root cause turned out to be a subtle problem with the client
router's use of `useDeferredValue` to switch between the cached UI and
the final UI. The mechanism only works as we intend when mounting a new
part of the tree. When updating an existing part of the tree, like a
shared layout, React will skip the cached UI entirely.

We were already aware of this issue with the `useDeferredValue`
approach. The plan is to instead model cached navigation as an
optimistic update followed by a transition update. Before we can make
such a change, though, we need to make a change in React to allow
optimistic updates to suspend (currently, optimistic updates act like
sync updates, and can trigger existing Suspense boundaries to switch
back to their fallback state).

To workaround this issue when using the Navigation Inspector, we're
going to change how the inspector works when multiple navigations are
performed during the same "lock". Instead of keeping the entire sequence
blocked until the end, we will instead only block the most recent one.
This more closely matches what the inspector UI implies, anyway: it
doesn't show a stack of pending navigations, only the most recent one.

This doesn't absolve us from needing to switch to using an optimistic
update. That's needed regardless to address similar behavior in
production. But this sidesteps the issue for now, and we were planning
to make this change to the Navigation Inspector regardless.

Co-authored-by: Sam Selikoff <sam.selikoff@gmail.com>
2026-07-16 20:19:57 -04:00
Tim Neutkens 491f78099c Fix Request Insights span collection (#95818)
## Summary

Fix Request Insights collection so the dev server records all non-hidden
Next.js spans locally, including non-vanilla trace and wrapped spans,
while preserving the existing OpenTelemetry export allowlist unless
verbose tracing is enabled.

Keep reporting timelines correct by propagating request identity before
work async storage exists, honoring explicit span timestamps, and
anchoring request duration to the BaseServer request span. Standardize
span categories on `next.span_category`, retain presentation-only UI
filtering, and filter internal request identity headers at ingress.

## Verification

- `pnpm test-dev-turbo
test/development/app-dir/request-insights/request-insights.test.ts`

<!-- NEXT_JS_LLM -->
2026-07-16 14:24:43 +02:00
Andrew Imm ca414736ba telemetry: add agentName to anonymous metadata (#95586)
Add agentName to anonymous telemetry metadata

### What

Adds an agentName: string | null field to the anonymous telemetry
metadata block that accompanies every payload, alongside existing fields
like ciName, nextVersion, and isCI. The value is the detected AI coding
agent driving the process (e.g. claude, cursor, codex), or null when
none is detected.

### Why

We currently have no visibility into how much Next.js development
happens under AI coding agents. This field lets us understand that split
in the same anonymous way we already track other CLI usage.

### How

Agent detection via @vercel/detect-agent
- Vendored @vercel/detect-agent (from the Vercel CLI) into
`next/dist/compiled/@vercel/detect-agent` (new taskfile ncc task,
externals entry, and type declaration), matching how I *understand*
other third-party deps are bundled.
- The vendored detection method is async (it does a filesystem probe for
Devin in addition to env-var checks) and potentially slower than we'd
want for logging, so it's wrapped in a memoized helper:
`telemetry/agent-name.ts`. `getAgentName()` caches the promise from
`determineAgent()`: the first call runs detection once, concurrent
callers share that in-flight promise, and every later call resolves
against the already-settled result. The agent shouldn't change over the
process lifetime, so this is safe. Detection failures resolve to null so
telemetry never throws on this path.

Wiring into the payload
- `getAnonymousMeta()` is now async and includes `agentName: await
getAgentName()`. `storage.ts` awaits it on the send path (the record
method was thankfully already async).
- The NEXT_TELEMETRY_DEBUG=1 output now includes the meta block
alongside each event, so the full payload — including agentName — is
observable locally without sending anything. We didn't output this
before, so it was harder to debug these fields.

Consolidating agent detection
- There was a second, synchronous, hand-vendored copy of agent detection
(telemetry/detect-agent.ts) used only by the AGENTS.md/CLAUDE.md
auto-generation hook. It's been removed and that hook
(ensureAgentRulesForDev) now uses the same vendored `getAgentName()`.
Now we have a single source of truth and can easily get upstream changes
from the vercel CLI package. `ensureAgentRulesForDev` became async, its
only caller in start-server.ts was already in an async scope, so this is
easy.

### Testing

- NEXT_TELEMETRY_DEBUG=1 next build shows agentName in the payload meta
block next to ciName/nextVersion.
- Verified the memoized helper: agent present -> name; cleared env ->
null; repeated calls return the same promise instantly.
- agent-rules-auto-generate dev tests pass (agent-detected generates
files; cleared-env does not)
- next-server-nft trace snapshot updated: adds @vercel/detect-agent +
agent-name.js, removes detect-agent.js.
- ts, build, existing telemetry unit test, lint
2026-07-14 16:56:01 -07:00
Jimmy Lai 4a4590a37c request insights: add agent diagnosis access (4/5) (#93977)
## What

Adds `next experimental-diagnose` and an MCP `get_request_insights` tool
so agents and local users can inspect Request Insights without opening
the DevTools overlay.

Stack:

1. request insights: record local framework spans (1/5)
2. request insights: derive request history and fetch data (2/5)
3. request insights: expose dev snapshots to tools and HMR (3/5)
4. request insights: add agent diagnosis access (4/5) ← current
5. request insights: add DevTools request panel (5/5)

## Review Focus

- CLI error handling and raw JSON output.
- MCP gating when Request Insights is unavailable.
- Telemetry registration for the new MCP tool name.

## Proof In This PR

- `pnpm jest packages/next/src/telemetry/events/mcp-telemetry.test.ts
--runInBand`
- `pnpm --filter=next types`
- Stack-tip smoke verifies `next experimental-diagnose --url
http://localhost:3003 --json` against a real dev server.

## Deferred Coverage

- A more curated agent summary format is intentionally deferred until
the raw private snapshot contract settles.

<!-- NEXT_JS_LLM_PR -->

---------

Co-authored-by: Tim Neutkens <tim@timneutkens.nl>
2026-07-13 14:01:53 +02:00
Will Binns-Smith 8b7b6fea86 Turbopack tests: remove assertions that duplicate webpack results (#95688)
We forked test assertions in places in the early days of merging
Turbopack. Now that we more closely align, a bunch of these had the
exact same assertions. Fold them into one.
2026-07-10 12:11:26 -07:00
Janka Uryga 46681d90f0 [PPF] Sync IO is only allowed in the dynamic stage (#95384)
This PR makes Sync IO behavior more restrictive when
`partialPrefetching` is on (either globally or for a page).

- Previously, we allowed `await cookies(); Date.now()` in in segments
that won't ever be runtime prefetched. This was achieved by separating
segments into "early" and "late" stages and only erroring in the "early"
ones.
- After this PR, it will always be an error to do sync IO anywhere other
than after `io()`/`connection()`/uncached IO (i.e. outside the Dynamic
stage).

We're doing this mainly because with `partialPrefetching`, `cookies()`
resolves in App Shells, and sync IO causes a severe deopt in any
prerender. `allow-runtime` also opts a route into `partialPrefetching`,
which means that even the segments above the runtime prefetch boundary
will need an App Shell and can't tolerate Sync IO.

This PR removes all early/late stage separation, because we no longer
need to vary the Sync IO behavior on the segment. Instead, we now pick
whether a stageController should use
- `SyncIOMode.AllowedInRuntimeOrDynamic` (legacy)
- `SyncIOMode.AllowedInDynamic` (`partialPrefetching`)
- `SyncIOMode.Untracked` if it needs to opt out

I've removed the existing tests that asserted that sync IO is allowed in
segments above the `allow-runtime` boundary. Instead, we check that sync
io either errors (if partialPrefetching is on) or is allowed (if
partialPrefetching is off)

Closes NAR-855
2026-07-10 15:36:41 +00:00
Jude Gao 86ff18a4be Refresh outdated agent-rules blocks on next dev and codemod upgrade (#95470)
Once the managed agent-rules block is installed, `next dev` never
touches it again — `ensureAgentRulesForDev` gates on marker presence —
and `@next/codemod upgrade` doesn't refresh it either, so changes to the
block's content (like the wording fix in #95467) never reach existing
projects.

`next dev` now gates on `hasCurrentAgentRules` and re-upserts a block
whose content differs from canonical, targeting whichever file already
hosts it so an upgrade rewrites the block in place instead of
duplicating it into a second file; legacy `NEXT-AGENTS-MD` blocks
migrate the same way, and CRLF files keep their line endings.
`@next/codemod upgrade` refreshes the block after the version bump by
delegating to the installed package's own generator
(`next/dist/server/lib/generate-agent-files`), so the codemod never
carries a copy of the wording and no-ops below 16.3 or when the project
never adopted the block. A malformed block (start marker without end)
converges to a single canonical block within two runs rather than
growing unboundedly.

The e2e test now covers the in-place upgrade (surrounding content
preserved, exactly one block, no `CLAUDE.md` created when `AGENTS.md`
hosts it) and idempotence across dev-server restarts, using the real
generator's output rather than hardcoded wording.
2026-07-08 22:58:49 -04:00
Benjamin Woodruff a87692e17c [ci] Split up large rsc-build-errors development test (#95624)
See https://github.com/vercel/next.js/pull/95553 for an explanation of
why we want to split these up. TL;DR it makes CI faster and saves us
some money.

In CI we have to run tests at the suite/file granularity, so if a test
suite is too long it limits parallelism and if it has to be retried, we
have to retry the full suite.
2026-07-08 19:56:27 -07:00
Benjamin Woodruff 337a40c672 [ci] Pin typescript version in tests (#95619)
Typescript 7 came out, which is breaking a lot of our e2e tests.
2026-07-08 14:12:48 -07:00
Sam Poder bb9af37519 [turbopack] Rename rscEndpoint to rscHmrEndpoint (#95538)
I'm introducing a new endpoint that doesn't compile SSR when doing a
page navigation, I'd like to call it a `rscEndpoint` but currently this
name is taken. `rscEndpoint` appears to be used for detecting changes
for HMR so I've renamed it to `rscHmrEndpoint` so I can use that name
for the new `rscEndpoint`.
2026-07-07 18:19:40 -07:00
Benjamin Woodruff c3dd28fd6a [ci] Split up large cache-components-dev-warmup test suite (#95553)
This splits two of our slowest test suites into smaller pieces that can
be run in parallel and retried in isolation.

## Context

Jest treats test suites as the smallest test execution unit.

Slow test suites can lead to situations where a test shard is using very
little CPU waiting for one last test suite to finish, and when tests
fail and have to be retried, we have to retry the entire suite.

Here's a particularly bad example:

<img width="3116" height="2634" alt="Screenshot 2026-06-08 at 20-00-05
CI Telemetry — turbopack production tests (1)"
src="https://github.com/user-attachments/assets/b5534200-4b77-40d5-a219-4b6fc552ed77"
/>

I instructed Claude to fetch test timing information from GitHub Actions
Artifact, and to report the slowest test suites.

> How timings work today
>
> - The `fetch-test-timings` job runs `node run-tests.js --timings
--write-timings`, which pulls per-file durations from a Vercel KV store
and uploads them as the test-timings artifact (test-timings.json, 5-day
retention).
> - Each sharded job downloads that artifact and run-tests.js -g N/M
greedily bin-packs whole test files into groups by predicted duration
(run-tests.js:528-549).
> - I pulled the artifact from the July 7 canary run (28843797348): 1684
test files, ~61,500s total, median 20s, but max 1023s.
>
> Since the file is the unit of scheduling, a shard can never finish
faster than its largest file. The job durations from that run confirm
the imbalance this causes: test dev 4/10 took 28 min while siblings took
10–17; test cache components dev 6/6 took 13 min vs 5–6 for siblings;
test turbopack production 5/7 took 20 min vs 10–11.

> The slowest suites

```
┌────────────────────┬────────────────────────────────────────────────────────────────────────────┐
│        Time        │                                   Suite                                    │
├────────────────────┼────────────────────────────────────────────────────────────────────────────┤
│ 1023s + 933s       │ test/development/app-dir/cache-components-dev-warmup/ (both variants)      │
├────────────────────┼────────────────────────────────────────────────────────────────────────────┤
│ 942s               │ test/production/create-next-app/templates/matrix.test.ts                   │
├────────────────────┼────────────────────────────────────────────────────────────────────────────┤
│ 788s + 432s + 232s │ test/e2e/app-dir/cache-components-errors/cache-components-errors.*.test.ts │
├────────────────────┼────────────────────────────────────────────────────────────────────────────┤
│ 779s               │ test/development/acceptance-app/rsc-build-errors.test.ts                   │
├────────────────────┼────────────────────────────────────────────────────────────────────────────┤
│ 470s               │ test/e2e/opentelemetry/instrumentation/opentelemetry.test.ts               │
├────────────────────┼────────────────────────────────────────────────────────────────────────────┤
│ 457s               │ test/e2e/app-dir/actions/app-action-node-middleware.test.ts                │
├────────────────────┼────────────────────────────────────────────────────────────────────────────┤
│ 451s               │ test/e2e/app-dir/next-after-app/index.test.ts                              │
├────────────────────┼────────────────────────────────────────────────────────────────────────────┤
│ 447s               │ test/e2e/app-dir/css-order/css-order.test.ts                               │
├────────────────────┼────────────────────────────────────────────────────────────────────────────┤
│ 426s               │ test/e2e/telemetry/config.test.ts                                          │
├────────────────────┼────────────────────────────────────────────────────────────────────────────┤
│ 402s               │ test/production/debug-build-path/debug-build-paths.test.ts                 │
└────────────────────┴────────────────────────────────────────────────────────────────────────────┘
```

> Reading these files, almost every one is slow for the same structural
reason: a `describe.each(...)` over fixtures/modes/runtimes where each
iteration boots its own `nextTestSetup` (dev server or full production
build), run serially within one Jest worker:

> - **cache-components-dev-warmup:** `describe.each` over 2 fixture
dirs, and it restarts the dev server (stop/clean/start) before every one
of ~17 tests. It's slow by design (cache-warmup isolation), so per-test
cost can't shrink — but the file can be split.
> - **create-next-app matrix:** `describe.each(app|pages)` × `it.each`
over a ~16–48-combination flag matrix, each doing a full CNA scaffold (+
dev-server cycle for pages), fully serial.
> - cache-components-errors.test.ts: 7490 lines, 129 tests, with
per-describe `next build --experimental-build-mode compile` and per-path
generate builds in prod mode.
> - **css-order:** three sibling `describe.each` blocks over 4–6
chunking modes, each mode booting its own server, ~3 blocks × modes ×
page-pair orderings.
> - **next-after-app, opentelemetry:** `describe.each` over
runtimes/configs, again one server per iteration.
2026-07-07 14:51:23 -07:00
Hendrik Liebau 60ddf0966a Cancel a superseded Server Components HMR refresh's server-side work (#95486)
When rapid edits overlap Server Components HMR refreshes in `next dev`,
only the newest refresh can commit. The client already aborts a
superseded refresh's fetch, which closes its response. We use that
response-close to stop the server work the superseded refresh started,
so the dev server no longer runs a render (and, under Cache Components,
a validation) whose result is discarded.

When the `serverComponentsHmrCancellation` flag is enabled and the
request is an HMR refresh over a Node response, we derive an abort
signal from `signalFromNodeResponse(ctx.res.originalResponse)` and
thread it into the Flight render. This covers both dev RSC render paths:
the Cache Components staged render in
`generateDynamicFlightRenderResultWithStagesInDev` and the
non-Cache-Components render in `generateDynamicFlightRenderResult`.

The render is aborted through `renderToNodeFlightStream`, which calls
`abort()` on the pipeable that `renderToPipeableStream` returns when the
signal fires. React's Node Flight API has no `signal` option, unlike the
Web `renderToReadableStream`, aborting the returned pipeable is the
intended way to stop the render. This also removes the incorrect
`signal` field from the `renderToPipeableStream` type declaration and
derives `FlightRenderOptions` from `renderToReadableStream`'s options,
so the render wrappers are no longer typed as `any`.

Under Cache Components the superseded refresh additionally skips its
detached validation, which only runs on the staged render, so it never
validates a discarded tree, and any error thrown while its background
renders tear down is swallowed. The aborted Flight renders surface as
abort errors that `createReactServerErrorHandler` already ignores, so
nothing reaches the error overlay or the CLI.

The `hmr-rsc-cancellation` suite exercises both render paths, with Cache
Components controlled by the `__NEXT_CACHE_COMPONENTS` test shard rather
than the fixture config. A child component logs when React renders it,
and the test asserts the superseded refresh never logs, since its render
is aborted before reaching the child, while the committed refresh does.
The detached-validation assertions run only when Cache Components is
enabled.
2026-07-07 21:53:00 +02:00
Benjamin Woodruff 727a3ffa86 [ci] Skip apt-installed dependencies in playwright install step (#95535)
The hypothesis is that this is just installing some font stuff that
isn't actually needed for Chromium to work:

```
The following additional packages will be installed:
  libnss3-tools xfonts-encodings xfonts-utils
Recommended packages:
  fonts-ipafont-mincho fonts-liberation-sans-narrow fonts-tlwg-loma
The following NEW packages will be installed:
  fonts-freefont-ttf fonts-ipafont-gothic fonts-liberation fonts-tlwg-loma-otf
  fonts-unifont fonts-wqy-zenhei xfonts-cyrillic xfonts-encodings
  xfonts-scalable xfonts-utils
The following packages will be upgraded:
  libnss3 libnss3-tools
2 upgraded, 10 newly installed, 0 to remove and 35 not upgraded.
```

`apt` is very slow, so skipping this step should significantly reduce
setup time per test shard.

It seems like this isn't always true for other browsers, so only skip
this for Chromium (that's fine, that's what most of our jobs use
anyways).

On 16 core arm runners, "Install Playwright Browsers" typically took
18-24 seconds. Now it takes 5-6s.

Let's estimate that saves about 15s per Linux job that installs
Chromium. Claude estimates that there are 73 chromium-only Linux jobs on
this PR:

<img width="891" height="148" alt="Screenshot 2026-07-06 at 10 30 16 PM"
src="https://github.com/user-attachments/assets/725893fa-9f6f-4806-a3b4-9858ab0ebe66"
/>

So that's 15*73/60=18.25 minutes saved per PR push (the average PR gets
many pushes). At the public (before any volume discounts) price of
$0.026/minute for the 16 core arm runners, that comes out to $0.4745 per
PR push saved.

This workflow has had 3,894 runs in the last 30 days, so that could be
$1,847/mo saved, but many of those workflow runs were canceled, and some
likely failed before getting to these jobs, so the actual savings are
probably somewhere around half of that -- scroll through
https://github.com/vercel/next.js/actions/workflows/.github/workflows/build_and_test.yml
and see that a little less than half of the workflow runs end up
canceled.
2026-07-07 09:48:46 +02:00
Hendrik Liebau 5f57774334 Abort superseded Server Components HMR requests on the client (#95463)
In `next dev`, rapid edits can start overlapping Server Components HMR
refreshes, but only the newest can commit. When a newer refresh
supersedes an older one, we abort the older request on the client so it
stops transferring and decoding RSC data, and we keep the browser from
reissuing the superseding request as a duplicate. Everything here is
gated on the `serverComponentsHmrCancellation` flag.

On the client, the router's `hmrRefresh` method tracks the newest
refresh generation in an `AbortController` and aborts the previous one
before scheduling the new one, threading the abort signal through the
HMR action down to the `fetch` call. An aborted request becomes a
`NavigationTaskExitStatus.Canceled` rather than a failure, so
`finishNavigationTask` leaves its cache nodes for the newer navigation
to fulfill and does not retry or fall back to an MPA navigation.
`hmrRefreshReducer` also bails out when its generation was aborted
before it ran, which happens when an HMR action waits behind a Server
Action in the router queue.

The subtle part is how an aborted request's partially received response
is handled. A superseded refresh can already have committed part of its
tree with a Suspense boundary still streaming. Rather than letting the
aborted fetch reject the still-pending rows, which a committed boundary
would throw on, we decode the response through a wrapper stream that we
close, so React marks the unresolved rows as halted: they suspend during
render instead of rejecting, and the superseded boundary keeps its
fallback until the newer response commits. Closing the stream also
avoids the unclosed-stream memory leak that #89610 fixed for prefetch
streams.

Aborting the superseded fetch exposed a Chromium-only side effect. In
development, App Router responses are served with `Cache-Control:
no-cache, must-revalidate` so the browser can restore them on back and
forward navigation, which keeps each response stored and keyed by URL.
Successive refreshes of the same page share one cache entry, and
aborting the superseded refresh mid-write leaves that entry
half-written; Chromium discards it and reissues another superseding
refresh on a second connection. The dev server then renders it twice and
emits its debug channel twice under the same request id, producing
`Cannot write to a CLOSED writable stream` errors on the client. Firefox
and Safari do not reissue. To prevent this we serve HMR refresh
responses, identified by the `next-hmr-refresh` request header, with
`Cache-Control: no-store`, so no shared entry exists for an aborted
refresh to leave half-written. All other dev responses keep `no-cache`,
so their restore behavior is unchanged.

A development test suite covers a superseded request being aborted while
the newest commits without a hard reload, a partially committed render
whose Suspense boundary is left streaming not surfacing an error when
superseded, and disabling the flag preserving the previous behavior. The
supersession tests also assert no browser console errors, which guards
against the duplicate debug channel regression.

Cancelling the superseded request's server-side render and validation,
so the dev server also stops the discarded work, is left to a follow-up.
2026-07-06 21:15:39 +02:00
Sebastian "Sebbie" Silbermann f656c597c6 [fragment-scroll] Enable new scroll handler by default (#95378) 2026-07-06 18:39:07 +00:00
Hendrik Liebau 4d9e955c01 [test] Unflake cache-components-dev-streaming test suite (#95466)
[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.type%3A%22nextjs%22%20%40test.status%3A%22fail%22%20%40test.suite%3Acache-components-dev-streaming&agg_m=count&agg_m_source=base&agg_t=count&fromUser=false&index=citest&start=1782496431626&end=1783101231626&paused=false)

<img width="425" height="170" alt="Screenshot 2026-07-03 at 19 55 48"
src="https://github.com/user-attachments/assets/7751d204-1ceb-4cc0-b90e-8f8f70adb803"
/>

The streaming assertions in this suite used `elementByCssInstant`, which
runs `page.waitForSelector` with a 10ms timeout. That budget is below
the floor of a Playwright selector query under CI load: the call is a
cross-process round-trip to the browser, and in a streaming render the
page's main thread is busy processing chunks, so the injected visibility
poll frequently cannot complete within 10ms even when the element is
already present and visible. Playwright then rejects with a timeout, and
the surrounding `retry()` only hides this behind a coarse 500ms poll,
leaving the tests racy. We have observed the instant selector throwing
immediately after a prior assertion had already confirmed the same
element was present.

This change replaces those `elementByCssInstant` and `retry` blocks with
`elementByCss(selector, { waitUntil: false })`. That gives the selector
query Playwright's default 5s budget and its efficient built-in waiting,
while still skipping the page `load` event, which (with `waitUntil:
'commit'`) only fires once the slowest cache has filled. The `retry()`
wrappers become redundant because every target here is a distinct id
from its fallback (for example `#dynamic` versus `#dynamic-fallback`),
and the Suspense boundary reveals the content subtree in a single
commit, so the moment the element exists its text is already final.

The `#cached` assertion keeps an explicit `timeout: 10000` because its
cache fill takes about 5s, which would race the default 5s selector
timeout. The `retry()` around the bare `<p>` in the `use-cache` test is
left in place: there the fallback and the content are both an id-less
`<p>`, so the same element's text changes from `Loading...` to a date,
and only a retry can observe that transition.
2026-07-06 12:27:49 +02:00
Hendrik Liebau dd8b154d9f Ignore-list internal frames whose source maps chain to original sources (#95448)
Next's dev error output (terminal logs and the browser overlay)
ignore-lists framework-internal stack frames. The forked
`EvalSourceMapDevToolPlugin` recomputes this ignore-list per module
because the vendored `webpack-sources` has no runtime `ignoreList`
support, so it drops the field when it combines a module's input source
map with the generated mappings. `shouldIgnorePath` matches on the
emitted source path, which works while a source resolves back to its
module. But a Next-internal module that ships an input source map (its
`.js.map`, which taskfile-swc marks entirely ignore-listed) resolves its
sources to the original files such as `src/server/web/adapter.ts`, which
no longer contain the `node_modules`/`next/dist` marker and no longer
resolve back to a module. Those frames then escape ignore-listing and
leak into both the terminal output and the overlay.

For such unresolved sources this falls back to the module's own resource
path, so a frame whose module lives in `node_modules`/`next/dist` stays
ignore-listed even when its source map points at the original source.
The fallback applies only to a `NormalModule`, whose sources all belong
to that one module; a `ConcatenatedModule` merges several modules'
sources under one map and is left to the path check, which the dev
`eval-source-map` devtool never produces anyway.
2026-07-03 14:27:48 +02:00
Hendrik Liebau f83bc252fc Type resolved cacheLife profiles, dropping runtime asserts (#95428)
Runtime `"use cache"` code validated the default cache-life profile on
every invocation because `cacheLifeProfiles` was typed as optional with
partial profiles: `assertDefaultCacheLife` on the generate and RDC-read
paths, a per-`cacheLife()` presence `InvariantError`, and optional
chaining on reads.

Config normalization already guarantees this once:
`assignDefaultsAndValidate` builds the config from `{...defaultConfig,
...config}`, so `cacheLife` is always present, and it backfills the
`default` profile's `stale`, `revalidate`, and `expire`. This moves that
guarantee into the type. A new `ResolvedCacheLifeProfiles` (in
`config-shared`) types the `default` profile as `Required<CacheLife>`
and overrides `NextConfigComplete.cacheLife`, and the type is threaded,
non-optional, through the render options, work store, and the build,
export, and dev workers. Runtime code now reads
`cacheLifeProfiles.default` directly, and the asserts, the presence
guard, and the optional chaining are gone.

The proxy (middleware) work store is the one construction site without a
resolved profile: the proxy does not support `"use cache"`, so it never
reads `cacheLife`. It is given a sentinel whose `default` getter throws
if ever read, matching the "never read" sentinels already used for its
other unused render options.
2026-07-03 14:27:47 +02:00
Hendrik Liebau 2850659b74 Cache short-expire 'use cache' values across dev reloads (#95362)
Development has recently gained several mechanisms that make `'use
cache'` reloads fast under Cache Components: `'use cache: private'`
entries are persisted in a dedicated in-memory handler,
`cacheMaxMemorySize: 0` uses a real in-memory handler instead of the
no-op stub, and custom handlers are fronted by a fast built-in handler
through the tiered handler. One case was still missing. A value that
opts into a dynamic, client-only life with an explicit short `expire`
(for example `cacheLife({ expire: 0 })`, or the built-in `'seconds'`
profile) was treated as a miss on every reload, for both the built-in
default handler and custom handlers, so reloads re-ran the cache
function and streamed slowly.

The reason is that `expire` is the value's expiration bound, the longest
it may still be served before it has to be treated as expired. That is
its purpose in both dev and production; what differs is which threshold
the built-in in-memory handler enforces. In `next dev` it serves stale
entries up to `expire` to keep reloads fast, whereas in production it
drops them earlier, once past `revalidate`. An `expire` of zero
therefore leaves the dev handler no window in which a reload can be
served from the cache, and the wrapper's serve-vs-regenerate check,
which also keys on `expire`, regenerates instead.

This change extends the same dev-only treatment to those values without
altering their resolved cache life. The built-in default handler now
retains an entry for at least `MIN_PRERENDERABLE_EXPIRE` in dev, a
minimum the custom front handler inherits by being a built-in default
handler, and the wrapper applies the same minimum when deciding whether
to serve or regenerate. That affects the retain and serve decisions
only; the entry keeps its real `expire`, so the staged dev render still
resolves it at the appropriate stage rather than in the shell stage. A
short-`expire` entry is also re-warmed in the background on every
dynamic request render, so a reload serves the previously cached value
immediately and the freshly recomputed one appears on the next reload.
This is the same stale-while-revalidate trade-off already accepted for
the private-cache and `cacheMaxMemorySize: 0` dev optimizations, which
likewise favor a fast reload over serving a value these configurations
would not otherwise cache at all. For custom handlers the re-warm
re-executes the function and writes through to the backing.

Unlike the private-cache case, we deliberately do not force a dynamic
cache life here, because forcing `revalidate: 0` would leak into the
cache life propagated to an enclosing `'use cache'` and trip the
nested-dynamic error with the wrong message. And unlike the size-0 case,
keeping the resolved life alone is not enough, because a short `expire`
is exactly what makes the dev handler drop the entry, which is why the
minimum retention is needed. Because the dev front handler now enforces
that minimum, the tiered handler can no longer evict a stale front entry
by writing `expire: 0` (the minimum would keep it alive), so
`toExpiredEntry` now writes a negative `expire`, which the default
handler recognizes as an eviction sentinel and reports as missing
regardless of the retention minimum. This mirrors the existing
`revalidate = -1` convention, though a negative `expire` means the entry
is dropped rather than served-but-revalidated.

Everything is gated on `process.env.__NEXT_DEV_SERVER`, so production
behaves exactly as before: short-`expire` values keep their real cache
life, and configured handlers are used directly. New development tests
cover the built-in and custom-handler cases, asserting that a cache-miss
navigation shows the Suspense fallback while a cache-hit one does not,
and that a reload serves the cached value yet converges to a fresh one.


---

<sub>Stack created with <a
href="https://github.com/github/gh-stack">GitHub Stacks CLI</a> • <a
href="https://gh.io/stacks-feedback">Give Feedback 💬</a></sub>
2026-07-02 11:26:08 +02:00
Aurora Scharff 41114a31b8 Remove 'silence this warning' from instant validation fix output (#95187)
## What

Removes the `silence this warning` phrasing from the structured fix
lines in instant validation output:

- `- [block] Set \`export const instant = false\` to ~~silence this
warning and~~ allow a blocking route`
- `- [ignore] Set \`export const instant = false\` to ~~silence this
warning and~~ opt the route out of instant-navigation validation`
- `- [ignore] Set \`export const instant = false\` to ~~silence this
warning and~~ opt the dropped segment out of instant-navigation
validation`

## Why

The line called itself a warning while being logged via `Error:`. This
PR originally renamed it to `silence this error`, but that has the
mirror problem: at validation level `warning` or `manual-warning` (or
when the check only surfaces in dev) nothing blocks, so "error"
over-claims.

The verb is also wrong either way. Setting `instant = false` doesn't
silence a problem that still exists. It declares that blocking is
acceptable for the route, so validation stops treating it as one.
Removing the clause leaves wording that is correct at every validation
level and matches the dev overlay cards ("Allow blocking route",
"Disable validation on this route") and the docs sections the lines link
to.

## How

- `blocking-route-messages.ts`, `dynamic-rendering-utils.ts`: drop
`silence this warning and` from the `[block]` lines
- `instant-messages.ts`: drop it from the `[ignore]` lines (unrendered
segment, link prefetch)
- `errors.json`: regenerated, append-only (codes 1394-1405)
- Storybook fixture and 15 test files updated to the new strings

<!-- NEXT_JS_LLM_PR -->
2026-07-01 20:42:56 -04:00
Sam Selikoff d6f4cd33a1 Fix Navigation Inspector in Safari (#95329)
Fixes the Navigation Inspector in Safari/WebKit on localhost.

Safari 18.6 exposes the Cookie Store API, but WebKit on localhost can
resolve `cookieStore.set()` and emit a change event without committing a
cookie that is visible to `document.cookie` or sent with requests. Since
the instant navigation lock depends on a real request cookie, the
inspector was not working correctly.

This switches instant-nav cookie writes/deletes to `document.cookie`,
keeps Cookie Store for observing changes, and makes Resume wait for the
delete event before starting the next pending capture.
2026-07-01 14:34:22 +00:00