Skip to main content

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-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.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

  1. Install:
    • npm install @cmfcmf/docusaurus-search-local or yarn add @cmfcmf/docusaurus-search-local.
  2. 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

  1. Pick the right Orama plugin:
    • Docusaurus v2: @orama/plugin-docusaurus
    • Docusaurus v3: @orama/plugin-docusaurus-v312
  2. Install (example for v2):
    • npm install @orama/plugin-docusaurus or yarn add @orama/plugin-docusaurus.2
  3. 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,
},
},
],
],
};
  1. Remove the old local search plugin:
    • Delete @cmfcmf/docusaurus-search-local from plugins and uninstall the package to avoid conflicting multiple search UIs.1

Prompt you can reuse:

“Given a Docusaurus v3 site using @orama/plugin-docusaurus-v3 in 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-local is 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. 3456789101112131415


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)
    • Built‑in support via themeConfig.algolia in the classic preset.16
    • Free for open documentation; uses Algolia’s hosted crawler and index.16
  • Typesense DocSearch
    • Community-maintained but documented on the official site.16
    • Uses a Typesense cluster (self‑hosted or managed) plus docusaurus-theme-search-typesense and a scraper.16

Prompt you can reuse:

“Configure Docusaurus vX with Algolia DocSearch, including contextual search, version filters, and language facets, with a full docusaurus.config example.”

Local / client‑side search plugins

  • Local search plugins (Lunr/FlexSearch)
    • 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.16
    • These avoid external services and are suitable for air‑gapped or privacy‑sensitive deployments.16

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 SearchBar component”, 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

Prompt you can reuse:

“Design a custom Docusaurus search integration using a self-hosted Meilisearch cluster, including the crawler/indexer pipeline, SearchBar swizzle, and TypeScript React search UI.” 1819202122232425


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 SearchBar component for the navbar item with type: 'search' in themeConfig.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-algolia to 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. 31323334353637383940


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

  1. Ensure you use the classic theme
    • In docusaurus.config.js, you should have @docusaurus/preset-classic (the default) which brings the navbar and searchbar UI.4243
  2. 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' },
],
},
},
};
  1. Run the swizzle command Swizzle the classic theme SearchBar component: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.


  1. Keep the navbar contract small
    • Implement only what the navbar expects: a search input and any popover/modal you want.
    • Accept className and ...props and forward them to your root element so the navbar styling continues to work.4344
  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 Esc to close, arrow keys to move between results, Enter to select.
  4. Keep config out of the component
    • Use environment variables or a config module for URLs, API keys, and index names.
    • The SearchBar should only know how to call a generic search function, not how Meilisearch/Elasticsearch is hosted.
  5. Respect Docusaurus routing
    • Use @docusaurus/Link (or useHistory in older setups) so navigation stays client‑side and preserves SPA behavior.42

Assume you have a Meilisearch index docs populated with { title, url, content, ... }.

  1. Install Meilisearch client
npm install meilisearch
# or
yarn add meilisearch
  1. 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 }>;
}
  1. 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>
);
}
  1. 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 docs index.
    • For a quick start, generate a sitemap or use Docusaurus’ built docs build as input to your own indexing script.4546

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.”


Assume you have an Elasticsearch index docs with { title, url, content, ... }.

  1. Install Elasticsearch client
npm install @elastic/elasticsearch
# or
yarn add @elastic/elasticsearch
  1. 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),
}));
}
  1. 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.

Prompt you can reuse:

“Provide a production-grade Docusaurus SearchBar implementation 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.

Footnotes

  1. https://docusaurus.io/community/resources 2 3 4

  2. https://docs.orama.com/docs/orama-js/plugins/plugin-docusaurus 2 3 4 5 6

  3. https://npm.io/package/@cmfcmf/docusaurus-search-local

  4. https://docusaurus.io/docs/search

  5. https://docusaurus.io/docs/static-assets

  6. https://github.com/nikolaymatrosov/orama-docusaurus

  7. https://blog.gitcode.com/2f614a6ee70b7a12de9425c587e92cd3.html

  8. https://github.com/oramasearch/orama/issues/887

  9. https://stackoverflow.com/questions/78875820/docusaurus-build-failed-due-to-docusaurus-search-local-plugin

  10. https://docusaurus.io/feature-requests/p/offline-search

  11. https://davidawindham.com/til/lists/stars/

  12. https://blog.gitcode.com/ca54ff8be49ee8b60faea3a4ec15cf7f.html

  13. https://githubhelp.com/cmfcmf/docusaurus-search-local

  14. https://npmjs.com/package/docusaurus-plugin-search-local

  15. https://docusaurus.io/pt-BR/docs/3.2.1/api/docusaurus-config

  16. https://docusaurus.io/docs/search 2 3 4 5 6 7 8 9

  17. https://dev.to/alakkadshaw/docusaurus-site-search-plugin-2cp

  18. https://github.com/webbertakken/awesome-docusaurus

  19. https://docusaurus.io/pt-BR/docs/3.2.1

  20. https://docusaurus.io/docs

  21. https://docusaurus.io/pt-BR/docs/next/styling-layout

  22. https://distr.sh/blog/distr-docs/

  23. https://docmd.mgks.dev/comparison/

  24. https://document360.com/blog/docusaurus-documentation/

  25. https://www.reddit.com/r/devops/comments/1fwl3lw/docusaurus_vs_mkdocs/

  26. https://docusaurus.io/docs/3.3.2/search 2 3 4 5 6

  27. https://docusaurus.io/docs/api/themes/configuration

  28. https://docusaurus.io/pt-BR/docs/3.1.1/search

  29. https://stackoverflow.com/questions/58501754/custom-search-bar-in-docusaurus-2

  30. https://github.com/facebook/docusaurus/discussions/4104

  31. https://www.youtube.com/watch?v=xXwvDVGBlqQ

  32. https://docusaurus.io/docs/styling-layout

  33. https://stackoverflow.com/questions/66012147/customizing-docusaurus-search-bar-v2

  34. https://github.com/facebook/docusaurus/discussions/4116

  35. https://github.com/easyops-cn/docusaurus-search-local

  36. https://docusaurus.io/pt-BR/docs/3.0.1/api/themes/configuration

  37. https://docusaurus.io/docs/search

  38. https://github.com/jacobkim9881/docusaurus-custom-search

  39. https://docs.inkeep.com/cloud/integrations/docusaurus/chat-button-and-search-bar

  40. https://docusaurus.io/docs/3.1.1/search

  41. https://docusaurus.io/docs/3.3.2/search 2 3 4

  42. https://docusaurus.io/pt-BR/docs/3.2.1 2

  43. https://docusaurus.io/docs/api/themes/configuration 2 3

  44. https://docusaurus.io/docs/styling-layout 2

  45. https://docusaurus.io/docs/search

  46. https://docmd.mgks.dev/comparison/