Library / SDK
praveenn77/docusaurus-lunr-search avatar
praveenn77/docusaurus-lunr-search

docusaurus-lunr-search, offline docs search, and the options that decide what gets indexed

Local / Offline Search for docusaurus

559 stars90 forksJavaScriptMIT

At a glance

What is it?
docusaurus-lunr-search adds a local search box to a Docusaurus v3.9.2+ documentation site by building a lunr index at build time, with no service to sign up for. It works by parsing your markdown into a search index, which also means the index only appears in a production build, and the plugin's own defaults decide which routes, headings and words you can actually find.
Who is it for?
docusaurus-lunr-search fits a documentation team that needs a working search box on a static site without paying for a hosted search service, and that is willing to treat the plugin's index options as part of its content structure. It does not fit a site that still runs on Docusaurus before v3.9.2, or a Node toolchain older than 20, because the peer dependency range and the engines field both start there.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 165 days ago.
What is it written in?
Mainly JavaScript, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 22, 2026, and from our analysis. They are not legal advice.

Editorial analysis

Search that runs in the browser with nothing to sign up for

docusaurus-lunr-search exists because a Docusaurus site has no search of its own. The plugin fills that gap with a local search component: the index is produced when your site is built, shipped as static files, and queried in the visitor's browser. No crawling service, no API key, no per-query cost.

The package targets Docusaurus v3.9.2 and newer, and the documentation states the prerequisites as Docusaurus above v3.9.2 and Node.js 20.X or later. The package manifest agrees on the lower bound, with a peer dependency on @docusaurus/core at ^3.9.2 alongside react and react-dom at ^19.0.0, and an engines field requiring node at or above 20.0.0. The published package version is 4.0.0.

The internals are worth knowing because they explain the behaviour you will hit. The dependency list names lunr and lunr-languages for the index and for stemming in other languages, rehype-parse and hast-util-select and hast-util-to-text for turning markdown into text, unified and to-vfile to drive that pipeline, mustache for the search UI templates, autocomplete.js for the query suggestions, mark.js for highlighting matches, and cli-progress for the progress bar during indexing. The minified index generation runs through worker threads, which is why the manifest carries a maxThreads option.

So the mechanism is: parse the documentation at build time, extract the fields lunr needs, ship the index, and query it locally. Nothing about that design requires a network call after the site is deployed, which is the point of the project.

Adding the plugin, building, and the dev server that shows no results

The installation is a small sequence of steps, and two of the documented steps are there for specific failures you may hit.

First, add the package to your project:

bash
yarn add docusaurus-lunr-search

The documentation also gives the npm equivalent, which saves the dependency by default:

bash
npm i docusaurus-lunr-search  --save

If npm fails with an error about being unable to resolve the dependency tree, the documentation tells you to install with legacy peer dependency resolution:

If npm fails to install the lunr package itself, the documented fix is to install it directly:

bash
npm i lunr --save

Next, register the plugin in docusaurus.config.js:

js
module.exports = {
  // ...
    plugins: [require.resolve('docusaurus-lunr-search')],
}

Then build and serve the site, using yarn:

bash
yarn build
bash
yarn serve

The npm equivalents are npm run build and npm run serve. The critical operational note comes last and is easy to miss: the Docusaurus search index is only generated from a full project build, and the local development server does not provide search functionality. If you add the plugin and the search box returns nothing while you work locally, that is expected behaviour rather than a broken setup. Verify search against the built site.

The route, tag and word filters that decide what is findable

The plugin's option list is the real specification of its behaviour, and several defaults will shape your site whether you intend them to or not.

Routing is the first axis. excludeRoutes takes an array and defaults to an empty list, meaning every route is in the index unless you remove it. includeRoutes also defaults to an empty array and, when used, includes only the specific routes you list. indexBaseUrl is a boolean defaulting to false, so the base url of your site is not indexed unless you set it to true. excludeTags removes certain tags from the search, and stopWords adds words to exclude from the search result, both defaulting to an empty array.

Presentation is the second axis. highlightResult defaults to false and, when enabled, highlights the searched word in the result page using mark.js, with the highlight colour customisable in CSS. maxHits defaults to 5 and sets the maximum number of hits shown. disableVersioning defaults to false, which means documentation versions are displayed by default and you set it to true to hide them.

The third axis is hosting. assetUrl is a string whose default is a backtick character, and it sets the url from which the generated search doc files are loaded, with the documentation pointing at issue 122 for the context.

Set these deliberately. An index that quietly excludes a route, or one that surfaces every version of a page at once, is the kind of bug that gets reported as search being broken rather than as a configuration choice.

Lunr field boosts and the heading coverage limit

Two mechanisms decide how relevant a hit looks and how much of your content is in the index at all.

The first is the fields option, which is passed into lunr directly as field attributes and lets you set the relative priority of different sources of keywords. The plugin sets this default:

javascript
{ 
  title: { boost: 200 },
  content: { boost: 2 },
  keywords: { boost: 100 }
}

So a match in a page title is weighted far above a match in body text, with keywords in between. If your documentation titles are vague and your body text is precise, this default may rank results the wrong way, and fields is where you would change that.

The second mechanism is a coverage limit that catches people out. By default the library searches only for headings that are direct children of the .markdown element. If you render content inside the .markdown element on a swizzled DocItem component, the headings inside those custom elements are not indexed. The fix is documented: add the attribute data-search-children to a parent element of the headings you want indexed, and the library will then look for all headings inside that element, including indirect children and grandchildren.

In other words, a documentation site with a custom layout can look like it has a search box that finds nothing, while the plugin is working exactly as designed. The README points at issue 115 for a worked example.

