Skip to content

Commit be1d308

Browse files
fix(router): scroll to top when href="/" and hash already present (vercel#32954)
When the URL already contains a hash (like `/#section`) and a `Link` with `href="/"` is clicked, the page should scroll to the top on the first click. Currently, it only happens with the second click (after `#section` has been removed from the URL). Fixes vercel#32931 ## Bug - [ ] Related issues linked using `fixes #number` - [ ] Integration tests added - [ ] Errors have helpful link attached, see `contributing.md` ## Feature - [ ] Implements an existing feature request or RFC. Make sure the feature request has been accepted for implementation before opening a PR. - [ ] Related issues linked using `fixes #number` - [ ] Integration tests added - [ ] Documentation added - [ ] Telemetry added. In case of a feature if it's used or not. - [ ] Errors have helpful link attached, see `contributing.md` ## Documentation / Examples - [ ] Make sure the linting passes by running `yarn lint`
1 parent 381ca37 commit be1d308

File tree

3 files changed

+68
-1
lines changed

3 files changed

+68
-1
lines changed

packages/next/shared/lib/router/router.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1615,7 +1615,7 @@ export default class Router implements BaseRouter {
16151615
}
16161616

16171617
scrollToHash(as: string): void {
1618-
const [, hash] = as.split('#')
1618+
const [, hash = ''] = as.split('#')
16191619
// Scroll to top if the hash is just `#` with no value or `#top`
16201620
// To mirror browsers
16211621
if (hash === '' || hash === 'top') {
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import Link from 'next/link'
2+
3+
export default function Page() {
4+
return (
5+
<div>
6+
<div style={{ height: '100vh' }} />
7+
<Link href="/">
8+
<a id="top-link">top</a>
9+
</Link>
10+
<Link href="#section">
11+
<a id="section-link">section link</a>
12+
</Link>
13+
<section id="section">section content</section>
14+
</div>
15+
)
16+
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
/* eslint-env jest */
2+
3+
import { join } from 'path'
4+
import webdriver from 'next-webdriver'
5+
import {
6+
findPort,
7+
launchApp,
8+
killApp,
9+
nextStart,
10+
nextBuild,
11+
} from 'next-test-utils'
12+
13+
let app
14+
let appPort
15+
const appDir = join(__dirname, '../')
16+
17+
function runTests() {
18+
it('scrolls to top when href="/" and url already contains a hash', async () => {
19+
const browser = await webdriver(appPort, '/#section')
20+
expect(await browser.eval(() => window.scrollY)).not.toBe(0)
21+
await browser.elementByCss('#top-link').click()
22+
expect(await browser.eval(() => window.scrollY)).toBe(0)
23+
await browser.close()
24+
})
25+
}
26+
27+
describe('router.isReady', () => {
28+
describe('dev mode', () => {
29+
beforeAll(async () => {
30+
appPort = await findPort()
31+
app = await launchApp(appDir, appPort)
32+
})
33+
afterAll(async () => {
34+
await killApp(app)
35+
})
36+
37+
runTests()
38+
})
39+
40+
describe('production mode', () => {
41+
beforeAll(async () => {
42+
await nextBuild(appDir)
43+
44+
appPort = await findPort()
45+
app = await nextStart(appDir, appPort)
46+
})
47+
afterAll(() => killApp(app))
48+
49+
runTests()
50+
})
51+
})

0 commit comments

Comments
 (0)