Handles same-page hash navigation by combining `history.pushState`, synthetic `hashchange` events, and smooth scrolling — working around Next.js limitations that suppress smooth scroll during navigation and treat same-URL `router.push` as a no-op. ## Key Components ### Constants | Constant | Value | Purpose | |---|---|---| | `STICKY_HEADER_OFFSET_PX` | `96` | Offset for pages with both a section-nav strip and the global hub header | | `HUB_HEADER_OFFSET_PX` | `80` | Offset for pages with only the global hub header (docs, blog, vendor detail) | ### Functions & Interfaces - **`normalizeHashFragment(hash)`** — Strips any extra `#` segments from a fragment string (e.g., `'#a#b'` → `'#a'`), keeping the URL bar and `getElementById` in sync. - **`NavigateSamePageHashOptions`** — Interface with optional `headerOffset` (pixels to subtract for sticky chrome) and `history` mode (`'push'` or `'replace'`). - **`navigateSamePageHash(target, options)`** — Core primitive that validates the target is same-origin/pathname/search, normalizes malformed fragments, updates history, fires a synthetic `hashchange` event, and smooth-scrolls to the anchor. Returns `true` if the navigation was claimed, `false` for cross-page targets. ## Usage Example ```typescript import { navigateSamePageHash, HUB_HEADER_OFFSET_PX, STICKY_HEADER_OFFSET_PX, } from './same-page-hash-nav' // In a TOC link click handler (replaces history entry instead of pushing) function handleTocClick(href: string) { const claimed = navigateSamePageHash(href, { headerOffset: HUB_HEADER_OFFSET_PX, history: 'replace', }) if (!claimed) { router.push(href) // fall through for cross-page targets } } // Bare-hash shorthand — no need to reconstruct pathname + search navigateSamePageHash('#features', { headerOffset: STICKY_HEADER_OFFSET_PX, }) ``` > **Dev-mode warnings:** In development, `navigateSamePageHash` logs to the console when it detects a malformed multi-fragment hash or a missing anchor element, pointing to the upstream composer as the source.