mirror of
https://github.com/hanzoai/next.js.git
synced 2026-09-24 17:00:51 +00:00
When deriving segment cache keys for dynamic params,
`parseDynamicParamFromURLPart` was applying `encodeURIComponent` to
pathname parts that come from `URL.pathname.split('/')`, but those parts
are already in the percent-encoded form the URL parser produces. The
server-side equivalent in `get-dynamic-param.ts` starts from a decoded
param value and applies `encodeURIComponent` once, so the two encodings
only matched for inputs without any percent sequences. For anything else
the client double-encoded, turning `%2F` into `%252F` and so on.
The mismatch stayed hidden until a navigation tried to align the
optimistic route prediction with the server response. At that point
`writeDynamicDataIntoNavigationTask` saw different segments on each
side, returned `didReceiveUnknownParallelRoute`, and let
`finishNavigationTask` fall through to `dispatchRetryDueToTreeMismatch`.
That retry calls `invalidateRouteCacheEntries`, which bumps the route
cache version and invalidates every entry. The next prefetch for the
same URL — for example a `<Link>` re-mounting after a back navigation —
therefore misses the cache and fires a fresh
`next-router-segment-prefetch: /_tree` request.
The fix decodes the URL part before re-encoding so the client produces
the same canonical form as the server. The decode step also takes care
of characters like `,`, `:`, and `+` that the URL parser leaves
untouched but `encodeURIComponent` percent-encodes; simply dropping
`encodeURIComponent` would silently mismatch on those.
The regression test exercises a prefetch → click → back flow against
`/foo` and `/foo%2Fbar`. With the bug present, the encoded variant fires
a route tree request on the back-nav link reveal while the unencoded
variant does not.
28 lines
690 B
TypeScript
28 lines
690 B
TypeScript
import { connection } from 'next/server'
|
|
import { Suspense } from 'react'
|
|
import { LinkAccordion } from '../components/link-accordion'
|
|
|
|
// `connection()` ensures the hub is dynamically rendered.
|
|
async function HubContent() {
|
|
await connection()
|
|
return <div id="hub-content">Hub</div>
|
|
}
|
|
|
|
export default function HubPage() {
|
|
return (
|
|
<div id="hub">
|
|
<Suspense fallback={<div data-loading>Loading…</div>}>
|
|
<HubContent />
|
|
</Suspense>
|
|
<ul>
|
|
<li>
|
|
<LinkAccordion href="/foo">unencoded</LinkAccordion>
|
|
</li>
|
|
<li>
|
|
<LinkAccordion href="/foo%2Fbar">encoded slash</LinkAccordion>
|
|
</li>
|
|
</ul>
|
|
</div>
|
|
)
|
|
}
|