Implement @cmfcmf/docusaurus-search-local + Orama
Overview
You cannot directly "combine" @cmfcmf/docusaurus-search-local and Orama in a supported way today; instead, the idiomatic approach is to pick one: keep @cmfcmf/docusaurus-search-local (Lunr-based, fully local) or replace it with the official Orama Docusaurus plugin (@orama/plugin-docusaurus or @orama/plugin-docusaurus-v3).
Two Viable Architectures
Option A: Pure Local (Lunr) Search
Use @cmfcmf/docusaurus-search-local alone for offline, firewall-only, fully static search with Lunr.js and an Algolia Autocomplete-based UI.
Option B: Orama-Native Search
Use Orama's Docusaurus plugin, which generates an Orama index during build and wires it into Orama's Searchbox UI, optionally backed by Orama Cloud.
Key Insight: If you want Orama, the clean solution is Option B; trying to bolt Orama onto the cmfcmf plugin means re‑implementing its frontend and index consumption layer.
Option A: Implement @cmfcmf/docusaurus-search-local
Installation
npm install @cmfcmf/docusaurus-search-local
# or
yarn add @cmfcmf/docusaurus-search-local
Configuration in docusaurus.config.js (Docusaurus v2)
// docusaurus.config.js
module.exports = {
// ...
plugins: [
[
require.resolve('@cmfcmf/docusaurus-search-local'),
{
indexDocs: true,
docsRouteBasePath: '/docs',
indexBlog: true,
blogRouteBasePath: '/blog',
indexPages: false,
language: ['en'], // or ['en', 'pt'] etc.
indexDocSidebarParentCategories: 1,
style: undefined,
lunr: {
tokenizerSeparator: /[\s\-]+/,
},
},
],
],
};
This generates a Lunr index at build time and wires the search bar automatically into the theme; it only works on the static build, not during yarn start.
When to Prefer This Option
- Air‑gapped or highly regulated deployments where adding Orama's runtime is not desired
- You're okay with Lunr and just need a functioning local search today
Extension Prompt
"Given a Docusaurus v2 site with
@cmfcmf/docusaurus-search-local, show me how to extend the plugin's indexer to include custom front‑matter fields (e.g. tags, domain, product) in the search index, with concrete code examples."
Option B: Replace with Orama's Docusaurus Plugin
Pick the Right Orama Plugin
- Docusaurus v2:
@orama/plugin-docusaurus - Docusaurus v3:
@orama/plugin-docusaurus-v3
Installation (Example for v2)
npm install @orama/plugin-docusaurus
# or
yarn add @orama/plugin-docusaurus
Configure in docusaurus.config.js
The Orama plugin typically:
- Hooks into the build, walks docs/blog/pages
- Builds an Orama index (local JSON or Orama Cloud)
- Injects Orama Searchbox components into your theme (navbar search, etc.)
// docusaurus.config.js
module.exports = {
// ...
plugins: [
[
'@orama/plugin-docusaurus',
{
// example options – adapt to docs
indexBlog: true,
indexDocs: true,
indexPages: false,
docsRouteBasePath: '/docs',
blogRouteBasePath: '/blog',
orama: {
// local-only index:
mode: 'local',
// or Orama Cloud:
// mode: 'cloud',
// apiKey: process.env.ORAMA_API_KEY,
// indexId: 'your-index-id',
},
searchbox: {
placeholder: 'Search docs…',
showSearchButton: true,
},
},
],
],
};
Remove the Old Local Search Plugin
Delete @cmfcmf/docusaurus-search-local from plugins and uninstall the package to avoid conflicting multiple search UIs.
npm uninstall @cmfcmf/docusaurus-search-local
Extension Prompt
"Given a Docusaurus v3 site using
@orama/plugin-docusaurus-v3in local mode (no cloud), show a full example of customizing the Orama Searchbox UI (keyboard shortcuts, result templates, filtering by docs version) with all TypeScript types."
Why Not Combine Both?
Architectural Incompatibility
@cmfcmf/docusaurus-search-localis tightly coupled to Lunr's index format and Algolia Autocomplete's UI; its build pipeline produces a Lunr JSON and its React components expect that structure- Orama's plugin produces an Orama database and wires React components that know how to query Orama (local or cloud)
Implementation Requirements
To "use Orama with @cmfcmf/docusaurus-search-local" you would need to:
- Disable its Lunr index and still hijack its UI, or
- Ignore its UI, generate a parallel Orama index and mount your own searchbox in the navbar
Reality Check: At that point you have effectively implemented Option B and no longer need the cmfcmf plugin.
Next Steps
If you share your Docusaurus major version (2 vs 3) and requirements (offline only vs Orama Cloud OK, multi‑locale, versioned docs), a next step can be a concrete docusaurus.config + custom searchbox component tailored to your stack.
Additional Docusaurus Search Options
Yes, Docusaurus supports several search options beyond @cmfcmf/docusaurus-search-local and Orama.
Official / First‑Class Options
Algolia DocSearch (Official)
- Built‑in support via
themeConfig.algoliain the classic preset - Free for open documentation; uses Algolia's hosted crawler and index
Configuration Prompt:
"Configure Docusaurus vX with Algolia DocSearch, including contextual search, version filters, and language facets, with a full
docusaurus.configexample."
Typesense DocSearch
- Community-maintained but documented on the official site
- Uses a Typesense cluster (self‑hosted or managed) plus
docusaurus-theme-search-typesenseand a scraper
Local / Client‑Side Search Plugins
Lunr/FlexSearch-Based Options
- Docusaurus docs list "Local Search" options as community plugins (e.g., Lunr-based) that build a static index at build time and run entirely client-side
- These avoid external services and are suitable for air‑gapped or privacy‑sensitive deployments
Comparison Prompt:
"Show a comparison of Docusaurus local search plugins (Lunr/FlexSearch/others) with bundle size, indexing features, and multilingual support, including links to each plugin's repo."
Bring‑Your‑Own Search Backend
Custom SearchBar + External Engine
Docusaurus allows "Your own SearchBar component", meaning you can swizzle the search bar and integrate any backend (Meilisearch, Elasticsearch, Postgres-based, AI RAG, etc.).
This is the route most third‑party or AI search integrations take (e.g., DocuScout's guide to embedding an AI+search widget in a swizzled SearchBar).
Custom Integration Prompt:
"Design a custom Docusaurus search integration using a self-hosted Meilisearch cluster, including the crawler/indexer pipeline,
SearchBarswizzle, and TypeScript React search UI."
Custom SearchBar Implementation Guide
Docusaurus exposes the navbar SearchBar as a theme component that you can "swizzle", letting you replace its implementation and wire it to any backend (Meilisearch, Elasticsearch, Postgres, RAG, etc.).
Swizzle Mechanism
1. Eject the SearchBar Component
npm run swizzle @docusaurus/theme-classic SearchBar -- --eject
# or
yarn swizzle @docusaurus/theme-classic SearchBar -- --eject
This creates src/theme/SearchBar/index.tsx (or .js / .jsx), which Docusaurus loads instead of the default theme component.
2. Implement Your Custom Search
The swizzled component receives:
className?: string(for styling integration)- Standard React props
You can:
- Replace the entire UI
- Call any search backend API
- Implement custom ranking, filtering, highlighting
- Add keyboard shortcuts, autocomplete, etc.
SearchBar Best Practices
1. Keep the Navbar Contract Small
- Implement only what the navbar expects: a search input and any popover/modal you want
- Accept
classNameand...propsand forward them to your root element so the navbar styling continues to work
2. Avoid Heavy Work on Every Keypress
- Use debouncing or throttling when calling your backend (e.g., trigger search 200–300 ms after typing stops)
- For large results, paginate or limit results client‑side to reduce render cost
3. Handle Empty and Error States Clearly
- Show distinct UI for: "type to search", "no results", "error loading", and "loading…"
- Keyboard accessibility: support
Escto close, arrow keys to move between results,Enterto select
4. Keep Config Out of the Component
- Use environment variables or a config module for URLs, API keys, and index names
- The
SearchBarshould only know how to call a generic search function, not how Meilisearch/Elasticsearch is hosted
5. Respect Docusaurus Routing
- Use
@docusaurus/Link(oruseHistoryin older setups) so navigation stays client‑side and preserves SPA behavior
Wiring Meilisearch into SearchBar
Assume you have a Meilisearch index docs populated with { title, url, content, ... }.
1. Install Meilisearch Client
npm install meilisearch
# or
yarn add meilisearch
2. Create a Search Client Helper
// src/search/meilisearchClient.ts
import { Meilisearch } from 'meilisearch';
const client = new Meilisearch({
host: process.env.MEILI_HOST!,
apiKey: process.env.MEILI_API_KEY!, // or omit for public
});
const index = client.index('docs');
export async function searchDocs(query: string) {
if (!query.trim()) return [];
const { hits } = await index.search(query, {
limit: 10,
});
return hits as Array<{ title: string; url: string; snippet?: string }>;
}
3. Implement the Custom SearchBar
// src/theme/SearchBar/index.tsx
import React, { useState, useEffect, useCallback } from 'react';
import Link from '@docusaurus/Link';
import { searchDocs } from '../../search/meilisearchClient';
const DEBOUNCE_MS = 250;
export default function SearchBar({ className }: { className?: string }) {
const [query, setQuery] = useState('');
const [results, setResults] = useState<
Array<{ title: string; url: string; snippet?: string }>
>([]);
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const doSearch = useCallback(
(q: string) => {
if (!q.trim()) {
setResults([]);
setLoading(false);
setError(null);
return;
}
setLoading(true);
setError(null);
searchDocs(q)
.then(setResults)
.catch((e) => setError(e.message ?? 'Search failed'))
.finally(() => setLoading(false));
},
[]
);
useEffect(() => {
const handle = setTimeout(() => doSearch(query), DEBOUNCE_MS);
return () => clearTimeout(handle);
}, [query, doSearch]);
return (
<div className={className}>
<input
type="search"
placeholder="Search docs…"
value={query}
onChange={(e) => setQuery(e.target.value)}
/>
{query && (
<div className="searchResults">
{loading && <div>Searching…</div>}
{error && <div>Error: {error}</div>}
{!loading && !error && results.length === 0 && (
<div>No results</div>
)}
<ul>
{results.map((r) => (
<li key={r.url}>
<Link to={r.url}>{r.title}</Link>
{r.snippet && <div className="snippet">{r.snippet}</div>}
</li>
))}
</ul>
</div>
)}
</div>
);
}
4. Indexing Pipeline (Out of Scope of Docusaurus)
- Use Meilisearch's crawler, a CI job, or a static‑site crawler to index Docusaurus pages into the
docsindex - For a quick start, generate a sitemap or use Docusaurus' built docs build as input to your own indexing script
Indexing Job Prompt:
"Design a Meilisearch indexing job for a Docusaurus site that runs in CI on every deploy, including URL discovery, content extraction from built HTML, and Meilisearch schema configuration."
Wiring Elasticsearch into SearchBar
Assume you have an Elasticsearch index docs with { title, url, content, ... }.
1. Install Elasticsearch Client
npm install @elastic/elasticsearch
# or
yarn add @elastic/elasticsearch
2. Create a Search Helper
// src/search/elasticsearchClient.ts
import { Client } from '@elastic/elasticsearch';
const client = new Client({
node: process.env.ES_NODE!,
auth: process.env.ES_USERNAME
? {
username: process.env.ES_USERNAME!,
password: process.env.ES_PASSWORD!,
}
: undefined,
});
export async function searchDocs(query: string) {
if (!query.trim()) return [];
const res = await client.search({
index: 'docs',
size: 10,
query: {
multi_match: {
query,
fields: ['title^3', 'content'],
},
},
highlight: {
fields: {
content: {},
},
},
});
const hits = (res.hits.hits ?? []) as any[];
return hits.map((h) => ({
title: h._source.title as string,
url: h._source.url as string,
snippet:
h.highlight?.content?.[0] ??
(h._source.content as string)?.slice(0, 160),
}));
}
3. Reuse the Same SearchBar UI
In src/theme/SearchBar/index.tsx, swap searchDocs import to the Elasticsearch helper and keep all UI logic identical.
This makes it trivial to switch between Meilisearch and Elasticsearch by changing one import.
Production SearchBar Prompt:
"Provide a production-grade Docusaurus
SearchBarimplementation backed by Elasticsearch, including keyboard navigation of results, version filtering using Docusaurus metadata, and security considerations for exposing the search endpoint."
Summary
Docusaurus provides maximum flexibility for search implementation:
- Official plugins (Algolia, Typesense) for managed solutions
- Local plugins (Lunr, Orama) for static/offline search
- Custom SearchBar for any backend (Meilisearch, Elasticsearch, RAG)
The swizzle mechanism enables complete control over search UX while maintaining Docusaurus theme integration.
For specific implementation guidance, share your Docusaurus version (2 vs 3) and preferred backend (Meilisearch, Elasticsearch, etc.).