From 0146877fc18a1ea66149aee9f4f859286ffe591a Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 14 Jul 2026 00:17:19 +0000 Subject: [PATCH] Fix anchor links not scrolling to target on client-side navigation (RND-11844) Cross-page anchor links (e.g. a homepage card linking to `/page#heading`) landed at the top of the destination page on soft navigation, while the same URL loaded directly scrolled to the heading correctly. `useScrollToHash` (the per-page safety net in PageClientLayout that scrolls once the page blocks are rendered) only depended on the navigation hash. The hash is set at click time while the previous page is still mounted, so the effect fired before the target element existed. Because sibling pages share the same `[pagePath]` route, PageClientLayout is reused rather than remounted, so the effect never re-ran once the destination content committed to the DOM. Add `pathname` as a dependency so the scroll is re-attempted when the destination page commits and the target heading is present. The `if (hash)` guard is unchanged, so hash-less navigations still scroll to top via ScrollPage and back/forward restoration is unaffected. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01TpGe6QkAF4Y1HDGUD4vbke --- .changeset/scroll-to-anchor-soft-nav.md | 5 +++++ packages/gitbook/src/components/hooks/useScrollPage.ts | 10 +++++++++- 2 files changed, 14 insertions(+), 1 deletion(-) create mode 100644 .changeset/scroll-to-anchor-soft-nav.md diff --git a/.changeset/scroll-to-anchor-soft-nav.md b/.changeset/scroll-to-anchor-soft-nav.md new file mode 100644 index 000000000..0418cfa07 --- /dev/null +++ b/.changeset/scroll-to-anchor-soft-nav.md @@ -0,0 +1,5 @@ +--- +"gitbook": patch +--- + +Fix anchor links (e.g. `/page#heading`) not scrolling to the target heading during client-side navigation between pages. diff --git a/packages/gitbook/src/components/hooks/useScrollPage.ts b/packages/gitbook/src/components/hooks/useScrollPage.ts index a2751b317..565de8916 100644 --- a/packages/gitbook/src/components/hooks/useScrollPage.ts +++ b/packages/gitbook/src/components/hooks/useScrollPage.ts @@ -69,12 +69,20 @@ export function ScrollPage() { */ export function useScrollToHash() { const hash = useHash(); + const pathname = usePathname(); + // Depend on `pathname` as well as `hash`: on a soft navigation to another page + // (e.g. a homepage card linking to `/page#heading`), the hash is set on click while + // the previous page is still mounted, so a hash-only effect fires before the target + // element exists. Because sibling pages share the same `[pagePath]` route, this hook's + // host component is reused rather than remounted, so it would otherwise never re-run + // once the destination content commits. Re-running on `pathname` re-attempts the scroll + // when the target heading is finally in the DOM. React.useEffect(() => { if (hash) { scrollToHash(hash); } - }, [hash]); + }, [hash, pathname]); } /**