From 2a8a289a90fb08c6329b084f8bc33389f2e13815 Mon Sep 17 00:00:00 2001 From: Dmitry Savy Date: Fri, 24 Jul 2026 17:11:18 -0500 Subject: [PATCH] fix(utils): do not read a query from a "?" inside the URL hash toURLPath distinguishes "no query" from an empty query by falling back to url.href.includes("?"), but href also includes the fragment. So a "?" inside the hash (for example "/foo#bar?baz") was read as an empty query. That returned search: "" instead of undefined, and serializeURLPath turns an empty search into a literal "?", so round-tripping "/foo#bar?baz" through parseURLPath and serializeURLPath corrupted it to "/foo?#bar?baz". Only look for the query in the part before the fragment. Added a test for a hash that contains a question mark. --- packages/docusaurus-utils/src/__tests__/urlUtils.test.ts | 9 +++++++++ packages/docusaurus-utils/src/urlUtils.ts | 6 +++++- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/packages/docusaurus-utils/src/__tests__/urlUtils.test.ts b/packages/docusaurus-utils/src/__tests__/urlUtils.test.ts index fc9cf1662032..4ad32a214771 100644 --- a/packages/docusaurus-utils/src/__tests__/urlUtils.test.ts +++ b/packages/docusaurus-utils/src/__tests__/urlUtils.test.ts @@ -277,6 +277,15 @@ describe('toURLPath', () => { hash: '', }); }); + + it('pathname + hash containing a question mark (no query)', () => { + const url = parseURLOrPath('/pathname#hash?notquery'); + expect(toURLPath(url)).toEqual({ + pathname: '/pathname', + search: undefined, + hash: 'hash?notquery', + }); + }); }); describe('parseLocalURLPath', () => { diff --git a/packages/docusaurus-utils/src/urlUtils.ts b/packages/docusaurus-utils/src/urlUtils.ts index 52c2ec7445b4..0b661ce3d6cc 100644 --- a/packages/docusaurus-utils/src/urlUtils.ts +++ b/packages/docusaurus-utils/src/urlUtils.ts @@ -177,13 +177,17 @@ export type URLPath = {pathname: string; search?: string; hash?: string}; export function toURLPath(url: URL): URLPath { const {pathname} = url; + // Only the part before the fragment can contain the query string. A "?" + // inside the hash (e.g. "/foo#bar?baz") must not be read as an empty query. + const beforeHash = url.hash ? url.href.slice(0, -url.hash.length) : url.href; + // Fixes annoying url.search behavior // "" => undefined // "?" => "" // "?param => "param" const search = url.search ? url.search.slice(1) - : url.href.includes('?') + : beforeHash.includes('?') ? '' : undefined;