Languages, stems and the option that fixed the container case

Multi-language support comes from passing a languages array to the plugin, and the documentation gives the shape of the configuration:

js
module.exports = {
  // ...
    plugins: [[ require.resolve('docusaurus-lunr-search'), {
      languages: ['en', 'de'] // language codes
    }]],
}

The default is a single English entry, and the supported language list is the one published by the lunr-languages project, so the set of codes you can use is defined there rather than by this plugin. The underlying mechanism is stemming: the option chooses the stemmer used when building the index, which is what lets a German query match a German word in a different inflection. This also means a multi-language site needs the index built with the right languages, otherwise German content is indexed with an English stemmer and matches poorly.

One option stands out because it fixes a specific and confusing failure. maxThreads is a number whose default is os.cpus().length, described as the maximum number of worker threads used for indexing. The documentation explains why that default bites: in containerized environments os.cpus() returns the host's CPU count instead of the container's limit, so the indexer can spawn more workers than the container is allowed. Setting maxThreads explicitly is the fix, and it is a good example of a default that is reasonable on a workstation and wrong in CI.

The indexing step also prints progress, which the cli-progress dependency provides, and the project notes a maximum number of hits shown defaulting to 5, so a very broad query returns a short list by design.

Where local search is the wrong choice

The first wrong case is a documentation set that is larger than a single build can index comfortably. The whole index is produced at build time and shipped as a static file, and the plugin gives you no way to split it across several assets other than where to load the search doc files from. A site with tens of thousands of pages should be measured before this is adopted, not after.

The second wrong case is a requirement for ranked, typo-tolerant search across a corpus that changes daily. A build-time index is only as fresh as the last build. A hosted search service re-indexes continuously and handles fuzzy matching that lunr does not provide out of the box.

The third wrong case is a team that needs a supported dependency. The repository is not archived, but the last push was on 2026-05-11, and the most recent tagged release in the release list is v3.6.1 from 2025-01-10, while the package manifest itself declares version 4.0.0. That gap between the published version number and the newest tag on the release page is the kind of thing you want to understand before you depend on it.

The fourth wrong case is a site that still needs search during local development, because the documentation states plainly that the dev server does not provide search functionality.

The fifth wrong case is expecting a hosted search product's features without a hosted service. There is no crawl on demand, no analytics, no query logging and no recrawl.

docusaurus-lunr-search against a hosted search service and against a static index

The realistic alternative to this plugin is not another plugin, it is a hosted search service such as the Algolia DocSearch component, which the documentation credits as the origin of the search component code. The difference in approach is straightforward.

A hosted service runs a crawler over your site, builds the index on its own infrastructure, and serves queries over the network. Indexing is independent of your build, so a page published in any deployment is searchable immediately, and fuzzy matching, typo tolerance and result analytics come with the service. The costs are an account, an index to configure, a network dependency at query time, and a hosted component in your dependency chain.

The local approach removes all four. The index is a build artifact, the query runs in the visitor's browser, and the only dependency is a package in your own repository. What it gives up is the crawler's coverage: only what the build parses ends up searchable, and the plugin's defaults for routes, tags and headings decide the edges of that coverage.

There is a third path worth naming. A static index generator that emits a prebuilt search asset, without a lunr query layer in the browser, trades interactive refinement for a smaller bundle and simpler hosting, but it also gives up the stemming and boosting controls that the fields option provides.

For a documentation site that must work offline or on an internal network, the local approach is the only one of the three that satisfies the constraint at all.

Editorial conclusion

docusaurus-lunr-search fits a documentation team that needs a working search box on a static site without paying for a hosted search service, and that is willing to treat the plugin's index options as part of its content structure. It does not fit a site that still runs on Docusaurus before v3.9.2, or a Node toolchain older than 20, because the peer dependency range and the engines field both start there. Before you adopt it, run a full build rather than the dev server, since the index is only generated from a full project build, check whether your headings sit inside swizzled components, and read the plugin's own option list to see which routes and tags it excludes by default.

Frequently asked questions

How do I install docusaurus-lunr-search in a Docusaurus project?

Add the package with yarn add docusaurus-lunr-search or npm i docusaurus-lunr-search --save, then register the plugin in docusaurus.config.js with plugins: [require.resolve('docusaurus-lunr-search')]. The documentation notes that npm may need --legacy-peer-deps to resolve the dependency tree and that lunr may need installing by hand.

Why does search return no results in the Docusaurus dev server?

The documentation states that the Docusaurus search index is only generated from a full project build, and that the local development server does not provide search functionality. Run yarn build and serve the built site to test search.

What are the Docusaurus and Node.js version requirements for docusaurus-lunr-search?

The documentation lists Docusaurus above v3.9.2 and Node.js 20.X or later. The package manifest sets a peer dependency on @docusaurus/core at ^3.9.2 with react and react-dom at ^19.0.0, and an engines field requiring node at or above 20.0.0.

How do I search headings that are not direct children of the markdown element?

By default the library indexes only headings that are direct children of the .markdown element. To index headings inside swizzled components, add the data-search-children attribute to a parent element of those headings, and the library will include direct and indirect children.

How do I enable languages other than English in docusaurus-lunr-search?

Pass the languages option to the plugin as an array of language codes, for example languages: ['en', 'de'], which selects the stemmers used when the index is built. The available codes are the ones listed in the lunr-languages project.

Official sources

  1. License: MIT
  2. praveenn77/docusaurus-lunr-search on GitHub
  3. Project website
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/praveenn77-docusaurus-lunr-search.svg)](https://hysenlabs.com/projects/praveenn77-docusaurus-lunr-search)

Was this useful?