cloudflare / cloudflare/vinext
Optimistic routing / segment cache: bail out on rewrite-shifted and conflicting-dynamic-sibling prefetches (fix infinite prefetch loops)
- Dominant language
- TypeScript
- Stars
- 8.8k
- Forks
- 406
- Avg merge
- 2d 6h
- Merged PRs (30d)
- 120
Description
## Upstream change
Next.js [5942b37](https://github.com/vercel/next.js/commit/5942b37a42abdcbc7e0f28a087cf41d04ecf08c6) — "Fix: Optimistic routing bugs leading to repeated prefetch loops" (#97128). Fixes #97135.
## The bugs
Two root causes in the client segment-cache optimistic routing (`packages/next/src/client/components/segment-cache/`):
1. **Prefix-rewrite prefetch loop.** A proxy that rewrites every URL to inject a leading path segment (e.g. i18n with default locale hidden: `/one/two` → `/en/one/two`) combined with a fully dynamic target route like `/[locale]/[...pages]` causes an infinite prefetch loop that never resolves. When receiving a prefetch response, Next.js did not verify the response matched the expected result; a tree mismatch made the prefetch task repeatedly retry to fulfill missing data.
2. **Conflicting dynamic parallel-route siblings.** Parallel routes with conflicting dynamic params at the same level (`@modal/[...catchAll]` next to `[username]`) can't be distinguished by the traversal algorithm, which assumes each segment resolves independently without inspecting siblings/children. This also triggers a loop.
## The fix
- Record on the local route definition that a **dynamic rewrite** occurred, disabling further optimistic resolution attempts for that route — the same strategy already used for normal navigation responses, now applied to the prefetch path (`handleMismatchDueToRewrite`).
- Compare the param's cache key (parsed from the rendered pathname) against the URL parts the segment would consume, per param type: `d` (single dynamic) canonicalizes the URL part and compares; `c`/`oc` (catch-all/optional catch-all) joins remaining parts with `/` and compares; interception params are skipped. On mismatch, bail to server resolution.
- Add `hasConflictingDynamicChildren` to the known-route-part trie: when parallel branches disagree about the dynamic segment (different param name/type) at a level, mark it conflicted so discovery stops storing patterns beneath it and matching bails to server resolution.
- New `canonicalizeURLPart` helper in `route-params.ts`.
## Why it's relevant to vinext
vinext reimplements App Router client-side navigation, prefetching, and the segment cache. Combined with vinext's middleware/rewrite handling (prefix rewrites for i18n) and parallel routes, the same infinite prefetch loops can occur. This is a high-impact correctness/perf bug (request waterfall / loop that never resolves), originally reported as a next-intl prefetch waterfall.
## Action
- Audit vinext's optimistic routing / segment-cache prediction for rewrite-affected responses (prefix-injecting rewrites) and add a mismatch bail-out that disables optimistic resolution for the affected route.
- Add cache-key vs URL-part comparison per dynamic param type (single, catch-all, optional catch-all) before predicting a route.
- Handle conflicting dynamic parallel-route siblings by bailing out to server resolution.
- Port the two e2e fixtures: `proxy-prefix-rewrite-prefetch-loop` and `modal-catchall-sibling-dynamic-prefetch-loop`, plus the added `optimistic-routing` cases.
## References
- Commit: https://github.com/vercel/next.js/commit/5942b37a42abdcbc7e0f28a087cf41d04ecf08c6
- PR: https://github.com/vercel/next.js/pull/97128
- Issue: https://github.com/vercel/next.js/issues/97135
Contributor guide
Research direction
Start with vinext’s optimistic routing and segment-cache implementation, using packages/next/src/client/components/segment-cache/ and upstream commit 5942b37 as references. Trace rewrite mismatch handling, dynamic cache-key comparisons, and conflicting dynamic sibling detection, then run or port the proxy-prefix-rewrite-prefetch-loop, modal-catchall-sibling-dynamic-prefetch-loop, and optimistic-routing cases. Done means these scenarios bail out to server resolution without repeated prefetch loops.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- next.js, typescript
- Domain
- frontend, performance, testing
- Issue type
- Bug
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 35/100