Add client side search to docs as Algolia fallback#482
Conversation
📝 WalkthroughWalkthroughAdds Lunr local search and dependencies, implements a DocSearch-based SearchBar (lazy modal, transform hooks), swizzles NavbarContent to health-check Algolia and fall back to Lunr, and adds related CSS and README documentation. Changes
Sequence Diagram(s)sequenceDiagram
participant User
participant Navbar as NavbarSearchSection
participant Health as Algolia Health Check
participant Provider as Search Provider
participant Modal as DocSearch Modal
User->>Navbar: load page / open navbar
Navbar->>Health: isAlgoliaReachable(config) (with timeout)
Health->>AlgoliaAPI: ping / health endpoint
alt Algolia reachable
AlgoliaAPI-->>Health: success
Health-->>Navbar: reachable
Navbar->>Provider: set provider = algolia (cache & localStorage)
else unreachable or timed out
Health-->>Navbar: unreachable
Navbar->>Provider: set provider = lunr (cache & localStorage)
end
User->>Navbar: activate search (click / hotkey)
Navbar->>Modal: open search modal using Provider
Modal-->>User: render search UI & results (Algolia or Lunr)
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Poem
🚥 Pre-merge checks | ✅ 1 | ❌ 2❌ Failed checks (2 warnings)
✅ Passed checks (1 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@website/docusaurus.config.ts`:
- Line 108: Remove the stale comment "// COMMENTED OUT: Testing local search
only" that sits immediately above the active algolia configuration block (the
algolia object in docusaurus.config.ts); locate the algolia config and delete
that misleading comment so the code no longer suggests the block is commented
out or only for local testing.
In `@website/src/css/custom.css`:
- Around line 361-370: Update the light-theme Algolia highlight color to a
higher-contrast value: for the selectors [data-theme='light'] .DocSearch-Hits
mark and [data-theme='light'] .DocSearch-Hit mark replace the current color
(`#ff7a18`) with a darker, more readable hex (for example `#b45309` or `#c2410f`) so
highlights are accessible on white backgrounds while keeping the dark-theme
rules unchanged.
In `@website/src/theme/Navbar/Content/index.tsx`:
- Around line 111-129: The component currently initializes provider state to
'algolia', causing a blocked UX until isAlgoliaReachable completes; change the
useState call that defines provider to default to 'lunr' (so Lunr is used
immediately), and keep the existing useEffect that calls
isAlgoliaReachable(algoliaConfig ?? {}) and calls setProvider(reachable ?
'algolia' : 'lunr') to promote to Algolia only when reachable; update any
related logic that assumes initial 'algolia' to accept the new default.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 5c429076-13d4-4cc1-9a32-8bb79ad31c09
⛔ Files ignored due to path filters (1)
website/package-lock.jsonis excluded by!**/package-lock.json
📒 Files selected for processing (7)
website/docusaurus.config.tswebsite/package.jsonwebsite/src/css/custom.csswebsite/src/theme/Navbar/Content/index.tsxwebsite/src/theme/Navbar/Content/styles.module.csswebsite/src/theme/SearchBar/index.tsxwebsite/src/theme/SearchBar/styles.css
There was a problem hiding this comment.
🧹 Nitpick comments (2)
website/README.md (2)
79-91: Consider adding context links and rationale for the swizzling approach.While the documentation explains what was swizzled and how to maintain it, adding brief context about why swizzling was necessary and linking to Docusaurus swizzling documentation would help future maintainers understand the design decision.
📚 Suggested enhancement for additional context
Add after line 79:
The documentation site uses a hybrid search setup (Algolia + Lunr fallback) with swizzled components. **Why swizzling?** Custom logic was required to detect Algolia availability at runtime and dynamically switch to Lunr when Algolia is geo-restricted or unavailable. This cannot be achieved with standard Docusaurus configuration alone. **Reference:** [Docusaurus Swizzling Documentation](https://docusaurus.io/docs/swizzling)🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@website/README.md` around lines 79 - 91, Add a short "Why swizzling?" paragraph to the README describing the rationale for swizzling (need for runtime detection of Algolia availability and dynamic fallback to Lunr) and include a link to the Docusaurus swizzling docs; reference the swizzled components (src/theme/SearchBar/index.tsx and src/theme/Navbar/Content/index.tsx) and briefly note that the custom logic lives in those files and is the reason standard Docusaurus config couldn't be used.
93-103: Consider adding search-specific testing steps to the upgrade checklist.The current validation steps (lines 100-102) cover general build verification but don't explicitly test the hybrid search functionality. Consider adding search-specific validation steps to ensure both search providers work correctly after upgrades.
📋 Suggested enhancement to the upgrade checklist
4. Validate behavior locally: - `npm run start` - `npm run build` + - Test Algolia search functionality in the navbar + - Simulate Algolia unavailability to verify Lunr fallback (e.g., block Algolia endpoints in browser DevTools)🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@website/README.md` around lines 93 - 103, The upgrade checklist lacks search-specific validation for the hybrid search setup; update the README checklist to add steps that exercise the swizzled SearchBar and both search providers (e.g., `@docusaurus/theme-search-algolia/src/theme/SearchBar/*`) after `npm run start`/`npm run build` by: 1) performing manual queries in the dev site to verify results from both Algolia and the local/hybrid provider, 2) verifying recent content appears in search indexes and that incremental updates are reflected, and 3) checking the swizzled Navbar/SearchBar UI behaviors (keyboard navigation, suggestions, no JS fallback) to ensure the hybrid search integration still functions.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In `@website/README.md`:
- Around line 79-91: Add a short "Why swizzling?" paragraph to the README
describing the rationale for swizzling (need for runtime detection of Algolia
availability and dynamic fallback to Lunr) and include a link to the Docusaurus
swizzling docs; reference the swizzled components (src/theme/SearchBar/index.tsx
and src/theme/Navbar/Content/index.tsx) and briefly note that the custom logic
lives in those files and is the reason standard Docusaurus config couldn't be
used.
- Around line 93-103: The upgrade checklist lacks search-specific validation for
the hybrid search setup; update the README checklist to add steps that exercise
the swizzled SearchBar and both search providers (e.g.,
`@docusaurus/theme-search-algolia/src/theme/SearchBar/*`) after `npm run
start`/`npm run build` by: 1) performing manual queries in the dev site to
verify results from both Algolia and the local/hybrid provider, 2) verifying
recent content appears in search indexes and that incremental updates are
reflected, and 3) checking the swizzled Navbar/SearchBar UI behaviors (keyboard
navigation, suggestions, no JS fallback) to ensure the hybrid search integration
still functions.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: a978b7c7-9f26-4a6f-b86f-a783c6219fe0
📒 Files selected for processing (2)
website/README.mdwebsite/docusaurus.config.ts
🚧 Files skipped from review as they are similar to previous changes (1)
- website/docusaurus.config.ts
a5anka
left a comment
There was a problem hiding this comment.
Health-check fires an actual search query on every page load
The isAlgoliaReachable() function sends a real search query ("health-check", hitsPerPage: 1) against the Algolia index on every page load. This has two costs:
- Algolia operations — each page view consumes a search operation, which counts against the plan quota
- Latency — the search bar defaults to Algolia initially, then swaps to Lunr after up to 5s if unreachable, causing a visual flash
Suggestions:
Use a no-op query to avoid consuming a real search operation:
body: JSON.stringify({ query: '', hitsPerPage: 0 }),And cache the result in localStorage so subsequent navigations skip the network call entirely:
const [provider, setProvider] = useState<SearchProvider | null>(() => {
try {
return (localStorage.getItem('search-provider') as SearchProvider) || null;
} catch { return null; }
});
// After determining provider in the effect:
localStorage.setItem('search-provider', reachable ? 'algolia' : 'lunr');This avoids redundant requests and eliminates the UI flicker on repeat visits.
a5anka
left a comment
There was a problem hiding this comment.
Health-check fires an actual search query on every page load
The isAlgoliaReachable() function sends a real search query ("health-check", hitsPerPage: 1) against the Algolia index on every single page load. This creates three problems:
Problem 1: Inflated search analytics
Every page view registers as a search operation in Algolia's dashboard. This pollutes search analytics — metrics like total searches, search-per-user, and zero-results rate all become unreliable because they include thousands of synthetic "health-check" queries that no real user initiated. This makes it impossible to trust the data when evaluating search quality or content gaps.
Problem 2: Unnecessary Algolia quota consumption
Each health-check counts against the plan's search operation quota. For a documentation site with moderate traffic (e.g., 10k page views/month), that's 10k wasted operations purely for connectivity detection — none of which serve actual user searches.
Problem 3: UI flicker during detection
The component initializes with provider = 'algolia', so the Algolia SearchBar renders immediately on mount. If Algolia turns out to be unreachable, the component swaps to the Lunr SearchBar after up to 5 seconds. During this window:
- The user sees the Algolia search button, which may not work if clicked
- The search UI visually swaps once the check completes — a jarring flash
Recommended solution
Step 1 — Use the List Indices endpoint instead of a search query.
The current approach hits the /query endpoint, which always counts as a search operation and pollutes analytics — even with an empty query or hitsPerPage: 0. Instead, use the List Indices metadata endpoint, which validates connectivity and credentials without triggering any search operation:
async function isAlgoliaReachable(algolia: {
appId?: string;
apiKey?: string;
indexName?: string;
}): Promise<boolean> {
if (!algolia.appId || !algolia.apiKey || !algolia.indexName) {
return false;
}
const controller = new AbortController();
const timeoutId = window.setTimeout(() => controller.abort(), 5000);
try {
const response = await fetch(
`https://${algolia.appId}-dsn.algolia.net/1/indexes`,
{
method: 'GET',
headers: {
'X-Algolia-Application-Id': algolia.appId,
'X-Algolia-API-Key': algolia.apiKey,
},
signal: controller.signal,
},
);
return response.ok;
} catch {
return false;
} finally {
window.clearTimeout(timeoutId);
}
}Why this works: The /1/indexes endpoint is a lightweight metadata call that returns a list of indices for the application. It confirms that:
- The Algolia host is reachable (network/DNS level)
- The app ID and API key are valid (authentication level)
- The service is responding correctly (
200 OK)
It does not count as a search operation, so search analytics remain clean and accurate.
Step 2 — Cache the provider decision in localStorage to eliminate flicker and redundant requests.
On the first visit, the health-check runs and determines the provider. On all subsequent page loads (and SPA navigations), the cached value is used immediately — no network call, no flicker. The cache is refreshed periodically (e.g., every 10 minutes) to detect connectivity changes without hammering the API on every page load.
const CACHE_KEY = 'docs-search-provider';
const CACHE_TTL = 10 * 60 * 1000; // 10 minutes
function getCachedProvider(): SearchProvider | null {
try {
const raw = localStorage.getItem(CACHE_KEY);
if (!raw) return null;
const { provider, timestamp } = JSON.parse(raw);
if (Date.now() - timestamp > CACHE_TTL) return null; // expired
return provider as SearchProvider;
} catch {
return null;
}
}
function setCachedProvider(provider: SearchProvider): void {
try {
localStorage.setItem(
CACHE_KEY,
JSON.stringify({ provider, timestamp: Date.now() }),
);
} catch {
// localStorage unavailable (private browsing, etc.) — fail silently
}
}Then in the component:
function NavbarSearchSection() {
const { siteConfig } = useDocusaurusContext();
const [provider, setProvider] = useState<SearchProvider | null>(
() => getCachedProvider(),
);
useEffect(() => {
// Skip the network check if we have a fresh cached result
if (provider !== null) return;
const algoliaConfig = siteConfig.themeConfig?.algolia as
| { appId?: string; apiKey?: string; indexName?: string }
| undefined;
let cancelled = false;
isAlgoliaReachable(algoliaConfig ?? {}).then((reachable) => {
if (cancelled) return;
const resolved = reachable ? 'algolia' : 'lunr';
setCachedProvider(resolved);
setProvider(resolved);
});
return () => { cancelled = true; };
}, [provider, siteConfig.themeConfig]);
// While the first-ever check is in flight, render nothing in the search slot
// to avoid showing a non-functional Algolia button
if (provider === null) return null;
const ActiveSearchBar =
provider === 'algolia' ? AlgoliaSearchBar : LunrSearchBar;
return (
<NavbarSearch className={styles.navbarSearch}>
<ActiveSearchBar key={provider} />
</NavbarSearch>
);
}What this achieves:
- Clean analytics: search metrics only reflect real user searches, not synthetic health-checks
- First visit: brief empty search slot (~200-500ms) while the check runs, then the correct provider appears — no misleading UI
- Repeat visits: instant render from cache, zero network calls
- Connectivity changes: cache expires after 10 minutes, so if Algolia becomes available (or goes down), the site adapts within that window
- Private browsing / no localStorage: falls back to the network check every time (same as current behavior, minus the flicker since
providerstarts asnull)
Minor: Remove redundant direct
|
bb8100c to
2bbfb0a
Compare
There was a problem hiding this comment.
Actionable comments posted: 5
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@website/src/css/custom.css`:
- Around line 362-369: The highlighted term color for light theme in the
selector block targeting [data-theme='light'] .DocSearch-Hits mark and
[data-theme='light'] .DocSearch-Hit mark is too low-contrast; update those
light-theme rules to use a darker accent color (while preserving background:
transparent, text-decoration: none, and font-weight: 600) and leave the existing
`#ff7a18` value for the dark-theme selectors ([data-theme='dark'] .DocSearch-Hits
mark, [data-theme='dark'] .DocSearch-Hit mark); ensure the new light-theme color
provides sufficient contrast against the light result rows and maintain the
!important specificity used in the original declarations.
In `@website/src/theme/Navbar/Content/index.tsx`:
- Line 25: The CACHE_TTL constant's numeric value (60 * 60 * 1000) equals 1 hour
but the comment says 10 minutes; either update the numeric value to 10 * 60 *
1000 if you want a 10-minute TTL or change the comment to "1 hour" to reflect
the current value—edit the CACHE_TTL declaration and its inline comment so they
match and avoid confusion.
- Around line 63-75: The current health-check uses GET /1/indexes which requires
listIndexes ACL and can 403 with DocSearch search-only keys; change the fetch in
the Navbar Content health-check to POST to /1/indexes/{indexName}/query (use the
existing Algolia index identifier from the algolia config, e.g.
algolia.indexName or algolia.index) with method 'POST', add 'Content-Type':
'application/json' to headers and send an empty query body (e.g. {} or {
"query": "" }) so the request only requires the search ACL, then keep returning
response.ok to indicate success; update the fetch call surrounding code (the
function performing this fetch in index.tsx) and preserve controller.signal for
cancellation.
In `@website/src/theme/Navbar/Content/styles.module.css`:
- Line 14: Stylelint flags the selector :global(.navbar__items--right) >
:last-child with selector-pseudo-class-no-unknown; add an inline suppression
immediately before that rule using a stylelint comment (e.g. /*
stylelint-disable-next-line selector-pseudo-class-no-unknown */) so the
:global(.navbar__items--right) > :last-child rule is allowed; ensure the comment
sits directly above the selector block containing :global(.navbar__items--right)
> :last-child so CI no longer fails.
In `@website/src/theme/SearchBar/index.tsx`:
- Around line 46-47: The type for contextualSearch in the SearchBar theme props
is incorrect—change the declaration of contextualSearch from string to boolean
in the interface so it matches Docusaurus v3/DocSearchV4 expectations; update
the type on the property where it’s declared (the contextualSearch field) and
ensure any places that cast or pass siteConfig.themeConfig.algolia (e.g., the
code that casts to DocSearchV4Props) treat contextualSearch as a boolean (and
adjust any default/usage accordingly).
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: df6c820f-7d58-4a87-adaa-ab55640fae7d
⛔ Files ignored due to path filters (1)
website/package-lock.jsonis excluded by!**/package-lock.json
📒 Files selected for processing (9)
website/README.mdwebsite/docusaurus.config.tswebsite/package.jsonwebsite/src/css/custom.csswebsite/src/pages/index.tsxwebsite/src/theme/Navbar/Content/index.tsxwebsite/src/theme/Navbar/Content/styles.module.csswebsite/src/theme/SearchBar/index.tsxwebsite/src/theme/SearchBar/styles.css
🚧 Files skipped from review as they are similar to previous changes (4)
- website/package.json
- website/README.md
- website/docusaurus.config.ts
- website/src/theme/SearchBar/styles.css
| [data-theme='light'] .DocSearch-Hits mark, | ||
| [data-theme='light'] .DocSearch-Hit mark, | ||
| [data-theme='dark'] .DocSearch-Hits mark, | ||
| [data-theme='dark'] .DocSearch-Hit mark { | ||
| background: transparent !important; | ||
| color: #ff7a18 !important; | ||
| text-decoration: none !important; | ||
| font-weight: 600; |
There was a problem hiding this comment.
Highlight color is too low-contrast in light mode.
Using #ff7a18 on light result rows makes marked terms harder to read. Use a darker accent in light theme and keep the current color in dark theme.
🎯 Suggested tweak
-[data-theme='light'] .DocSearch-Hits mark,
-[data-theme='light'] .DocSearch-Hit mark,
-[data-theme='dark'] .DocSearch-Hits mark,
-[data-theme='dark'] .DocSearch-Hit mark {
+[data-theme='light'] .DocSearch-Hits mark,
+[data-theme='light'] .DocSearch-Hit mark {
background: transparent !important;
- color: `#ff7a18` !important;
+ color: `#b54708` !important;
text-decoration: none !important;
font-weight: 600;
}
+
+[data-theme='dark'] .DocSearch-Hits mark,
+[data-theme='dark'] .DocSearch-Hit mark {
+ background: transparent !important;
+ color: `#ff7a18` !important;
+ text-decoration: none !important;
+ font-weight: 600;
+}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| [data-theme='light'] .DocSearch-Hits mark, | |
| [data-theme='light'] .DocSearch-Hit mark, | |
| [data-theme='dark'] .DocSearch-Hits mark, | |
| [data-theme='dark'] .DocSearch-Hit mark { | |
| background: transparent !important; | |
| color: #ff7a18 !important; | |
| text-decoration: none !important; | |
| font-weight: 600; | |
| [data-theme='light'] .DocSearch-Hits mark, | |
| [data-theme='light'] .DocSearch-Hit mark { | |
| background: transparent !important; | |
| color: `#b54708` !important; | |
| text-decoration: none !important; | |
| font-weight: 600; | |
| } | |
| [data-theme='dark'] .DocSearch-Hits mark, | |
| [data-theme='dark'] .DocSearch-Hit mark { | |
| background: transparent !important; | |
| color: `#ff7a18` !important; | |
| text-decoration: none !important; | |
| font-weight: 600; | |
| } |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@website/src/css/custom.css` around lines 362 - 369, The highlighted term
color for light theme in the selector block targeting [data-theme='light']
.DocSearch-Hits mark and [data-theme='light'] .DocSearch-Hit mark is too
low-contrast; update those light-theme rules to use a darker accent color (while
preserving background: transparent, text-decoration: none, and font-weight: 600)
and leave the existing `#ff7a18` value for the dark-theme selectors
([data-theme='dark'] .DocSearch-Hits mark, [data-theme='dark'] .DocSearch-Hit
mark); ensure the new light-theme color provides sufficient contrast against the
light result rows and maintain the !important specificity used in the original
declarations.
| type SearchProvider = 'algolia' | 'lunr'; | ||
|
|
||
| const CACHE_KEY = 'docs-search-provider'; | ||
| const CACHE_TTL = 60 * 60 * 1000; // 10 minutes |
There was a problem hiding this comment.
TTL value and comment are inconsistent.
Line 25 computes 1 hour, not 10 minutes. Please align the value or the comment to avoid incorrect cache behavior assumptions.
🛠️ If 10 minutes is intended
-const CACHE_TTL = 60 * 60 * 1000; // 10 minutes
+const CACHE_TTL = 10 * 60 * 1000; // 10 minutes📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const CACHE_TTL = 60 * 60 * 1000; // 10 minutes | |
| const CACHE_TTL = 10 * 60 * 1000; // 10 minutes |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@website/src/theme/Navbar/Content/index.tsx` at line 25, The CACHE_TTL
constant's numeric value (60 * 60 * 1000) equals 1 hour but the comment says 10
minutes; either update the numeric value to 10 * 60 * 1000 if you want a
10-minute TTL or change the comment to "1 hour" to reflect the current
value—edit the CACHE_TTL declaration and its inline comment so they match and
avoid confusion.
| const response = await fetch( | ||
| `https://${algolia.appId}-dsn.algolia.net/1/indexes`, | ||
| { | ||
| method: 'GET', | ||
| headers: { | ||
| 'X-Algolia-Application-Id': algolia.appId, | ||
| 'X-Algolia-API-Key': algolia.apiKey, | ||
| }, | ||
| signal: controller.signal, | ||
| }, | ||
| ); | ||
|
|
||
| return response.ok; |
There was a problem hiding this comment.
🧩 Analysis chain
🌐 Web query:
Algolia GET /1/indexes ACL requirements listIndexes documentation
💡 Result:
For Algolia’s List indices endpoint (GET /1/indexes), the required API key ACL is:
listIndexes(required) — otherwise you’ll get 403 Forbidden. The response also respects any index restrictions attached to the API key (you’ll only see allowed indices). [1]
REST doc (includes GET /1/indexes and required headers): [1]
🏁 Script executed:
cd website && find . -name "index.tsx" | head -20Repository: wso2/agent-manager
Length of output: 346
🏁 Script executed:
find . -path "*/theme/Navbar/Content/index.tsx" -type fRepository: wso2/agent-manager
Length of output: 106
🏁 Script executed:
cat -n ./website/src/theme/Navbar/Content/index.tsx | sed -n '60,80p'Repository: wso2/agent-manager
Length of output: 703
🌐 Web query:
Algolia POST /1/indexes indexName query endpoint ACL requirements API key permissions
💡 Result:
For POST /1/indexes/{indexName}/query (Search an index), the API key must include the ACL permission search (that’s the required ACL for this endpoint). [1]
Notes on which keys work:
- Search-only API key works because it’s meant for “search your data” (and is generally the one you can safely use client-side, optionally further restricted via secured API keys/restrictions). [2]
- Admin API key also works (it has broad permissions), but it should remain confidential and is not appropriate for frontend use. [2]
Sources
[1] Algolia REST API — Search an index (/1/indexes/{indexName}/query) (Required ACL: search) https://www.algolia.com/doc/rest-api/search/search-single-index
[2] Algolia Guide — API keys (Search-only vs Admin key, ACL concepts) https://www.algolia.com/doc/guides/security/api-keys/
🌐 Web query:
Algolia DocSearch public search API key ACL permissions restrictions listIndexes
💡 Result:
For Algolia DocSearch, the key you ship to the browser is meant to be a Search API key (public, “search-only”), not an admin key. DocSearch’s API reference explicitly says apiKey is “Your Algolia Search API key.” [2]
ACL permissions
- Recommended for a public DocSearch key:
searchonly. (That’s sufficient to query the index.) [1][2] - Do not enable
listIndexeson a public key.listIndexesis a separate ACL right that “allow[s] to get a list of all existing indices,” which is typically unnecessary for DocSearch and increases exposure if the key leaks (it will be public). [1] - Likewise, only enable
browseif you truly need it; it grants access to retrieve index data via the Browse endpoint. [1]
Key restrictions (highly recommended for public/front-end keys)
Use Algolia’s API key restrictions to limit blast radius, especially:
- Indices restriction: restrict the key to only the DocSearch index/indices it must query. [1]
- HTTP referrers restriction: restrict usage to your documentation site origins. [1]
- (Optional) rate limits, expiration, and enforced query parameters. [1]
Bottom line: a DocSearch public key should normally be search (only) and restricted to specific DocSearch indices + allowed referrers; it should not include listIndexes. [1][2]
🌐 Web query:
Algolia POST /1/indexes/{indexName}/query health check minimal query best practices
💡 Result:
For health checks, Algolia generally recommends using the Monitoring API (it’s explicitly meant to “check the status of your Algolia infrastructure”) rather than spending search operations on /1/indexes/{indexName}/query. The simplest infra check is: GET https://status.algolia.com/1/status. [1]
If you also want an application-level “can I query this index with my API key?” check (DNS/TLS/auth/index exists/routing), keep the query as close to “no work, no payload” as possible:
Minimal query health-check (single index)
Use the Search single index endpoint (POST /1/indexes/{indexName}/query) and send parameters via params in the JSON body. [2]
Key best practices:
- Use an empty query (
query=). (Empty query is valid and commonly used.) [3] - Return zero hits (
hitsPerPage=0) to minimize work/response size. [4] - Disable analytics for the health check (
analytics=false) so it doesn’t pollute dashboards. [5] - Reduce response size with
responseFields(it controls which fields are included in the search response). [6]
Example (curl):
curl --request POST \
--url "https://{APP_ID}.algolia.net/1/indexes/{INDEX_NAME}/query" \
--header "x-algolia-application-id: {APP_ID}" \
--header "x-algolia-api-key: {SEARCH_API_KEY}" \
--header "content-type: application/json" \
--data '{
"params": "query=&hitsPerPage=0&analytics=false&responseFields=%5B%22processingTimeMS%22%2C%22nbHits%22%5D"
}'What to assert:
- HTTP status is 2xx
- Response contains (for example)
processingTimeMS(and optionallynbHits) per yourresponseFields.
Sources
- Algolia Monitoring API status endpoint (
GET https://status.algolia.com/1/status). (algolia.com) - Search single index endpoint uses
POST /1/indexes/{indexName}/querywith{"params":"..."}. (algolia.com) - Empty query usage (empty query can be used to fetch all records). (algolia.com)
hitsPerPagecontrols number of hits returned. (support.algolia.com)- Searches with
analytics=falseare excluded from analytics. (algolia.com) responseFieldsreduces the size of the search response (controls which fields are included). (algolia.com)
Citations:
- 1: https://www.algolia.com/doc/rest-api/monitoring/get-status
- 2: https://www.algolia.com/doc/rest-api/search/search-single-index?utm_source=openai
- 3: https://www.algolia.com/doc/libraries/sdk/v1/methods/search?utm_source=openai
- 4: https://support.algolia.com/hc/en-us/articles/4919170182545-Is-there-a-limit-on-hitsPerPage?utm_source=openai
- 5: https://www.algolia.com/doc/guides/search-analytics/concepts/metrics?utm_source=openai
- 6: https://www.algolia.com/doc/api-reference/api-parameters/responseFields/?utm_source=openai
Replace health-check endpoint to work with DocSearch search-only keys.
GET /1/indexes requires listIndexes ACL, which DocSearch's recommended search-only public keys do not have. This causes a false negative health check (403 response) even when Algolia is working, forcing unnecessary Lunr fallback.
Use POST /1/indexes/{indexName}/query instead, which requires only the search ACL that DocSearch keys have:
✅ Safer health-check approach (search ACL)
- const response = await fetch(
- `https://${algolia.appId}-dsn.algolia.net/1/indexes`,
- {
- method: 'GET',
- headers: {
- 'X-Algolia-Application-Id': algolia.appId,
- 'X-Algolia-API-Key': algolia.apiKey,
- },
- signal: controller.signal,
- },
- );
+ const encodedIndexName = encodeURIComponent(algolia.indexName);
+ const response = await fetch(
+ `https://${algolia.appId}-dsn.algolia.net/1/indexes/${encodedIndexName}/query`,
+ {
+ method: 'POST',
+ headers: {
+ 'X-Algolia-Application-Id': algolia.appId,
+ 'X-Algolia-API-Key': algolia.apiKey,
+ 'Content-Type': 'application/json',
+ },
+ body: JSON.stringify({query: '', hitsPerPage: 1}),
+ signal: controller.signal,
+ },
+ );📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const response = await fetch( | |
| `https://${algolia.appId}-dsn.algolia.net/1/indexes`, | |
| { | |
| method: 'GET', | |
| headers: { | |
| 'X-Algolia-Application-Id': algolia.appId, | |
| 'X-Algolia-API-Key': algolia.apiKey, | |
| }, | |
| signal: controller.signal, | |
| }, | |
| ); | |
| return response.ok; | |
| const encodedIndexName = encodeURIComponent(algolia.indexName); | |
| const response = await fetch( | |
| `https://${algolia.appId}-dsn.algolia.net/1/indexes/${encodedIndexName}/query`, | |
| { | |
| method: 'POST', | |
| headers: { | |
| 'X-Algolia-Application-Id': algolia.appId, | |
| 'X-Algolia-API-Key': algolia.apiKey, | |
| 'Content-Type': 'application/json', | |
| }, | |
| body: JSON.stringify({query: '', hitsPerPage: 1}), | |
| signal: controller.signal, | |
| }, | |
| ); | |
| return response.ok; |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@website/src/theme/Navbar/Content/index.tsx` around lines 63 - 75, The current
health-check uses GET /1/indexes which requires listIndexes ACL and can 403 with
DocSearch search-only keys; change the fetch in the Navbar Content health-check
to POST to /1/indexes/{indexName}/query (use the existing Algolia index
identifier from the algolia config, e.g. algolia.indexName or algolia.index)
with method 'POST', add 'Content-Type': 'application/json' to headers and send
an empty query body (e.g. {} or { "query": "" }) so the request only requires
the search ACL, then keep returning response.ok to indicate success; update the
fetch call surrounding code (the function performing this fetch in index.tsx)
and preserve controller.signal for cancellation.
| Restore some Infima style that broke with CSS Cascade Layers | ||
| See https://github.com/facebook/docusaurus/pull/11142 | ||
| */ | ||
| :global(.navbar__items--right) > :last-child { |
There was a problem hiding this comment.
Stylelint will fail on :global without an inline suppression/config update.
Line 14 currently trips selector-pseudo-class-no-unknown, so this can block CI.
🧩 Minimal fix in-file
+/* stylelint-disable-next-line selector-pseudo-class-no-unknown */
:global(.navbar__items--right) > :last-child {
padding-right: 0;
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| :global(.navbar__items--right) > :last-child { | |
| /* stylelint-disable-next-line selector-pseudo-class-no-unknown */ | |
| :global(.navbar__items--right) > :last-child { | |
| padding-right: 0; | |
| } |
🧰 Tools
🪛 Stylelint (17.3.0)
[error] 14-14: Unexpected unknown pseudo-class selector ":global" (selector-pseudo-class-no-unknown)
(selector-pseudo-class-no-unknown)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@website/src/theme/Navbar/Content/styles.module.css` at line 14, Stylelint
flags the selector :global(.navbar__items--right) > :last-child with
selector-pseudo-class-no-unknown; add an inline suppression immediately before
that rule using a stylelint comment (e.g. /* stylelint-disable-next-line
selector-pseudo-class-no-unknown */) so the :global(.navbar__items--right) >
:last-child rule is allowed; ensure the comment sits directly above the selector
block containing :global(.navbar__items--right) > :last-child so CI no longer
fails.
| contextualSearch?: string; | ||
| externalUrlRegex?: string; |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# First, let's examine the SearchBar file to see the type definition and line 311
cat -n website/src/theme/SearchBar/index.tsx | head -350Repository: wso2/agent-manager
Length of output: 11536
🏁 Script executed:
# Let's also check if there are any type definition files or interfaces in the SearchBar directory
find website/src/theme/SearchBar -type f -name "*.ts" -o -name "*.tsx" | head -20Repository: wso2/agent-manager
Length of output: 99
🌐 Web query:
Docusaurus v3 @docusaurus/theme-search-algolia contextualSearch type configuration
💡 Result:
In Docusaurus v3, contextualSearch is a boolean option configured at:
// docusaurus.config.ts
export default {
themeConfig: {
algolia: {
contextualSearch: true, // default
},
},
};What it does:
- When
contextualSearch: true(default), Docusaurus adds contextual facet filters (based on the current page/version/locale, etc.) and merges them with any filters you set inalgolia.searchParameters.facetFilters. [1] - If you need full control, disable it and provide your own
facetFiltersyourself: [1]
export default {
themeConfig: {
algolia: {
contextualSearch: false,
searchParameters: {
facetFilters: ['language:en', ['filter1', 'filter2'], 'filter3'],
},
},
},
};If contextual search “only works when disabled”, it’s usually because your Algolia index isn’t configured to allow faceting on the fields Docusaurus uses (notably docusaurus_tag, language, lang, version, type). [1]
Sources:
[1] Docusaurus “Search” docs (Algolia contextual search + facetFilters behavior and troubleshooting) (docusaurus.io)
Citations:
Change contextualSearch type from string to boolean.
The Docusaurus v3 Algolia configuration expects contextualSearch to be a boolean (default: true). The current type definition at lines 46–47 diverges from this, creating an unsafe type mismatch at line 311 where siteConfig.themeConfig.algolia is cast to DocSearchV4Props.
Type correction
- contextualSearch?: string;
+ contextualSearch?: boolean;📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| contextualSearch?: string; | |
| externalUrlRegex?: string; | |
| contextualSearch?: boolean; | |
| externalUrlRegex?: string; |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@website/src/theme/SearchBar/index.tsx` around lines 46 - 47, The type for
contextualSearch in the SearchBar theme props is incorrect—change the
declaration of contextualSearch from string to boolean in the interface so it
matches Docusaurus v3/DocSearchV4 expectations; update the type on the property
where it’s declared (the contextualSearch field) and ensure any places that cast
or pass siteConfig.themeConfig.algolia (e.g., the code that casts to
DocSearchV4Props) treat contextualSearch as a boolean (and adjust any
default/usage accordingly).
Purpose
Resolves #479
This PR introcues lunr.js client side search as a fallback, which can be used in scenarios Algolia docsearch is geo-restricted or unavailable.
Goals
Approach
User stories
Release note
Documentation
Training
Certification
Marketing
Automation tests
Security checks
Samples
Related PRs
Migrations (if applicable)
Test environment
Learning
Summary by CodeRabbit
New Features
Style
Documentation