client side search with lunr for jekyll sites
Why Static Sites Need Client-Side Search
One of the biggest limitations of static sites like those powered by Jekyll is the absence of dynamic search. Unlike CMS platforms that rely on databases, Jekyll serves pre-rendered HTML pages, which means traditional search engines or database queries aren't available. This creates a gap when users want to find specific content quickly—especially in large documentation or knowledge base sites.
Enter Lunr.js—a lightweight, open-source JavaScript search engine that runs entirely in the browser. It indexes your content ahead of time and allows users to search through it on the client side without server overhead.
What is Lunr and How Does It Work?
Lunr.js creates an inverted index from your site’s content, enabling full-text search on static files. It's modeled loosely on the behavior of Solr or Elasticsearch but optimized for small-scale, client-side search.
- It builds a search index from your content during site build time.
- That index is saved as a JSON file.
- A JavaScript search form loads the index and performs real-time lookup on the client.
Advantages of Using Lunr for Jekyll
- No server or backend needed—works on GitHub Pages.
- Highly customizable indexing strategy.
- Instant results without page reloads.
- Lightweight and scalable for small-to-medium documentation projects.
Generating the Search Index with Jekyll
To start using Lunr, you’ll first need to generate a search index file that contains all the searchable content from your Jekyll site. Here's how to do it:
Step 1: Create search.json
Inside your root directory, create a file named search.json with the following content:
---
layout: none
---
[
{% assign docs = site.docs | sort: "order" %}
{% for doc in docs %}
{
"title": "{{ doc.title | escape }}",
"url": "{{ doc.url | relative_url }}",
"content": {{ doc.content | strip_html | strip_newlines | jsonify }}
}{% unless forloop.last %},{% endunless %}
{% endfor %}
]
This renders a static JSON file at build time, containing the titles, URLs, and raw content of your documentation files.
Adding Lunr.js to Your Site
Download Lunr from its official site or include it via CDN:
<script src="https://unpkg.com/lunr/lunr.js"></script>
Next, create a JavaScript file (e.g., search.js) that will handle indexing and querying.
Example: Basic Lunr Search Script
let idx = null;
let documents = [];
fetch("/search.json")
.then(response => response.json())
.then(data => {
documents = data;
idx = lunr(function () {
this.ref("url");
this.field("title");
this.field("content");
data.forEach(function (doc) {
this.add(doc);
}, this);
});
});
document.getElementById("search-input").addEventListener("input", function () {
const query = this.value;
const results = idx.search(query);
const output = document.getElementById("search-results");
output.innerHTML = "";
results.forEach(result => {
const match = documents.find(doc => doc.url === result.ref);
const item = document.createElement("li");
item.innerHTML = `${match.title}`;
output.appendChild(item);
});
});
Creating the Search UI
In your layout or documentation index page, add the following markup:
<input type="text" id="search-input" placeholder="Search documentation..." />
<ul id="search-results"></ul>
<script src="/assets/js/search.js"></script>
This creates a simple input field that triggers instant search as users type.
Improving the Search Experience
While Lunr is powerful, its default behavior can be extended in many ways:
Tokenizing and Boosting
- Boost title matches over content using
this.field("title", { boost: 10 }). - Ignore stopwords or use stemmers for fuzzy matching.
Highlighting Matching Terms
To enhance readability, highlight search terms in results using a simple regex-based highlighter or third-party libraries like mark.js.
Filtering by Category
If your documents include a category field in front matter, you can filter search results by category:
const filtered = documents.filter(doc => doc.category === "getting-started");
Deploying to GitHub Pages
Lunr works perfectly with GitHub Pages since everything is static. Make sure:
search.jsonis committed and not ignored by.gitignore- You're using relative URLs or baseurl correctly if your site is in a subfolder
No extra server configuration is needed. The client-side JS will handle everything at runtime.
SEO Considerations
Since search content is client-side, bots won’t index search.json meaningfully. However, your documentation pages themselves will still be fully indexable by search engines. Keep content accessible through both menus and internal linking.
Limitations of Lunr
While effective for many use cases, Lunr has some constraints:
- It loads the entire search index into the browser, which can be heavy on large sites.
- No fuzzy suggestions or typo correction out of the box.
- It only works with plain text—no dynamic filters or faceted search.
If your documentation grows significantly, consider migrating to Algolia DocSearch or building a server-backed solution.
Conclusion
Lunr.js is a simple, reliable way to add client-side search to any Jekyll site—especially for documentation and knowledge bases hosted on GitHub Pages. By generating a static JSON index and hooking it to a custom search UI, you empower your users to quickly find what they need without navigating through menus or page reloads.
In the next article, we’ll explore how to combine multiple collections into one unified search index, handling multilingual content and versioning in a single search experience.