fix: move reference pages to hash navigation and apply redirect rules (#46501)

This commit is contained in:
Jeremias Menichelli authored and GitHub committed 2026-05-29 15:32:51 +00:00
1 parent 3260660daa
commit 92dfbac899
9 files changed
+54 -40

No files matched your search

@@ -9,7 +9,7 @@ import { SidebarSkeleton } from '~/layouts/MainSkeleton'
export async function ApiReferencePage() {
return (
<ReferenceContentScrollHandler libPath="api" version="latest" isLatestVersion={true}>
<ReferenceContentScrollHandler>
<SidebarSkeleton
menuId={MenuId.RefApi}
NavigationMenu={
@@ -9,7 +9,7 @@ import { SidebarSkeleton } from '~/layouts/MainSkeleton'
export async function CliReferencePage() {
return (
<ReferenceContentScrollHandler libPath="cli" version="latest" isLatestVersion={true}>
<ReferenceContentScrollHandler>
<SidebarSkeleton
menuId={MenuId.RefCli}
NavigationMenu={
@@ -42,7 +42,7 @@ export async function ClientLibIntroduction({
return (
<ReferenceSectionWrapper
id="introduction"
link={`/docs/reference/${libPath}/${isLatestVersion ? '' : `${version}/`}introduction`}
link={`/docs/reference/${libPath}${isLatestVersion ? '' : `/${version}`}#introduction`}
className={cn('prose', className)}
>
<MDXRemoteRefs source={content} />
@@ -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 (
<ReferenceContentInitiallyScrolledContext.Provider value={initiallyScrolled}>
@@ -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<HTMLAnchorElement>(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
@@ -21,11 +21,7 @@ export async function ClientSdkReferencePage({ sdkId, libVersion }: ClientSdkRef
const menuData = NavItems[libraryMeta.meta[libVersion].libId]
return (
<ReferenceContentScrollHandler
libPath={libraryMeta.libPath}
version={libVersion}
isLatestVersion={isLatestVersion}
>
<ReferenceContentScrollHandler>
<SidebarSkeleton
menuId={libraryMeta.meta[libVersion].libId}
NavigationMenu={
@@ -94,7 +94,7 @@ export function SectionSwitch({ libraryId, version, section }: SectionSwitchProp
const allAvailableVersions = REFERENCES[libraryId.replaceAll('-', '_')].versions
const isLatestVersion = allAvailableVersions.length === 0 || version === allAvailableVersions[0]
const sectionLink = `/docs/reference/${libPath}/${isLatestVersion ? '' : `${version}/`}${section.slug}`
const sectionLink = `/docs/reference/${libPath}${isLatestVersion ? '' : `/${version}`}#${section.slug}`
switch (section.type) {
case 'markdown':
@@ -192,7 +192,7 @@ async function CliCommandSection({ link, section }: CliCommandSectionProps) {
return (
<li key={index} className="ml-4">
<RefInternalLink
href={`/reference/cli/${subcommandDetails.id}`}
href={`/reference/cli#${subcommandDetails.id}`}
sectionSlug={subcommandDetails.id}
>
{subcommandDetails.title}
@@ -48,7 +48,7 @@ export async function SelfHostingReferencePage({
const name = REFERENCES[servicePath.replaceAll('-', '_')].name
return (
<ReferenceContentScrollHandler libPath={servicePath} version="latest" isLatestVersion={true}>
<ReferenceContentScrollHandler>
<SidebarSkeleton
menuId={menuId}
NavigationMenu={
+14
View File
@@ -171,6 +171,20 @@ const nextConfig = {
source: '/guides/database/replication/replication-faq',
destination: '/guides/database/replication/external-replication-faq',
permanent: true,
},
// Reference pages use hash anchors for sections; redirect the legacy
// path-style /reference/<lib>/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,
},
]
},
+14
View File
@@ -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/<lib>/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' },