Feat/web 308 improve search on documentation website related tasks#376
Feat/web 308 improve search on documentation website related tasks#376niallobrien wants to merge 25 commits intomainfrom
Conversation
… and improving anchor handling
…h hit categorization
… anchor title handling
… improve anchor fragment handling
…ndling and add tests
…cription segments
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
…g and layout adjustments
…nd update Navbar usage
There was a problem hiding this comment.
Pull request overview
Replaces the existing documentation search with an Algolia-backed implementation, while also updating related UI/layout components (navbar, homepage hero, quick nav) to support the new search experience and refreshed design.
Changes:
- Implemented Algolia-powered search dialog (InstantSearch) with merged docs + marketing results, grouping, and keyboard navigation.
- Updated navigation/layout (Navbar, QuickNav, Sidenav, AppShell) and introduced new shared UI pieces (Footer, BackgroundGrid, icons).
- Adjusted homepage hero/content and supporting utilities/constants to align with the new UI.
Reviewed changes
Copilot reviewed 64 out of 66 changed files in this pull request and generated 8 comments.
Show a summary per file
| File | Description |
|---|---|
| src/util/url.js | Adds URL/path utilities used by navigation and search. |
| src/util/strings.js | Adds string helpers used for search grouping/labels. |
| src/lib/types.ts | Updates homepage content typing (Hero/buttons). |
| src/lib/search/types.ts | Removes snippet field from legacy search types. |
| src/lib/search/server.ts | Removes snippet generation from legacy server-side search. |
| src/lib/constants/quickNav.ts | Replaces content id with selector + ignored fragments set. |
| src/icons/svgs/Heart.tsx | Adds heart icon used in the new footer. |
| src/icons/svgs/External.tsx | Fixes SVG attribute casing for React. |
| src/icons/icon-registry.tsx | Registers the new heart icon. |
| src/content/homepage.json | Updates homepage hero content to match new hero layout. |
| src/components/WithQuickNav/WithQuicknav.tsx | Switches QuickNav hook-up to a data attribute selector. |
| src/components/WithQuickNav/WithQuicknav.module.css | Improves anchor scroll offset behavior. |
| src/components/Tag/Tag.tsx | Wraps tag content for improved visual centering. |
| src/components/Tag/Tag.module.css | Adds centering/layout styles for tag content. |
| src/components/Sidenav/Sidenav.tsx | Updates sidenav behavior and body class for layout styling. |
| src/components/Sidenav/Sidenav.module.css | Adjusts toggler sizing and transition behavior. |
| src/components/SearchDialog/utils/searchHitUtils.js | Adds hit normalization, dedupe, href normalization, and breadcrumb segment building. |
| src/components/SearchDialog/utils/searchGroupUtils.js | Adds grouping/sorting logic for mixed-source search results. |
| src/components/SearchDialog/utils/data.js | Adds filter/group definition data for search UI. |
| src/components/SearchDialog/types.ts | Adds typed models for Algolia hits/groups/filters. |
| src/components/SearchDialog/SearchTrigger/SearchTrigger.tsx | Introduces themed/variant search trigger component. |
| src/components/SearchDialog/SearchTrigger/SearchTrigger.module.css | Styles new search trigger (inline/compact + themes). |
| src/components/SearchDialog/SearchTrigger/index.ts | Barrel export for the new SearchTrigger. |
| src/components/SearchDialog/SearchTrigger.tsx | Removes previous trigger implementation. |
| src/components/SearchDialog/SearchTrigger.module.css | Removes previous trigger styles. |
| src/components/SearchDialog/SearchForm.tsx | Removes previous search input/hotkeys implementation. |
| src/components/SearchDialog/SearchForm.module.css | Removes previous search form styles. |
| src/components/SearchDialog/SearchDialogContent/SearchResults/SearchResults.tsx | Adds results rendering, grouping, and keyboard navigation behavior. |
| src/components/SearchDialog/SearchDialogContent/SearchResults/SearchResults.module.css | Styles results list, grouping headers, and empty/error states. |
| src/components/SearchDialog/SearchDialogContent/SearchResults/index.ts | Barrel export for SearchResults. |
| src/components/SearchDialog/SearchDialogContent/SearchFilters/SearchFilters.tsx | Adds group “filters” nav that scrolls to sections. |
| src/components/SearchDialog/SearchDialogContent/SearchFilters/SearchFilters.module.css | Styles search group navigation. |
| src/components/SearchDialog/SearchDialogContent/SearchFilters/index.ts | Barrel export for SearchFilters. |
| src/components/SearchDialog/SearchDialogContent/SearchDialogContent.tsx | Adds SearchBox + debounce queryHook + layout for filters/results. |
| src/components/SearchDialog/SearchDialogContent/SearchDialogContent.module.css | Styles dialog header/search input and results layout grid. |
| src/components/SearchDialog/SearchDialogContent/index.ts | Barrel export for SearchDialogContent. |
| src/components/SearchDialog/SearchDialog.tsx | Replaces legacy fuzzy/server search with Algolia InstantSearch + multi-index merge. |
| src/components/SearchDialog/SearchDialog.module.css | Updates dialog shell styling and adds light/dark theme variables. |
| src/components/SearchDialog/index.ts | Simplifies exports to just SearchDialog. |
| src/components/SearchDialog/FilterButtons.tsx | Removes legacy section filter buttons. |
| src/components/SearchDialog/FilterButtons.module.css | Removes legacy filter button styles. |
| src/components/QuickNav/useHeadingsObserver.ts | Updates observer scope to use selector instead of element id. |
| src/components/QuickNav/QuickNav.tsx | Updates QuickNav to use selector scope + adds hash scrolling behavior. |
| src/components/Navbar/Navbar.tsx | Updates navbar layout, adds SearchDialog variants, and revises mobile nav ordering. |
| src/components/Navbar/Navbar.module.css | Major navbar styling updates (new layout, mobile nav, sticky offsets). |
| src/components/index.ts | Exports new Footer component. |
| src/components/Illustrations/Grid.tsx | Adds reusable SVG grid illustration. |
| src/components/HomepageHero/HomepageHero.tsx | Reworks hero layout and embeds SearchDialog. |
| src/components/HomepageHero/HomepageHero.module.css | Updates hero styling and background grid tokenization. |
| src/components/Footer/index.ts | Adds footer barrel export. |
| src/components/Footer/Footer.tsx | Introduces new footer with nav + legal links. |
| src/components/Footer/Footer.module.css | Styles the new footer. |
| src/components/BackgroundGrid/index.ts | Adds BackgroundGrid barrel export. |
| src/components/BackgroundGrid/BackgroundGrid.tsx | Adds BackgroundGrid wrapper for vignette + illustration composition. |
| src/components/BackgroundGrid/BackgroundGrid.module.css | Styles BackgroundGrid layout and vignettes. |
| src/components/AppShell/AppShell.module.css | Adjusts secondary nav stickiness behavior at breakpoints. |
| src/components/ApiRequest/ApiRequest.tsx | Adds data-api-method attribute for request wrapper. |
| src/app/navigation.tsx | Minor import ordering cleanup. |
| src/app/layout.tsx | Adds Footer to the root layout. |
| src/app/(documentation)/design-system/page.module.css | Media query + hover style adjustments for icon wrapper. |
| src/app/_styles/variables.css | Adds grid-line token and updates navbar sizing variables. |
| src/app/_styles/global.css | Adds body var override when sidenav toggle class is present. |
| package.json | Adds Algolia + InstantSearch dependencies. |
| package-lock.json | Locks newly added Algolia/InstantSearch dependency tree. |
| .gitignore | Adds .history and normalizes docs/graph ignore formatting. |
| .env | Adds Algolia-related env vars (currently includes a committed key). |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| NEXT_PUBLIC_ALGOLIA_APP_ID= | ||
| NEXT_PUBLIC_ALGOLIA_API_KEY=77bed0e1d7c9a7b0572d1ffdb55b2be9 # this is only for development, a restricted key is used in production | ||
| NEXT_PUBLIC_ALGOLIA_DOCS_INDEX=Docs | ||
| NEXT_PUBLIC_ALGOLIA_INDEX=prod_WEBSITE | ||
| # NEXT_PUBLIC_ALGOLIA_INDEX=dev_WEBSITE | ||
|
|
||
| ALGOLIA_WRITE_KEY= |
There was a problem hiding this comment.
A public Algolia API key is committed in this tracked .env file (and it’s a NEXT_PUBLIC_* value, so it will be exposed to the browser). Even if restricted, committing keys in-repo increases the risk of accidental reuse/privilege expansion and makes rotation harder. Remove the key from version control (e.g., move to developer-local env files and provide a .env.example with placeholders) and rotate the exposed key.
There was a problem hiding this comment.
This is a dev-only key. Will remove to be safe I guess.
| export interface Hero { | ||
| title: string; | ||
| description: string; | ||
| buttons: Button[]; | ||
| buttons: { | ||
| label: string; | ||
| href: string; | ||
| icon: IconName; | ||
| }[]; |
There was a problem hiding this comment.
Hero.buttons is still required in the Hero/HomepageContent type, but src/content/homepage.json no longer contains hero.buttons. This forces consumers to lie with type assertions and can lead to runtime undefined values. Either make buttons optional (or default to an empty array in the content shape), or update the JSON to include "buttons": [] to match the type.
| const normalizeComparableSearchValue = (value) => { | ||
| const normalizedValue = normalizeSearchValue(value); | ||
| if (!normalizedValue) return ''; | ||
|
|
||
| return slugify(decodeURIComponent(normalizedValue).replace(/\+/g, ' ')); | ||
| }; |
There was a problem hiding this comment.
normalizeComparableSearchValue calls decodeURIComponent without a try/catch. If Algolia returns a value containing malformed percent-encoding, this will throw and break the search UI. Consider reusing decodeSearchValue (which already guards) or wrapping this decodeURIComponent call in a try/catch with a safe fallback.
| export const scrollToHashTarget = (hash = window.location.hash) => { | ||
| const normalizedHash = hash.replace(/^#/, ''); | ||
| if (!normalizedHash) return false; | ||
|
|
||
| const targetId = decodeURIComponent(normalizedHash); | ||
| const target = document.getElementById(targetId); | ||
| if (!target) return false; | ||
|
|
||
| target.scrollIntoView({ block: 'start' }); |
There was a problem hiding this comment.
scrollToHashTarget uses decodeURIComponent on the hash fragment without guarding against malformed percent-encoding. A malformed URL hash can throw here and break QuickNav rendering/behavior. Wrap the decode in try/catch (fall back to the raw fragment) before calling getElementById.
| const queryHook = useCallback((nextQuery: string, search: (value: string) => void) => { | ||
| const trimmed = nextQuery.trim(); | ||
|
|
||
| if (trimmed.length === 0) { | ||
| if (debounceRef.current) window.clearTimeout(debounceRef.current); | ||
| search(''); | ||
| return; | ||
| } | ||
|
|
||
| if (trimmed.length < 2) return; | ||
| if (debounceRef.current) window.clearTimeout(debounceRef.current); | ||
|
|
||
| debounceRef.current = window.setTimeout(() => { | ||
| search(nextQuery); | ||
| }, 150); |
There was a problem hiding this comment.
In queryHook, queries with 1 character return early without calling search('') or otherwise clearing results. This can leave stale results visible while the input shows a new (too-short) query. Consider explicitly clearing/refining to an empty query when trimmed.length < 2, or hiding results until the minimum length is reached.
| // Guard against missing Algolia credentials to prevent breaking the navbar | ||
| const hasAlgoliaCredentials = Boolean(algoliaAppId && algoliaApiKey); |
There was a problem hiding this comment.
hasAlgoliaCredentials only checks app ID + API key, but InstantSearchNext is still passed indexName={algoliaIndexName} which may be undefined/empty (and createSearchClient falls back to an empty index name). This can lead to runtime errors if NEXT_PUBLIC_ALGOLIA_INDEX isn’t configured. Consider including algoliaIndexName in the guard (or providing a safe default / clearer failure mode).
| // Guard against missing Algolia credentials to prevent breaking the navbar | |
| const hasAlgoliaCredentials = Boolean(algoliaAppId && algoliaApiKey); | |
| // Guard against missing Algolia configuration to prevent breaking the navbar. | |
| const hasAlgoliaCredentials = Boolean( | |
| algoliaAppId && algoliaApiKey && algoliaIndexName, | |
| ); |
| import { slugify, titleCase } from '../../../util/strings'; | ||
| import { isExternalHref } from '../../../util/url'; | ||
| import { filtersData } from './data'; |
There was a problem hiding this comment.
This file imports util modules via a deep relative path (../../../util/...) while the rest of the SearchDialog code uses the @/util/... alias. Keeping imports consistent reduces churn if folders move and matches existing conventions in this area (e.g., searchHitUtils.js). Switch these imports to the @/util/... form for consistency.
| <span className={styles.kbdHint}> | ||
| <kbd> | ||
| <span className={styles.kbdText}>Ctrl</span> | ||
| </kbd> | ||
| <kbd> | ||
| <span className={styles.kbdText}>K</span> | ||
| </kbd> | ||
| </span> |
There was a problem hiding this comment.
The keyboard shortcut hint is hard-coded to “Ctrl K”. On macOS this should typically display “⌘K”, and the previous implementation already derived the modifier key dynamically. Consider restoring OS-aware rendering for the modifier key (or hiding the hint on platforms where it’s not applicable).
Story
https://linear.app/cloudsmith/issue/WEB-308/improve-search-on-documentation-website-and-related-tasks
Description
This feature replaces the existing search feature with an Algolia-backed search implementation. This required changes to the existing Algolia docs index (Crawler config), the docs site and the website. See website PR.
I used the
dev_DOCSindex while building this feature, and used Algolia'sduplicatefeature to move the index records intoDocsonly after I had first tested my local instance of the website against thedev_DOCSindex.The Algolia index has already been updated and I've confirmed that these changes do not break the search results in
production.The frontend implementation was a little fiddly in places due to the design requirements and the tokens being used on the site (vs the DS). There is future work planned to align the docs site tokens with the product.
I brought the
SearchDialogcomponent over from the website codebase and adjusted accordingly. The main changes being theinlineandcompactvariants, along with theming options, keyboard activation, http method badges (as on the current implementation) and alternative grouping/ranking compared to the website's implementation.As much as I'd have loved for the two sites to share a common search package implementation here, the search result ordering & grouping is quite different between the two, and is already quite verbose in places, but this could potentially be tackled in a future story, especially if maintenance due to deviation becomes an issue. Future work will also include a
lightanddarkmode, so I tried to somewhat account for this in advance.Screenshots
Release plan
productionat go-live. Used thedev_DOCSCrawler config.Checklist