fix(theme): render sidebar active state during ssr

dcb7a755 turned hasActiveLink from a computed into a ref fed only by a
non-immediate watch and onMounted, so is-active/has-active stopped
rendering in static HTML. This broke pre-hydration sidebar highlighting
and the docsearch crawler's lvl0 selector (section.has-active div h2),
which has indexed every record as the defaultValue since then.

Run the update once during setup with the hash check skipped: the server
and the client's initial render compute identical path-only state, so
hydration stays clean; the watch and onMounted refine with the real hash
after mount.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
pull/5391/head
Divyansh Singh 3 weeks ago
parent b0da3845f8
commit 005aa5c3e8

@ -83,12 +83,14 @@ export function useSidebarItemControl(
const isActiveLink = ref(false) const isActiveLink = ref(false)
const hasActiveLink = ref(false) const hasActiveLink = ref(false)
function updateActiveLink(): void { function updateActiveLink(skipHashCheck = false): void {
if (item.value.link) { if (item.value.link) {
isActiveLink.value = isActive( isActiveLink.value = isActive(
route.data.relativePath, route.data.relativePath,
route.hash, route.hash,
item.value.link item.value.link,
false,
skipHashCheck
) )
} else { } else {
isActiveLink.value = false isActiveLink.value = false
@ -105,15 +107,21 @@ export function useSidebarItemControl(
hasActiveLink.value = containsActiveLink( hasActiveLink.value = containsActiveLink(
route.data.relativePath, route.data.relativePath,
route.hash, route.hash,
item.value.items item.value.items,
skipHashCheck
) )
if (hasActiveLink.value) { if (hasActiveLink.value) {
nextTick(() => (collapsed.value = false)) nextTick(() => (collapsed.value = false))
} }
} }
watch([item, route], updateActiveLink) // runs during setup so active classes render in SSR output too; the hash
onMounted(updateActiveLink) // isn't known on the server (and may differ at hydration), so it's skipped
// until mounted
updateActiveLink(true)
watch([item, route], () => updateActiveLink())
onMounted(() => updateActiveLink())
const hasChildren = computed(() => { const hasChildren = computed(() => {
return !!(item.value.items && item.value.items.length) return !!(item.value.items && item.value.items.length)

@ -100,16 +100,17 @@ export function getFlatSideBarLinks(sidebar: SidebarItem[]): SidebarLink[] {
export function hasActiveLink( export function hasActiveLink(
path: string, path: string,
hash: string, hash: string,
items: SidebarItem | SidebarItem[] items: SidebarItem | SidebarItem[],
skipHashCheck = false
): boolean { ): boolean {
if (Array.isArray(items)) { if (Array.isArray(items)) {
return items.some((item) => hasActiveLink(path, hash, item)) return items.some((item) => hasActiveLink(path, hash, item, skipHashCheck))
} }
if (items.link && isActive(path, hash, items.link)) { if (items.link && isActive(path, hash, items.link, false, skipHashCheck)) {
return true return true
} }
if (items.items) { if (items.items) {
return hasActiveLink(path, hash, items.items) return hasActiveLink(path, hash, items.items, skipHashCheck)
} }
return false return false
} }

Loading…
Cancel
Save