Skip to content

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

Description

@github-actions

Upstream change

Next.js 5942b37 — "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

Metadata

Metadata

Assignees

No one assigned

    Labels

    nextjs-trackingTracking issue for a Next.js canary change relevant to vinext

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions