diff --git a/crates/mdbook-html/front-end/templates/toc.js.hbs b/crates/mdbook-html/front-end/templates/toc.js.hbs
index 1a9f751ccf..2f80c21f23 100644
--- a/crates/mdbook-html/front-end/templates/toc.js.hbs
+++ b/crates/mdbook-html/front-end/templates/toc.js.hbs
@@ -14,6 +14,19 @@ class MDBookSidebarScrollbox extends HTMLElement {
if (current_page.endsWith('/')) {
current_page += 'index.html';
}
+ // Normalize a URL for comparison by stripping .html suffix.
+ // This ensures TOC highlighting works on hosts that use "pretty URLs"
+ // (e.g. Cloudflare Pages) which strip the .html extension.
+ function normalizeUrl(url) {
+ if (url.endsWith('/index.html')) {
+ return url.slice(0, -'/index.html'.length + 1);
+ }
+ if (url.endsWith('.html')) {
+ return url.slice(0, -'.html'.length);
+ }
+ return url;
+ }
+ const normalizedCurrentPage = normalizeUrl(current_page);
const links = Array.prototype.slice.call(this.querySelectorAll('a'));
const l = links.length;
for (let i = 0; i < l; ++i) {
@@ -24,6 +37,7 @@ class MDBookSidebarScrollbox extends HTMLElement {
}
// The 'index' page is supposed to alias the first chapter in the book.
if (link.href === current_page
+ || normalizeUrl(link.href) === normalizedCurrentPage
|| i === 0
&& path_to_root === ''
&& current_page.endsWith('/index.html')) {
diff --git a/tests/testsuite/toc.rs b/tests/testsuite/toc.rs
index 40bb0001e2..72981c2c1d 100644
--- a/tests/testsuite/toc.rs
+++ b/tests/testsuite/toc.rs
@@ -141,6 +141,16 @@ fn check_link_target_fallback() {
);
}
+/// The generated toc.js should normalize URLs for comparison so that TOC
+/// highlighting works on hosts that use "pretty URLs" (stripping .html).
+/// See .
+#[test]
+fn toc_js_normalizes_urls_for_pretty_urls() {
+ BookTest::from_dir("toc/basic_toc")
+ .check_file_contains("book/toc*.js", "normalizeUrl")
+ .check_file_contains("book/toc*.js", "normalizeUrl(link.href) === normalizedCurrentPage");
+}
+
// Checks formatting of summary names with inline elements.
#[test]
fn summary_with_markdown_formatting() {