Skip to content

Commit a2f9ef5

Browse files
blurrahztannerijjk
authored
fix(next/client): keep hash when navigating from app to pages router (#56223)
### What? Fixes the pages router not receiving a hash when being linked from the app router. ### Why? The hash being removed breaks sites that rely on it for client side features. ### How? The hash gets omitted from the URL when used as a key for the preflightCache. Once it's clear that it's an external URL and that it's not empty we can use the initial href to send the hash as well. Not completely sure if there's an edge case that might break, I added an extra check for when the hash is only used to scroll the page. This might need an additional test case just for `navigate-reducer.test.tsx`. Fixes #56212 --------- Co-authored-by: Zack Tanner <zacktanner@gmail.com> Co-authored-by: JJ Kasper <jj@jjsweb.site>
1 parent a970f28 commit a2f9ef5

File tree

4 files changed

+34
-0
lines changed

4 files changed

+34
-0
lines changed

packages/next/src/client/components/router-reducer/fetch-server-response.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,11 @@ export async function fetchServerResponse(
122122
// If fetch returns something different than flight response handle it like a mpa navigation
123123
// If the fetch was not 200, we also handle it like a mpa navigation
124124
if (!isFlightResponse || !res.ok) {
125+
// in case the original URL came with a hash, preserve it before redirecting to the new URL
126+
if (url.hash) {
127+
responseUrl.hash = url.hash
128+
}
129+
125130
return doMpaNavigation(responseUrl.toString())
126131
}
127132

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
* {
2+
margin: 0;
3+
padding: 0;
4+
box-sizing: border-box;
5+
font-size: 14px;
6+
line-height: 1;
7+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import Link from 'next/link'
2+
import './global.css'
3+
4+
export default function HashPage() {
5+
return (
6+
<div style={{ fontFamily: 'sans-serif', fontSize: '16px' }}>
7+
<p>Hash To Pages Router Page</p>
8+
<Link href="/some#non-existent" id="link-to-pages-router">
9+
To pages router
10+
</Link>
11+
</div>
12+
)
13+
}

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

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -498,6 +498,15 @@ createNextDescribe(
498498
expect(await browser.url()).toBe(next.url + '/some')
499499
})
500500

501+
it('should not omit the hash while navigating from app to pages', async () => {
502+
const browser = await next.browser('/hash-link-to-pages-router')
503+
await browser
504+
.elementByCss('#link-to-pages-router')
505+
.click()
506+
.waitForElementByCss('#link-to-app')
507+
await check(() => browser.url(), next.url + '/some#non-existent')
508+
})
509+
501510
if (!isNextDev) {
502511
// this test is pretty hard to test in playwright, so most of the heavy lifting is in the page component itself
503512
// it triggers a hover on a link to initiate a prefetch request every second, and so we check that

0 commit comments

Comments
 (0)