How to Add Search to a Jekyll Site (Lunr.js vs Algolia)
Add full-text search to your Jekyll site using Lunr.js (free, no backend) or Algolia (fast, scalable). Setup guides for both options with pros and cons.
Jekyll doesn’t include search out of the box — but adding it is simpler than you might think. This guide covers the two best options: Lunr.js for a free, no-backend solution and Algolia for a fast, scalable search service.
Option 1: Lunr.js (Free, No Backend Required)
Lunr.js is a client-side search library. Jekyll generates a JSON search index at build time, and Lunr searches it entirely in the browser.
Pros: Free, privacy-friendly, no external service required, works on GitHub Pages
Cons: Index grows with site size, can be slow on very large sites (500+ posts)
Step 1: Generate a Search Index
Create search-index.json at your site root. This file is regenerated on every build:
---
layout: null
---
[
{% for post in site.posts %}
{
"title": {{ post.title | jsonify }},
"url": {{ post.url | absolute_url | jsonify }},
"date": {{ post.date | date: "%B %d, %Y" | jsonify }},
"excerpt": {{ post.excerpt | strip_html | truncatewords: 50 | jsonify }},
"content": {{ post.content | strip_html | truncatewords: 300 | jsonify }},
"tags": {{ post.tags | jsonify }},
"categories": {{ post.categories | jsonify }}
}{% unless forloop.last %},{% endunless %}
{% endfor %}
{% if site.posts.size > 0 and site.pages.size > 0 %},{% endif %}
{% for page in site.pages %}
{% if page.title and page.layout != null %}
{
"title": {{ page.title | jsonify }},
"url": {{ page.url | absolute_url | jsonify }},
"excerpt": {{ page.content | strip_html | truncatewords: 50 | jsonify }},
"content": {{ page.content | strip_html | truncatewords: 300 | jsonify }}
}{% unless forloop.last %},{% endunless %}
{% endif %}
{% endfor %}
]
Save this as search-index.json in your site root.
Step 2: Create the Search Page
Create search.md:
---
layout: page
title: Search
permalink: /search/
---
<input type="search" id="search-input" placeholder="Search posts and pages..." autofocus>
<div id="search-results"></div>
<script src="https://unpkg.com/lunr/lunr.js"></script>
<script>
let searchIndex;
let documents = {};
fetch('/search-index.json')
.then(res => res.json())
.then(data => {
data.forEach(doc => { documents[doc.url] = doc; });
searchIndex = lunr(function () {
this.ref('url');
this.field('title', { boost: 10 });
this.field('tags', { boost: 5 });
this.field('content');
data.forEach(doc => { this.add(doc); });
});
});
document.getElementById('search-input').addEventListener('input', function () {
const query = this.value.trim();
const resultsDiv = document.getElementById('search-results');
if (!query || !searchIndex) {
resultsDiv.innerHTML = '';
return;
}
const results = searchIndex.search(query + '*');
if (results.length === 0) {
resultsDiv.innerHTML = '<p>No results found.</p>';
return;
}
resultsDiv.innerHTML = results.map(result => {
const doc = documents[result.ref];
return `<div class="search-result">
<a href="${doc.url}"><h3>${doc.title}</h3></a>
<p>${doc.excerpt}</p>
</div>`;
}).join('');
});
</script>
Step 3: Add Search to Your Navigation
Add a search link or a header search box that submits to /search/?q=query:
<form action="/search/" method="get">
<input type="search" name="q" placeholder="Search...">
<button type="submit">Search</button>
</form>
Then in your search page, pre-fill from the URL parameter:
const params = new URLSearchParams(window.location.search);
const q = params.get('q');
if (q) {
document.getElementById('search-input').value = q;
// Trigger search...
}
Option 2: Algolia (Fast, Scalable)
Algolia is a hosted search service. It’s significantly faster than Lunr for large sites and provides instant results as you type.
Free plan: 10,000 search requests/month and 10,000 records
Pros: Fast, typo-tolerant, instant search, great developer experience
Cons: Requires an Algolia account, data is stored externally
Step 1: Create an Algolia Account
- Sign up at algolia.com
- Create a new application
- Create an index (e.g.
jekyllhub_production) - Note your Application ID, Search-Only API Key, and Admin API Key
Step 2: Install jekyll-algolia
# Gemfile
gem "jekyll-algolia"
# _config.yml
algolia:
application_id: YOUR_APP_ID
index_name: YOUR_INDEX_NAME
search_only_api_key: YOUR_SEARCH_ONLY_KEY
Important: Your search-only key is safe to put in _config.yml. Never put your Admin API key in version control.
Set the Admin key as an environment variable:
export ALGOLIA_API_KEY='your-admin-key'
Step 3: Index Your Content
bundle exec jekyll algolia
This pushes all your posts and pages to Algolia. Run this whenever you add new content (or add it to your CI/CD pipeline).
Step 4: Add InstantSearch to Your Site
Algolia provides InstantSearch.js for the frontend:
<!-- search.html -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/instantsearch.css@7/themes/satellite-min.css">
<div id="searchbox"></div>
<div id="hits"></div>
<div id="pagination"></div>
<script src="https://cdn.jsdelivr.net/npm/algoliasearch@4/dist/algoliasearch-lite.umd.js"></script>
<script src="https://cdn.jsdelivr.net/npm/instantsearch.js@4"></script>
<script>
const searchClient = algoliasearch('YOUR_APP_ID', 'YOUR_SEARCH_ONLY_KEY');
const search = instantsearch({
indexName: 'YOUR_INDEX_NAME',
searchClient,
});
search.addWidgets([
instantsearch.widgets.searchBox({
container: '#searchbox',
placeholder: 'Search posts...',
}),
instantsearch.widgets.hits({
container: '#hits',
templates: {
item(hit) {
return `
<a href="${hit.url}">
<h3>${instantsearch.highlight({ hit, attribute: 'title' })}</h3>
<p>${instantsearch.snippet({ hit, attribute: 'content' })}</p>
</a>
`;
},
empty: '<p>No results found.</p>',
},
}),
instantsearch.widgets.pagination({
container: '#pagination',
}),
]);
search.start();
</script>
Which Should You Choose?
| Lunr.js | Algolia | |
|---|---|---|
| Cost | Free | Free up to 10k requests/month |
| Setup | Simple | Moderate |
| Speed | Good (small sites) | Excellent (any size) |
| Typo tolerance | Limited | Built-in |
| Works offline | Yes | No |
| GitHub Pages | Yes | Yes |
| Privacy | Full (no external service) | Data sent to Algolia |
Choose Lunr.js if: your site has under 200 posts, you want zero dependencies, or privacy is a priority.
Choose Algolia if: you have a large site, want instant-as-you-type results, or need typo-tolerant search.
Many Jekyll themes on JekyllHub include search functionality built in — check the theme’s feature list before building from scratch.