Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
f401472
test(website): dogfood a blog post with a non-ASCII html slug (AI-ass…
slorber Oct 30, 2026
d1d9c84
test(argos): screenshot pages served at .html URLs by static hosts (A…
slorber Oct 30, 2026
b958094
fix(core): preload the normalized pathname before hydrating (AI-assis…
slorber Oct 29, 2026
82ebf8b
fix(core): decode the browser pathname before normalizing it (AI-assi…
slorber Oct 30, 2026
8a5a051
test(website): use a dogfood doc instead of a blog post for the café …
slorber Oct 30, 2026
a6941d2
Merge branch 'dup-render/dogfood-encoded-html-slug' into dup-render/a…
slorber Oct 30, 2026
b1242fa
test(argos): screenshot the café dogfood doc instead of the blog post…
slorber Oct 30, 2026
24b9b40
Merge branch 'dup-render/argos-static-host-urls' into dup-render/prel…
slorber Oct 30, 2026
d397459
test(argos): move the café regression test out of the repro (AI-assis…
slorber Oct 30, 2026
053c351
test(argos): capture the duplicate render in aria snapshots too (AI-a…
slorber Oct 30, 2026
def29d0
Merge branch 'dup-render/argos-static-host-urls' into dup-render/prel…
slorber Oct 30, 2026
96892be
test(argos): remove the static host tests, they belong to #12813 (AI-…
slorber Oct 30, 2026
f2f26d0
Merge branch 'main' into dup-render/preload-normalize (AI-assisted)
slorber Oct 30, 2026
1454dd9
Merge branch 'main' into dup-render/preload-normalize (AI-assisted)
slorber Nov 5, 2026
a0d9b11
Merge branch 'main' into dup-render/preload-normalize (AI-assisted)
slorber Nov 6, 2026
094f25e
Merge branch 'main' into dup-render/preload-normalize (AI-assisted)
slorber Nov 6, 2026
0e77866
refactor(core): explain the .html normalization of the preloaded path…
slorber Nov 6, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@
*/

import {describe, expect, it, vi} from 'vitest';
import normalizeLocation from '../normalizeLocation';
import normalizeLocation, {
normalizeBrowserPathname,
} from '../normalizeLocation';
import type {Location} from 'history';

vi.mock('@generated/routes', () => import('./__mocks__/@generated/routes'));
Expand Down Expand Up @@ -111,3 +113,19 @@ describe('normalizeLocation', () => {
});
});
});

describe('normalizeBrowserPathname', () => {
it('normalizes the decoded pathname', () => {
expect(normalizeBrowserPathname('/index.html')).toBe('/');
expect(normalizeBrowserPathname('/docs/installation.html')).toBe(
'/docs/installation',
);
expect(normalizeBrowserPathname('/caf%C3%A9.html')).toBe('/caf%C3%A9.html');
});

it('returns a pathname that can be decoded', () => {
const pathname = normalizeBrowserPathname('/100%25.html');
expect(pathname).toBe('/100%25');
expect(decodeURI(pathname)).toBe('/100%');
});
});
6 changes: 5 additions & 1 deletion packages/docusaurus/src/client/clientEntry.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import siteConfig from '@generated/docusaurus.config';
import ExecutionEnvironment from './exports/ExecutionEnvironment';
import App from './App';
import preload from './preload';
import {normalizeBrowserPathname} from './normalizeLocation';
import docusaurus from './docusaurus';

function Router({children}: {children: ReactNode}): ReactNode {
Expand Down Expand Up @@ -63,7 +64,10 @@ if (ExecutionEnvironment.canUseDOM) {
}
};

preload(window.location.pathname).then(() => {
// Normalize /page.html to /page, the route App renders: preloading
// /page.html would hydrate an empty route and render the page twice
// See https://github.com/meta/docusaurus/pull/12810
preload(normalizeBrowserPathname(window.location.pathname)).then(() => {
startTransition(renderApp);
});

Expand Down
38 changes: 22 additions & 16 deletions packages/docusaurus/src/client/normalizeLocation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,29 +12,35 @@ import type {Location} from 'history';
// Memoize previously normalized pathnames.
const pathnames = new Map<string, string>();

export default function normalizeLocation<T extends Location>(location: T): T {
if (pathnames.has(location.pathname)) {
return {
...location,
pathname: pathnames.get(location.pathname),
};
}

function computeNormalizedPathname(pathname: string): string {
// If the location was registered with an `.html` extension, we don't strip it
// away, or it will render to a 404 page.
const matchedRoutes = matchRoutes(routes, location.pathname);
const matchedRoutes = matchRoutes(routes, pathname);
if (matchedRoutes.some(({route}) => route.exact === true)) {
pathnames.set(location.pathname, location.pathname);
return location;
return pathname;
}
return pathname.trim().replace(/(?:\/index)?\.html$/, '') || '/';
}

const pathname =
location.pathname.trim().replace(/(?:\/index)?\.html$/, '') || '/';

pathnames.set(location.pathname, pathname);
function normalizePathname(pathname: string): string {
let normalizedPathname = pathnames.get(pathname);
if (normalizedPathname === undefined) {
normalizedPathname = computeNormalizedPathname(pathname);
pathnames.set(pathname, normalizedPathname);
}
return normalizedPathname;
}

export default function normalizeLocation<T extends Location>(location: T): T {
return {
...location,
pathname,
pathname: normalizePathname(location.pathname),
};
}

// The router decodes the browser pathname before App normalizes it. Encode it
// back for preload(), which decodes again and throws on /100%25 if not encoded
export function normalizeBrowserPathname(browserPathname: string): string {
const routerPathname = decodeURI(browserPathname);
return encodeURI(normalizePathname(routerPathname));
}
Loading