Static websites are popular nowadays. There are many static site generators, but not all have search built-in. Recently I’ve added a static search option to a few websites, including the one you’re reading. In this article I would like to share how I did this, as it might take less efforts than you think!
When searching to find a good library for a static full-text search, I came across popular solutions such as Lunr.js, FlexSearch and Fuse.js. To my surprise, these libraries are not very well maintained anymore, while they all have quite some open issues. To me, this does not relate to the popularity of static websites in general.
Later, I was lucky enough to find MiniSearch when looking for “search index” in the npm registry. This package happens to be easy to use, while performance and file size feel good. To be honest, I didn’t do an actual file size and performance comparison between the various options, but this MiniSearch blogpost has a good overview.
The fuzzy search and prefix search are optional, and work very well for the websites where I integrated it. This always requires a bit of fine-tuning, depending on the size and density of documents. I did not try the auto-suggestion feature yet. Overall a pleasant experience, much like the alternatives. What stood out initially, was simply better search results.
In my experience so far, the contents of Markdown documents can be used just fine as input for the index. The minimal syntax of Markdown does not seem to negatively impact the index. This makes solutions like MiniSearch great for static websites, as they are often powered by Markdown files.
So, how to integrate MiniSearch in your static website? There are roughly four steps here:
- Build the index
- Serve the index with the rest of the static site
- Connect the index with a DOM element (such as an input field)
- Search and render results
Building the index
The result (
documents) can be provided to a
MiniSearch instance using
minisearch.addAll(documents), which creates and returns the actual index. The
final step here is to store this JSON file to disk:
This example uses the
content fields of each document to index the
full-text search. The
storeFields) will be available
to render the search results later.
Serving the index
Next step is to make sure the index is served with the rest of the static website. This could be the root of the “dist” or “public” folder.
Connecting the search component
Depending on the requirements and the type of (static) website, the next part
could be implemented in many ways. Here I’m going to try and keep it very
concise. Let’s add
search.js to the static site with the following snippet.
This will attach an event listener to an existing
<input type="search"> in the
Here we already have the basics of our search component, in only a few lines of
code. Note that the indexed fields
content should be provided
again when loading the index. For brevity, this example logs the search results
in the browser console. Combined with very little styling, this is everything
this website uses for the static search.
Searching and rendering results
How to render search results depends on the type of static website or which
framework is being used. For the sake of completeness, here’s a minimal example
search function from the previous
This is roughly the code used on this website, and appends a
to the DOM as a sibling of the
input element. This way, the search results can
be rendered relative to this input field.
The search results (with the
pathname fields we stored in the
index before) are appended the container element as an ordered list. Ordered,
since MiniSearch provides the results sorted by relevance score.
If you are using React, you might be interested in react-minisearch, providing React integration for MiniSearch.
The search index is a relatively large static asset, as it includes both the
index and the data to show in the search results. Loading this file on page
load, as shown above, could degrade the performance of your website. It does not
block the main render thread as it uses a dynamic import, but for larger
websites this may impact overall performance. One way to mitigate this is to
only load the index when the user actually uses the search, for instance on the
focus event of the input field. To get an idea of the file size, currently the
index of this website with its first 11 articles, the size is 113Kb
uncompressed, and 20Kb gzipped. This is generally not really an issue, but
definitely something to keep an eye on when a website is large or growing. After
the first load, the browser will cache the static search index on subsequent
Multiple search indices
Depending of the site contents, another interesting feature might be to create multiple indices. This would be straight-forward following the steps in this article.