next.js
75548c9a - [backport] Fix Nav Inspector request loop on repeat captures (#97326)

Commit
54 days ago
[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>
Author
Parents
Loading