diff --git a/apps/docs/features/docs/Reference.apiPage.tsx b/apps/docs/features/docs/Reference.apiPage.tsx index 3e77558f3e6..e1bd542dc98 100644 --- a/apps/docs/features/docs/Reference.apiPage.tsx +++ b/apps/docs/features/docs/Reference.apiPage.tsx @@ -9,7 +9,7 @@ import { SidebarSkeleton } from '~/layouts/MainSkeleton' export async function ApiReferencePage() { return ( - + + diff --git a/apps/docs/features/docs/Reference.navigation.client.tsx b/apps/docs/features/docs/Reference.navigation.client.tsx index 75e2ea65fb2..3e991fd6dc1 100644 --- a/apps/docs/features/docs/Reference.navigation.client.tsx +++ b/apps/docs/features/docs/Reference.navigation.client.tsx @@ -6,7 +6,6 @@ import { BASE_PATH } from '~/lib/constants' import { debounce } from 'lodash-es' import { ChevronUp } from 'lucide-react' import Link from 'next/link' -import { usePathname } from 'next/navigation' import { Collapsible } from 'radix-ui' import type { HTMLAttributes, MouseEvent, PropsWithChildren } from 'react' import { @@ -37,6 +36,7 @@ function subscribeToPathname(callback: () => void) { if (patchCount === 0) { window.addEventListener('popstate', notifyPathnameListeners) + window.addEventListener('hashchange', notifyPathnameListeners) originalPushState = history.pushState.bind(history) history.pushState = (...args) => { @@ -58,6 +58,7 @@ function subscribeToPathname(callback: () => void) { if (patchCount === 0) { window.removeEventListener('popstate', notifyPathnameListeners) + window.removeEventListener('hashchange', notifyPathnameListeners) history.pushState = originalPushState! history.replaceState = originalReplaceState! originalPushState = null @@ -66,40 +67,29 @@ function subscribeToPathname(callback: () => void) { } } -function getPathname() { +function getLocation() { if (typeof window === 'undefined') return '' const pathname = window.location.pathname - return pathname.startsWith(BASE_PATH) ? pathname.slice(BASE_PATH.length) : pathname + const strippedPathname = pathname.startsWith(BASE_PATH) + ? pathname.slice(BASE_PATH.length) + : pathname + return `${strippedPathname}${window.location.hash}` } -function getServerPathname() { +function getServerLocation() { return '' } -function useCurrentPathname() { - return useSyncExternalStore(subscribeToPathname, getPathname, getServerPathname) +function useCurrentLocation() { + return useSyncExternalStore(subscribeToPathname, getLocation, getServerLocation) } -export function ReferenceContentScrollHandler({ - libPath, - version, - isLatestVersion, - children, -}: PropsWithChildren<{ - libPath: string - version: string - isLatestVersion: boolean -}>) { +export function ReferenceContentScrollHandler({ children }: PropsWithChildren) { const [initiallyScrolled, setInitiallyScrolled] = useState(false) - const pathname = usePathname() - useEffect(() => { if (!initiallyScrolled) { - const initialSelectedSection = pathname.replace( - `/reference/${libPath}/${isLatestVersion ? '' : `${version}/`}`, - '' - ) + const initialSelectedSection = window.location.hash.replace(/^#/, '') if (initialSelectedSection) { const section = document.getElementById(initialSelectedSection) if (section) { @@ -110,7 +100,7 @@ export function ReferenceContentScrollHandler({ setInitiallyScrolled(true) } - }, [pathname, libPath, version, isLatestVersion, initiallyScrolled]) + }, [initiallyScrolled]) return ( @@ -177,7 +167,7 @@ export function ReferenceNavigationScrollHandler({ } function deriveHref(basePath: string, section: AbbrevApiReferenceSection) { - return 'slug' in section ? `${basePath}/${section.slug}` : '' + return 'slug' in section ? `${basePath}#${section.slug}` : '' } function getLinkStyles(isActive: boolean, className?: string) { @@ -247,10 +237,10 @@ export function RefLink({ }) { const ref = useRef(null) - const pathname = useCurrentPathname() + const location = useCurrentLocation() const href = deriveHref(basePath, section) const isActive = - pathname === href || (pathname === basePath && href.replace(basePath, '') === '/introduction') + location === href || (location === basePath && href.replace(basePath, '') === '#introduction') useEffect(() => { if (ref.current) { @@ -293,15 +283,15 @@ export function RefLink({ function useCompoundRefLinkActive(basePath: string, section: AbbrevApiReferenceSection) { const [open, _setOpen] = useState(false) - const pathname = useCurrentPathname() + const location = useCurrentLocation() const parentHref = deriveHref(basePath, section) - const isParentActive = pathname === parentHref + const isParentActive = location === parentHref const childHrefs = useMemo( () => new Set((section.items || []).map((item) => deriveHref(basePath, item))), [basePath, section] ) - const isChildActive = childHrefs.has(pathname) + const isChildActive = childHrefs.has(location) const isActive = isParentActive || isChildActive diff --git a/apps/docs/features/docs/Reference.sdkPage.tsx b/apps/docs/features/docs/Reference.sdkPage.tsx index 9f03d453bde..5d095d52034 100644 --- a/apps/docs/features/docs/Reference.sdkPage.tsx +++ b/apps/docs/features/docs/Reference.sdkPage.tsx @@ -21,11 +21,7 @@ export async function ClientSdkReferencePage({ sdkId, libVersion }: ClientSdkRef const menuData = NavItems[libraryMeta.meta[libVersion].libId] return ( - + {subcommandDetails.title} diff --git a/apps/docs/features/docs/Reference.selfHostingPage.tsx b/apps/docs/features/docs/Reference.selfHostingPage.tsx index 53773af3514..7fad5fefc0f 100644 --- a/apps/docs/features/docs/Reference.selfHostingPage.tsx +++ b/apps/docs/features/docs/Reference.selfHostingPage.tsx @@ -48,7 +48,7 @@ export async function SelfHostingReferencePage({ const name = REFERENCES[servicePath.replaceAll('-', '_')].name return ( - + /introduction (and versioned variants) + // back to the base reference URL. Order matters: introduction first so + // it strips to a bare URL, then the section rules add a hash anchor. + { + source: '/reference/:lib/:version(v\\d+)/:section', + destination: '/reference/:lib/:version#:section', + permanent: true, + }, + { + source: '/reference/:lib/:section((?!v\\d+$)[^/]+)', + destination: '/reference/:lib#:section', + permanent: true, }, ] }, diff --git a/apps/www/lib/redirects.js b/apps/www/lib/redirects.js index 551a7613f9a..934a83da7f2 100644 --- a/apps/www/lib/redirects.js +++ b/apps/www/lib/redirects.js @@ -3203,6 +3203,20 @@ module.exports = [ destination: '/dashboard/redeem?code=:code', permanent: false, }, + // Reference pages use hash anchors for sections; redirect the legacy + // path-style /docs/reference//introduction (and versioned variants) + // back to the base reference URL. Order matters: introduction first so it + // strips to a bare URL, then the section rules add a hash anchor. + { + permanent: true, + source: '/docs/reference/:lib/:version(v\\d+)/:section', + destination: '/docs/reference/:lib/:version#:section', + }, + { + permanent: true, + source: '/docs/reference/:lib/:section((?!v\\d+$)[^/]+)', + destination: '/docs/reference/:lib#:section', + }, // Legacy product .txt URLs → new .md routes { permanent: true, source: '/llms/homepage.txt', destination: '/homepage.md' }, { permanent: true, source: '/llms/auth.txt', destination: '/auth.md' },