mirror of
https://github.com/hanzoai/next.js.git
synced 2026-10-30 14:46:38 +00:00
When matching a URL against the known route trie, if a URL part matches a static child but the remaining parts don't resolve within that child's subtree, the algorithm incorrectly falls back to a dynamic catch-all sibling at the same level. This causes the catch-all to consume the already-matched static part plus the remaining parts, producing a synthetic entry for the wrong route. For example, given routes `/dashboard/settings/profile` and `/dashboard/[...catchall]`, navigating to `/dashboard/settings/profile` could incorrectly match the catch-all with params `["settings", "profile"]` instead of the intended static route. The fix treats the static child match as authoritative: if a real (non-placeholder) static child exists but its subtree can't resolve the remaining URL parts, bail out to server resolution rather than trying the dynamic sibling. This is safe because it just means an extra network request for routes whose subtree hasn't been fully discovered.
66 lines
1.9 KiB
TypeScript
66 lines
1.9 KiB
TypeScript
'use client'
|
|
|
|
import { useState } from 'react'
|
|
import { useParams, usePathname, useSearchParams } from 'next/navigation'
|
|
|
|
type RenderedRouteEntry = {
|
|
url: string
|
|
params: Record<string, string | string[] | undefined>
|
|
}
|
|
|
|
/**
|
|
* Accumulates every [url, params] pair rendered throughout the app's lifetime.
|
|
* Uses the setState-in-render pattern to capture changes synchronously,
|
|
* before React commits. This catches any intermediate/wrong route state that
|
|
* might flash briefly due to incorrect route prediction.
|
|
*/
|
|
export function RenderedRouteHistory() {
|
|
const params = useParams()
|
|
const pathname = usePathname()
|
|
const searchParams = useSearchParams()
|
|
|
|
const search = searchParams.toString()
|
|
const url = search ? `${pathname}?${search}` : pathname
|
|
const entry: RenderedRouteEntry = { url, params }
|
|
const entryJson = JSON.stringify(entry)
|
|
|
|
const [history, setHistory] = useState<string[]>([entryJson])
|
|
const [prevEntry, setPrevEntry] = useState(entryJson)
|
|
|
|
// setState-in-render pattern: update state synchronously during render
|
|
// if the route state has changed. This captures intermediate states.
|
|
if (entryJson !== prevEntry) {
|
|
setPrevEntry(entryJson)
|
|
setHistory((prev) => [...prev, entryJson])
|
|
}
|
|
|
|
return (
|
|
<div
|
|
id="rendered-route-history"
|
|
data-history={JSON.stringify(history)}
|
|
style={{
|
|
padding: '8px',
|
|
marginTop: '16px',
|
|
background: '#f5f5f5',
|
|
borderRadius: '4px',
|
|
fontSize: '12px',
|
|
fontFamily: 'monospace',
|
|
}}
|
|
>
|
|
<div style={{ fontWeight: 'bold', marginBottom: '4px' }}>
|
|
Rendered Route History (debug)
|
|
</div>
|
|
<div>Current: {entryJson}</div>
|
|
<div style={{ marginTop: '4px' }}>
|
|
History:{' '}
|
|
{history.map((h, i) => (
|
|
<span key={i}>
|
|
{i > 0 && ' → '}
|
|
{h}
|
|
</span>
|
|
))}
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|