Implement @cmfcmf/docusaurus-search-local + Orama
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).12
Two viable architectures
- Option A: Pure local (Lunr) search
Use
@cmfcmf/docusaurus-search-localalone 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.21
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
- Install:
npm install @cmfcmf/docusaurus-search-localoryarn add @cmfcmf/docusaurus-search-local.
- Configure 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.
3. When to prefer this:
- 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.
Prompt you can reuse:
“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:
- Install (example for v2):
npm install @orama/plugin-docusaurusoryarn add @orama/plugin-docusaurus.2
- 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.).2
A typical config (you’ll adapt names/paths to your site):
// 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-localfrompluginsand uninstall the package to avoid conflicting multiple search UIs.1
- Delete
Prompt you can reuse:
“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?
@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).2
- 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. At that point you have effectively implemented Option B and no longer need the cmfcmf plugin.
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.
are there other Docusaurus search options?
Yes. Docusaurus supports several search options beyond @cmfcmf/docusaurus-search-local and Orama.16
Official / first‑class options
- Algolia DocSearch (official)
- Typesense DocSearch
Prompt you can reuse:
“Configure Docusaurus vX with Algolia DocSearch, including contextual search, version filters, and language facets, with a full
docusaurus.configexample.”
Local / client‑side search plugins
- Local search plugins (Lunr/FlexSearch)
Prompt you can reuse:
“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 search bar + external engine
- Docusaurus allows “Your own
SearchBarcomponent”, meaning you can swizzle the search bar and integrate any backend (Meilisearch, Elasticsearch, Postgres-based, AI RAG, etc.).16 - 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).1716
- Docusaurus allows “Your own
Prompt you can reuse:
“Design a custom Docusaurus search integration using a self-hosted Meilisearch cluster, including the crawler/indexer pipeline,
SearchBarswizzle, and TypeScript React search UI.”
Docusaurus allows “Your own SearchBar component”, meaning you can swizzle the search bar and integrate any backend (Meilisearch, Elasticsearch, Postgres-based, AI RAG, etc.).
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.).26
How swizzling works
- Docusaurus uses the classic theme’s
SearchBarcomponent for the navbar item withtype: 'search'inthemeConfig.navbar.items.2726 - Running the swizzle command copies that internal component into your project under
src/theme/SearchBar, and Docusaurus will start using your version instead.26
Example commands (pick your package manager):26
npm run swizzle @docusaurus/theme-classic SearchBar
# or
yarn swizzle @docusaurus/theme-classic SearchBar
# or
pnpm run swizzle @docusaurus/theme-classic SearchBar
# or
bun run swizzle @docusaurus/theme-classic SearchBar
This creates src/theme/SearchBar in your repo; editing it lets you call any custom search API (Meilisearch, Elasticsearch, RAG service, etc.) while keeping the navbar integration intact.26
Using Algolia’s UI as a base
- Instead of starting from the bare classic theme component, you can swizzle from
@docusaurus/theme-search-algoliato customize the Algolia searchbar implementation.2826 - Command (alternative):
npm run swizzle @docusaurus/theme-search-algolia SearchBar
This gives you a prebuilt search UI you can adapt, for example by swapping the Algolia client calls for your own backend.2930
If you want, a next step can be a concrete src/theme/SearchBar/index.tsx wired to Meilisearch or a RAG endpoint, plus the minimal docusaurus.config needed to keep the navbar item working.
How to swizzle the SearchBar in Docusaurus step by step
Best practices for implementing a custom SearchBar component How to connect Meilisearch to a custom Docusaurus SearchBar How to use Elasticsearch with Docusaurus search
Docusaurus lets you “swizzle” (override) the SearchBar component and then wire it to any search backend such as Meilisearch or Elasticsearch. Below is a concise, step‑by‑step path you can apply to any backend.41
Swizzle SearchBar step by step
- Ensure you use the classic theme
- Add a search item to the navbar
In
themeConfig.navbar.items, ensure there is a search item:4341
// docusaurus.config.js
module.exports = {
// ...
themeConfig: {
navbar: {
items: [
{ type: 'search', position: 'right' },
],
},
},
};
- Run the swizzle command
Swizzle the classic theme
SearchBarcomponent:41
# pick one based on your package manager
npm run swizzle @docusaurus/theme-classic SearchBar
# or
yarn swizzle @docusaurus/theme-classic SearchBar
# or
pnpm run swizzle @docusaurus/theme-classic SearchBar
# or
bun run swizzle @docusaurus/theme-classic SearchBar
- Choose “Eject” when prompted to get a full copy you can freely edit.[^4_4][^4_1]
4. Verify generated files
- You should now have something like:
- src/theme/SearchBar/index.tsx (or .js)
- Docusaurus will automatically use this file instead of the built‑in searchbar.4441
5. Restart dev server
- Stop npm run start / yarn start and run it again so the new theme component loads.
Best practices for a custom SearchBar
- Keep the navbar contract small
- 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.
- 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.
- 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.
- Respect Docusaurus routing
- Use
@docusaurus/Link(oruseHistoryin older setups) so navigation stays client‑side and preserves SPA behavior.42
- Use
Wiring Meilisearch into SearchBar
Assume you have a Meilisearch index docs populated with { title, url, content, ... }.
- Install Meilisearch client
npm install meilisearch
# or
yarn add meilisearch
- Create a tiny 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 }>;
}
- 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>
);
}
- Indexing pipeline (out of scope of Docusaurus)
Prompt you can reuse:
“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, ... }.
- Install Elasticsearch client
npm install @elastic/elasticsearch
# or
yarn add @elastic/elasticsearch
- 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?. ??
(h._source.content as string)?.slice(0, 160),
}));
}
- Reuse the same
SearchBarUI- In
src/theme/SearchBar/index.tsx, swapsearchDocsimport to the Elasticsearch helper and keep all UI logic identical. - This makes it trivial to switch between Meilisearch and Elasticsearch by changing one import.
- In
Prompt you can reuse:
“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.”
If you tell the Docusaurus major version (2 vs 3) and whether you prefer Meilisearch or Elasticsearch first, a next iteration can tighten this into a fully typed TS implementation plus an example index mapping/schema.