diff --git a/.github/ISSUE_TEMPLATE/1.bug_report.yml b/.github/ISSUE_TEMPLATE/1.bug_report.yml
index 7bc9dedbb4fd017..d6b3957f5a83b31 100644
--- a/.github/ISSUE_TEMPLATE/1.bug_report.yml
+++ b/.github/ISSUE_TEMPLATE/1.bug_report.yml
@@ -12,10 +12,10 @@ inputs:
value: If you leave out sections there is a high likelihood it will be moved to the GitHub Discussions "Help" section.
- type: description
attributes:
- value: "Please first verify if your issue exists in the Next.js canary release line: `npm install next@canary`."
+ value: 'Please first verify if your issue exists in the Next.js canary release line: `npm install next@canary`.'
- type: description
attributes:
- value: "next@canary is the beta version of Next.js. It includes all features and fixes that are pending to land on the stable release line."
+ value: 'next@canary is the beta version of Next.js. It includes all features and fixes that are pending to land on the stable release line.'
- type: input
attributes:
label: What version of Next.js are you using?
diff --git a/packages/next/next-server/lib/router/router.ts b/packages/next/next-server/lib/router/router.ts
index e59fada6cbb1110..44cc9486e395912 100644
--- a/packages/next/next-server/lib/router/router.ts
+++ b/packages/next/next-server/lib/router/router.ts
@@ -163,7 +163,8 @@ export function delBasePath(path: string): string {
* Detects whether a given url is routable by the Next.js router (browser only).
*/
export function isLocalURL(url: string): boolean {
- if (url.startsWith('/')) return true
+ // prevent a hydration mismatch on href for url with anchor refs
+ if (url.startsWith('/') || url.startsWith('#')) return true
try {
// absolute urls can be local if they are on the same origin
const locationOrigin = getLocationOrigin()
diff --git a/test/integration/link-with-hash/pages/index.js b/test/integration/link-with-hash/pages/index.js
new file mode 100644
index 000000000000000..f39a8ed7342d281
--- /dev/null
+++ b/test/integration/link-with-hash/pages/index.js
@@ -0,0 +1,14 @@
+import React from 'react'
+import Link from 'next/link'
+
+const Home = () => {
+ return (
+ <>
+
+ Hash Link
+
+ >
+ )
+}
+
+export default Home
diff --git a/test/integration/link-with-hash/test/index.test.js b/test/integration/link-with-hash/test/index.test.js
new file mode 100644
index 000000000000000..365cade8ad9bbf0
--- /dev/null
+++ b/test/integration/link-with-hash/test/index.test.js
@@ -0,0 +1,53 @@
+/* eslint-env jest */
+
+import { join } from 'path'
+import webdriver from 'next-webdriver'
+import {
+ findPort,
+ launchApp,
+ killApp,
+ nextStart,
+ nextBuild,
+} from 'next-test-utils'
+
+jest.setTimeout(1000 * 60 * 5)
+let app
+let appPort
+const appDir = join(__dirname, '..')
+
+const runTests = () => {
+ it('should not have hydration mis-match for hash link', async () => {
+ const browser = await webdriver(appPort, '/')
+ const browserLogs = await browser.log('browser')
+ let found = false
+ browserLogs.forEach((log) => {
+ if (log.message.includes('Warning: Prop')) {
+ found = true
+ }
+ })
+ expect(found).toEqual(false)
+ })
+}
+
+describe('Link with hash href', () => {
+ describe('development', () => {
+ beforeAll(async () => {
+ appPort = await findPort()
+ app = await launchApp(appDir, appPort)
+ })
+ afterAll(() => killApp(app))
+
+ runTests()
+ })
+
+ describe('production', () => {
+ beforeAll(async () => {
+ await nextBuild(appDir)
+ appPort = await findPort()
+ app = await nextStart(appDir, appPort)
+ })
+ afterAll(() => killApp(app))
+
+ runTests()
+ })
+})