From 8340e16930c779c20836c64b29bd5f55daa8bb0b Mon Sep 17 00:00:00 2001 From: Zeno Date: Fri, 31 Jul 2026 00:18:28 +0000 Subject: [PATCH] Fix search results linking to section anchors on title matches (RND-11916) Use the API `resultType` on page search results to decide the link target: page/title-level matches link to the top of the page, while section-level matches keep their section anchor. The section snippet is still shown as a preview. `resultType` is read defensively since it is not yet present in the published `@gitbook/api` types. Co-Authored-By: Claude Opus 4.8 --- .changeset/search-resulttype-page-linking.md | 5 +++++ .../[siteURL]/[siteData]/~gitbook/search/route.ts | 7 +++++++ .../src/components/Search/SearchPageResultItem.tsx | 12 +++++++++--- .../gitbook/src/components/Search/search-types.ts | 7 +++++++ 4 files changed, 28 insertions(+), 3 deletions(-) create mode 100644 .changeset/search-resulttype-page-linking.md diff --git a/.changeset/search-resulttype-page-linking.md b/.changeset/search-resulttype-page-linking.md new file mode 100644 index 000000000..6844b3078 --- /dev/null +++ b/.changeset/search-resulttype-page-linking.md @@ -0,0 +1,5 @@ +--- +"gitbook": patch +--- + +Search results from a page/title-level match now link to the top of the page instead of a section anchor; only section-level matches deep-link to their section. diff --git a/packages/gitbook/src/app/sites/dynamic/[mode]/[siteURL]/[siteData]/~gitbook/search/route.ts b/packages/gitbook/src/app/sites/dynamic/[mode]/[siteURL]/[siteData]/~gitbook/search/route.ts index 92f36e83b..172c83a72 100644 --- a/packages/gitbook/src/app/sites/dynamic/[mode]/[siteURL]/[siteData]/~gitbook/search/route.ts +++ b/packages/gitbook/src/app/sites/dynamic/[mode]/[siteURL]/[siteData]/~gitbook/search/route.ts @@ -143,6 +143,12 @@ function transformSitePageResult(args: { ? toEmbeddableLinkForPublishedContent(linker, spaceURL, pageItem.path) : linker.toLinkForContent(joinPathWithBaseURL(spaceURL, pageItem.path)); + // `resultType` marks whether the API matched the page at the title/page level or on a + // specific section. It is read defensively because it is not yet in the published + // `@gitbook/api` types; once present, page-level matches link to the top of the page. + const resultType = (pageItem as SearchPageResult & { resultType?: 'page' | 'section' }) + .resultType; + const page: ComputedPageResult = { type: 'page', id: `${spaceItem.id}/${pageItem.id}`, @@ -151,6 +157,7 @@ function transformSitePageResult(args: { pageId: pageItem.id, spaceId: spaceItem.id, score: pageItem.score, + resultType, breadcrumbs, }; diff --git a/packages/gitbook/src/components/Search/SearchPageResultItem.tsx b/packages/gitbook/src/components/Search/SearchPageResultItem.tsx index 9b70246bc..735c5a7e4 100644 --- a/packages/gitbook/src/components/Search/SearchPageResultItem.tsx +++ b/packages/gitbook/src/components/Search/SearchPageResultItem.tsx @@ -26,9 +26,15 @@ export const SearchPageResultItem = React.forwardRef(function SearchPageResultIt const language = useLanguage(); const bestSection = item.type === 'page' ? item.bestSection : undefined; - // When the section snippet is displayed, link to the section anchor so the - // link matches what the user sees. - const href = bestSection?.body ? bestSection.href : 'href' in item ? item.href : item.pathname; + // Section-level matches deep-link to their section anchor; page/title-level matches + // link to the top of the page even when a section snippet is shown as a preview. + const isPageLevelMatch = item.type === 'page' && item.resultType === 'page'; + const href = + bestSection?.body && !isPageLevelMatch + ? bestSection.href + : 'href' in item + ? item.href + : item.pathname; const emoji = 'emoji' in item ? item.emoji : undefined; const icon = 'icon' in item ? item.icon : undefined; diff --git a/packages/gitbook/src/components/Search/search-types.ts b/packages/gitbook/src/components/Search/search-types.ts index b1751edcf..3ed87deaa 100644 --- a/packages/gitbook/src/components/Search/search-types.ts +++ b/packages/gitbook/src/components/Search/search-types.ts @@ -20,6 +20,13 @@ export type ComputedPageResult = BaseComputedResult & { type: 'page'; pageId: string; spaceId: string; + /** + * Whether the API matched this result at the page/title level or on a specific + * section. Page-level matches link to the top of the page; section-level matches + * keep the section anchor. Optional to stay backward-compatible with API responses + * that do not yet return it. + */ + resultType?: 'page' | 'section'; breadcrumbs?: Array<{ icon?: IconName; label: string }>; /** The highest-scoring section for this page, used as a body snippet preview. */ bestSection?: {