From a33f42528201dc6768db713ab7dfea5e73c42c57 Mon Sep 17 00:00:00 2001 From: SukkaW Date: Wed, 11 May 2022 09:36:15 +0800 Subject: [PATCH 1/5] fix(parseRelativeUrl): parse relative path correctly --- .../lib/router/utils/parse-relative-url.ts | 12 +++++++++++- test/unit/parse-relative-url.test.ts | 18 ++++++++++++++++++ 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/packages/next/shared/lib/router/utils/parse-relative-url.ts b/packages/next/shared/lib/router/utils/parse-relative-url.ts index 219dd3b5b4207..9a4c6b100fdab 100644 --- a/packages/next/shared/lib/router/utils/parse-relative-url.ts +++ b/packages/next/shared/lib/router/utils/parse-relative-url.ts @@ -8,10 +8,20 @@ import { searchParamsToUrlQuery } from './querystring' * the current origin will be parsed as relative */ export function parseRelativeUrl(url: string, base?: string) { + const isNotPathRelative = url.charAt(0) === '.' const globalBase = new URL( typeof window === 'undefined' ? 'http://n' : getLocationOrigin() ) - const resolvedBase = base ? new URL(base, globalBase) : globalBase + + let resolvedBase = globalBase // default to base + if (base) { + resolvedBase = new URL(base, globalBase) + } else if (isNotPathRelative) { + resolvedBase = new URL( + typeof window === 'undefined' ? 'http://n' : window.location.href + ) + } + const { pathname, searchParams, search, hash, href, origin } = new URL( url, resolvedBase diff --git a/test/unit/parse-relative-url.test.ts b/test/unit/parse-relative-url.test.ts index 5343ec49ce6a1..5257a42871e9d 100644 --- a/test/unit/parse-relative-url.test.ts +++ b/test/unit/parse-relative-url.test.ts @@ -42,6 +42,24 @@ describe('parseRelativeUrl', () => { check( 'http://example.com:3210/someA/pathB?fooC=barD#hashE', './someF/pathG?fooH=barI#hashJ', + { + pathname: '/someA/someF/pathG', + search: '?fooH=barI', + hash: '#hashJ', + } + ) + check( + 'http://example.com:3210/someA/pathB', + '../someF/pathG?fooH=barI#hashJ', + { + pathname: '/someF/pathG', + search: '?fooH=barI', + hash: '#hashJ', + } + ) + check( + 'http://example.com:3210/someA/pathB', + '../../someF/pathG?fooH=barI#hashJ', { pathname: '/someF/pathG', search: '?fooH=barI', From 8cb319ab0d10dcf1ca12b946695fe02407be733f Mon Sep 17 00:00:00 2001 From: SukkaW Date: Wed, 11 May 2022 11:10:34 +0800 Subject: [PATCH 2/5] test: add relative path test case --- .../pages/nav/relative/index.js | 10 ++++++++ .../pages/nav/relative/relative-1.js | 12 ++++++++++ .../pages/nav/relative/relative-2.js | 18 ++++++++++++++ .../client-navigation/test/index.test.js | 24 +++++++++++++++++++ 4 files changed, 64 insertions(+) create mode 100644 test/integration/client-navigation/pages/nav/relative/index.js create mode 100644 test/integration/client-navigation/pages/nav/relative/relative-1.js create mode 100644 test/integration/client-navigation/pages/nav/relative/relative-2.js diff --git a/test/integration/client-navigation/pages/nav/relative/index.js b/test/integration/client-navigation/pages/nav/relative/index.js new file mode 100644 index 0000000000000..0ca5d654f3db0 --- /dev/null +++ b/test/integration/client-navigation/pages/nav/relative/index.js @@ -0,0 +1,10 @@ +import Link from 'next/link' + +export default function Relative() { + return ( +
+ On relative index + To relative 1 +
+ ) +} diff --git a/test/integration/client-navigation/pages/nav/relative/relative-1.js b/test/integration/client-navigation/pages/nav/relative/relative-1.js new file mode 100644 index 0000000000000..4229cafc30afd --- /dev/null +++ b/test/integration/client-navigation/pages/nav/relative/relative-1.js @@ -0,0 +1,12 @@ +import Link from 'next/link' + +export default function Relative1() { + return ( +
+ On relative 1 + + To relative 2 + +
+ ) +} diff --git a/test/integration/client-navigation/pages/nav/relative/relative-2.js b/test/integration/client-navigation/pages/nav/relative/relative-2.js new file mode 100644 index 0000000000000..d5cbf991d7879 --- /dev/null +++ b/test/integration/client-navigation/pages/nav/relative/relative-2.js @@ -0,0 +1,18 @@ +import { useRouter } from 'next/router' + +export default function Relative2() { + const router = useRouter() + return ( +
+ On relative 2 + +
+ ) +} diff --git a/test/integration/client-navigation/test/index.test.js b/test/integration/client-navigation/test/index.test.js index b0fc4128bf953..fd48937384c05 100644 --- a/test/integration/client-navigation/test/index.test.js +++ b/test/integration/client-navigation/test/index.test.js @@ -1611,6 +1611,30 @@ describe('Client Navigation', () => { expect(value).toBe(false) }) + it('should navigate to paths relative to the current page', async () => { + const browser = await webdriver(context.appPort, '/nav/relative') + await browser.waitForElementByCss('body', 500) + + let page + + await browser.elementByCss('a').click() + + page = await browser.elementByCss('body').text() + expect(page).toMatch('On relative 1') + await browser.elementByCss('a').click() + + browser.waitForElementByCss('#relative-2', 500) + page = await browser.elementByCss('body').text() + expect(page).toMatch(/On relative 2/) + + await browser.elementByCss('button').click() + browser.waitForElementByCss('#relative', 500) + page = await browser.elementByCss('body').text() + expect(page).toMatch(/On relative index/) + + await browser.close() + }) + renderingSuite( (p, q) => renderViaHTTP(context.appPort, p, q), (p, q) => fetchViaHTTP(context.appPort, p, q), From 0ff5279ebd40d724adeeb202e329cb8310fc971b Mon Sep 17 00:00:00 2001 From: SukkaW Date: Wed, 11 May 2022 11:18:36 +0800 Subject: [PATCH 3/5] fix(router): avoid relative path causes hydration mismatch --- packages/next/shared/lib/router/router.ts | 4 ++-- packages/next/shared/lib/utils.ts | 5 +++++ 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/packages/next/shared/lib/router/router.ts b/packages/next/shared/lib/router/router.ts index d36d38a9ff64d..5b7f7c7e8f95a 100644 --- a/packages/next/shared/lib/router/router.ts +++ b/packages/next/shared/lib/router/router.ts @@ -30,6 +30,7 @@ import { NextPageContext, ST, NEXT_DATA, + isAbsoluteUrl, } from '../utils' import { isDynamicRoute } from './utils/is-dynamic' import { parseRelativeUrl } from './utils/parse-relative-url' @@ -221,8 +222,7 @@ export function delBasePath(path: string): string { */ export function isLocalURL(url: string): boolean { // prevent a hydration mismatch on href for url with anchor refs - if (url.startsWith('/') || url.startsWith('#') || url.startsWith('?')) - return true + if (!isAbsoluteUrl(url)) return true try { // absolute urls can be local if they are on the same origin const locationOrigin = getLocationOrigin() diff --git a/packages/next/shared/lib/utils.ts b/packages/next/shared/lib/utils.ts index 20dbe91ea53f7..52717cb414bbe 100644 --- a/packages/next/shared/lib/utils.ts +++ b/packages/next/shared/lib/utils.ts @@ -290,6 +290,11 @@ export function execOnce ReturnType>( }) as T } +// Scheme: https://tools.ietf.org/html/rfc3986#section-3.1 +// Absolute URL: https://tools.ietf.org/html/rfc3986#section-4.3 +const ABSOLUTE_URL_REGEX = /^[a-zA-Z][a-zA-Z\d+\-.]*?:/ +export const isAbsoluteUrl = (url: string) => ABSOLUTE_URL_REGEX.test(url) + export function getLocationOrigin() { const { protocol, hostname, port } = window.location return `${protocol}//${hostname}${port ? ':' + port : ''}` From e1c6f8596d82cb11023caf9f88ebcc4031245439 Mon Sep 17 00:00:00 2001 From: SukkaW Date: Wed, 11 May 2022 11:27:06 +0800 Subject: [PATCH 4/5] test: fix relative path page location --- .../client-navigation/pages/nav/{relative => }/relative-1.js | 0 .../client-navigation/pages/nav/{relative => }/relative-2.js | 2 +- .../integration/client-navigation/pages/nav/relative/index.js | 4 +++- test/integration/client-navigation/test/index.test.js | 4 ++-- 4 files changed, 6 insertions(+), 4 deletions(-) rename test/integration/client-navigation/pages/nav/{relative => }/relative-1.js (100%) rename test/integration/client-navigation/pages/nav/{relative => }/relative-2.js (89%) diff --git a/test/integration/client-navigation/pages/nav/relative/relative-1.js b/test/integration/client-navigation/pages/nav/relative-1.js similarity index 100% rename from test/integration/client-navigation/pages/nav/relative/relative-1.js rename to test/integration/client-navigation/pages/nav/relative-1.js diff --git a/test/integration/client-navigation/pages/nav/relative/relative-2.js b/test/integration/client-navigation/pages/nav/relative-2.js similarity index 89% rename from test/integration/client-navigation/pages/nav/relative/relative-2.js rename to test/integration/client-navigation/pages/nav/relative-2.js index d5cbf991d7879..8322125ab09a9 100644 --- a/test/integration/client-navigation/pages/nav/relative/relative-2.js +++ b/test/integration/client-navigation/pages/nav/relative-2.js @@ -8,7 +8,7 @@ export default function Relative2() {