Skip to content

Commit 459617a

Browse files
authored
fix: double fragment on navigation (#93132)
Fixes: #93126, #95551 Repro: - from `/` navigate to `/abc#foo` - on `/abc#foo` - now click `/abc#bar` - the url is now at `/abc#foo#bar` Unlike, #93855, this PR fixes what gets stored in the segment cache navigation, rather than slicing away an existing hash. It is plausible that dropping the hash from the segment cache is not desired though.
1 parent cbf0cef commit 459617a

5 files changed

Lines changed: 67 additions & 1 deletion

File tree

‎packages/next/src/client/components/segment-cache/navigation.ts‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -534,7 +534,9 @@ async function navigateToUnknownRoute(
534534
navigationSeed.routeTree,
535535
metadataVaryPath,
536536
couldBeIntercepted,
537-
createHrefFromUrl(canonicalUrl),
537+
// Store a hashless canonical URL: the entry is shared across hashes, and
538+
// a later same-route hash nav appends `url.hash` to it.
539+
createHrefFromUrl(canonicalUrl, false),
538540
supportsPerSegmentPrefetching,
539541
false // hasDynamicRewrite - not a retry, rewrite detection happens during traversal
540542
)
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import Link from 'next/link'
2+
3+
export function RouterPushToTarget() {
4+
return (
5+
<Link id="link-to-target-bar" href="/hash-cross-path-push/destination#bar">
6+
To destination#bar
7+
</Link>
8+
)
9+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import Link from 'next/link'
2+
3+
export default function Page() {
4+
return (
5+
<>
6+
<h1>Target</h1>
7+
<Link
8+
href="/hash-cross-path-push/destination#baz"
9+
id="link-to-target-baz"
10+
>
11+
Link to destination#baz
12+
</Link>
13+
</>
14+
)
15+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import Link from 'next/link'
2+
import { RouterPushToTarget } from './client-component'
3+
4+
export default function Page() {
5+
return (
6+
<>
7+
<h1>Start</h1>
8+
<Link
9+
href="/hash-cross-path-push/destination#foo"
10+
id="link-to-target-foo"
11+
>
12+
Link to destination#foo
13+
</Link>
14+
<RouterPushToTarget />
15+
</>
16+
)
17+
}

‎test/e2e/app-dir/navigation/navigation.test.ts‎

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -366,6 +366,29 @@ describe('app dir - navigation', () => {
366366
})
367367
})
368368

369+
describe('cross-pathname Link then same-pathname hash change', () => {
370+
const startPath = '/hash-cross-path-push'
371+
const destinationPath = '/hash-cross-path-push/destination'
372+
373+
it('should replace (not concatenate) the hash when <Link> triggers the same-pathname hash change', async () => {
374+
const browser = await next.browser(startPath)
375+
376+
await browser.elementByCss('#link-to-target-foo').click()
377+
await retry(() =>
378+
expect(browser.url()).resolves.toEqual(
379+
next.url + destinationPath + '#foo'
380+
)
381+
)
382+
383+
await browser.elementByCss('#link-to-target-baz').click()
384+
await retry(() =>
385+
expect(browser.url()).resolves.toEqual(
386+
next.url + destinationPath + '#baz'
387+
)
388+
)
389+
})
390+
})
391+
369392
describe('not-found', () => {
370393
it('should trigger not-found in a server component', async () => {
371394
const browser = await next.browser('/not-found/servercomponent')

0 commit comments

Comments
 (0)