cloudflare / cloudflare/vinext

Optimistic routing / segment cache: bail out on rewrite-shifted and conflicting-dynamic-sibling prefetches (fix infinite prefetch loops)

Open
#2,898 1 comment 0 reactions 0 assignees View on GitHub
nextjs-tracking
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

Open the contributing 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.