Blog statis Jekyll tidak punya database, sehingga fitur pencarian tidak bisa dibuat seperti di WordPress yang query ke database. Tapi bukan berarti tidak bisa sama sekali. Dengan Lunr.js — library pencarian full-text berbasis JavaScript — kamu bisa membuat fitur search yang bekerja sepenuhnya di sisi client (browser), tanpa server, tanpa plugin, dan tanpa biaya tambahan.
Bagaimana Cara Kerjanya?
Alur kerjanya sederhana:
- Saat build, Jekyll menghasilkan file JSON (
search.json) berisi semua konten artikel (judul, URL, excerpt, kategori, tags) - Saat halaman dimuat, Lunr.js membaca
search.jsondan membangun index pencarian di memori browser - Saat user mengetik, Lunr.js mencari di index tersebut dan menampilkan hasilnya secara real-time — tanpa request ke server
Langkah 1: Tambahkan Lunr.js
Lunr.js sudah tersedia di project ini di assets/js/lunr.js. Jika belum ada, download dari lunrjs.com atau via CDN:
<script src="https://unpkg.com/lunr/lunr.js"></script>
Langkah 2: Buat File Data JSON
Buat file search.json di root project. Ini adalah file Liquid yang akan di-generate Jekyll menjadi JSON berisi semua konten artikel:
---
layout: none
---
[
{% for post in site.posts %}
{
"title" : {{ post.title | jsonify }},
"url" : {{ post.url | relative_url | jsonify }},
"date" : {{ post.date | date: "%d %B %Y" | jsonify }},
"categories" : {{ post.categories | join: ", " | jsonify }},
"tags" : {{ post.tags | join: ", " | jsonify }},
"content" : {{ post.content | strip_html | strip_newlines | truncatewords: 200 | jsonify }}
}{% unless forloop.last %},{% endunless %}
{% endfor %}
]
Setelah build, Jekyll akan menghasilkan file /search.json berisi data semua artikel dalam format JSON.
Langkah 3: Buat Halaman Search
Buat file _pages/search.html:
---
layout: page
title: Cari Artikel
permalink: /search/
---
<div class="search-container">
<input
type="search"
id="search-input"
placeholder="Ketik kata kunci..."
autocomplete="off"
autofocus
aria-label="Cari artikel"
/>
<p id="search-status" class="search-status"></p>
</div>
<div id="search-results" class="search-results"></div>
<script>
// Muat data dan inisialisasi Lunr
let lunrIndex;
let searchData;
fetch("/search.json")
.then((response) => response.json())
.then((data) => {
searchData = data;
// Bangun index Lunr
lunrIndex = lunr(function () {
this.ref("url");
this.field("title", { boost: 10 }); // judul lebih penting
this.field("categories", { boost: 5 });
this.field("tags", { boost: 5 });
this.field("content");
data.forEach(function (doc) {
this.add(doc);
}, this);
});
// Cek jika ada query di URL (?q=keyword)
const urlParams = new URLSearchParams(window.location.search);
const query = urlParams.get("q");
if (query) {
document.getElementById("search-input").value = query;
doSearch(query);
}
});
// Jalankan pencarian
function doSearch(query) {
const resultsContainer = document.getElementById("search-results");
const statusEl = document.getElementById("search-status");
if (!query || query.trim().length < 2) {
resultsContainer.innerHTML = "";
statusEl.textContent = "";
return;
}
let results;
try {
// Coba pencarian fuzzy dengan wildcard
results = lunrIndex.search(query + "*");
// Jika tidak ada hasil, coba tanpa wildcard
if (results.length === 0) {
results = lunrIndex.search(query);
}
} catch (e) {
results = [];
}
if (results.length === 0) {
resultsContainer.innerHTML =
'<p class="no-results">Tidak ada artikel yang cocok dengan pencarian "<strong>' +
escapeHtml(query) +
'</strong>".</p>';
statusEl.textContent = "";
return;
}
statusEl.textContent =
results.length + ' artikel ditemukan untuk "' + query + '"';
const html = results
.map(function (result) {
const item = searchData.find((d) => d.url === result.ref);
if (!item) return "";
return `
<article class="search-result-item">
<h2><a href="${item.url}">${escapeHtml(item.title)}</a></h2>
<div class="meta">
<span class="date">${item.date}</span>
${item.categories ? '<span class="categories">' + escapeHtml(item.categories) + "</span>" : ""}
</div>
<p>${escapeHtml(item.content.substring(0, 150))}...</p>
</article>
`;
})
.join("");
resultsContainer.innerHTML = html;
}
// Escape HTML untuk keamanan
function escapeHtml(text) {
const div = document.createElement("div");
div.appendChild(document.createTextNode(text));
return div.innerHTML;
}
// Trigger search saat mengetik (debounce 300ms)
let debounceTimer;
document
.getElementById("search-input")
.addEventListener("input", function () {
clearTimeout(debounceTimer);
const query = this.value.trim();
debounceTimer = setTimeout(function () {
doSearch(query);
// Update URL tanpa reload halaman
const url = new URL(window.location);
if (query) {
url.searchParams.set("q", query);
} else {
url.searchParams.delete("q");
}
window.history.replaceState({}, "", url);
}, 300);
});
</script>
Langkah 4: Tambahkan CSS untuk Search
Di file CSS kamu:
.search-container {
margin-bottom: 2rem;
}
#search-input {
width: 100%;
padding: 0.75rem 1rem;
font-size: 1rem;
border: 2px solid var(--border-color);
border-radius: 8px;
background-color: var(--bg-color);
color: var(--text-color);
outline: none;
transition: border-color 0.2s;
}
#search-input:focus {
border-color: var(--link-color);
}
.search-status {
color: var(--text-muted);
font-size: 0.875rem;
margin-top: 0.5rem;
}
.search-result-item {
padding: 1.25rem 0;
border-bottom: 1px solid var(--border-color);
}
.search-result-item:last-child {
border-bottom: none;
}
.search-result-item h2 {
margin-bottom: 0.25rem;
font-size: 1.1rem;
}
.search-result-item .meta {
font-size: 0.85rem;
color: var(--text-muted);
margin-bottom: 0.5rem;
}
.no-results {
color: var(--text-muted);
text-align: center;
padding: 2rem;
}
Langkah 5: Tambahkan Link Search ke Navigasi
Di layout header, tambahkan link ke halaman search:
<a href="/search/" aria-label="Cari artikel">
<svg
xmlns="http://www.w3.org/2000/svg"
width="18"
height="18"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
>
<circle cx="11" cy="11" r="8"></circle>
<line x1="21" y1="21" x2="16.65" y2="16.65"></line>
</svg>
</a>
Fitur Bonus: Search dari Navbar
Tambahkan form search kecil di navbar yang redirect ke halaman search:
<form action="/search/" method="get" class="navbar-search">
<input type="search" name="q" placeholder="Cari..." aria-label="Cari" />
<button type="submit">🔍</button>
</form>
Karena halaman search sudah membaca parameter ?q= dari URL, form ini langsung akan menampilkan hasil saat user submit.
Kesimpulan
Dengan Lunr.js dan satu file search.json yang dihasilkan Jekyll, kamu bisa membangun fitur pencarian yang responsif dan bekerja sepenuhnya di browser — tanpa server, tanpa API, tanpa biaya. Lunr.js sudah termasuk dalam banyak tema Jekyll populer, sehingga implementasinya tinggal mengikuti pola ini.
Kekurangannya: index pencarian dibangun ulang setiap kali halaman dimuat dan semua data JSON diunduh sekaligus. Untuk blog dengan ribuan artikel, ini bisa membuat loading sedikit lebih lambat. Tapi untuk blog biasa dengan ratusan artikel, performa Lunr.js lebih dari memadai.
Kiki/🎮🍉⌨️🍩💻