Search
Hwaro generates a search index that works with Fuse.js for client-side search.
Configuration
Enable in config.toml:
[search]
enabled = true
format = "fuse_json"
fields = ["title", "content", "description", "tags", "url", "section"]
filename = "search.json"
exclude = ["/private", "/drafts"]
| Key | Type | Default | Description |
|---|---|---|---|
| enabled | bool | false | Generate search index |
| format | string | "fuse_json" | Search index format |
| fields | array | ["title", "content"] | Fields to include in index |
| filename | string | "search.json" | Output filename |
| exclude | array | [] | Paths (prefixes) to exclude from search index |
Generated Files
When enabled, Hwaro generates /search.json (configurable via filename):
[
{
"title": "My Post",
"url": "/blog/my-post/",
"content": "Page content...",
"description": "Post description",
"section": "blog",
"tags": ["tutorial"]
}
]
Fields Indexed
| Field | Description |
|---|---|
| title | Page title |
| url | Page URL |
| content | Page content (if `"content"` is in `fields`) |
| description | Page description |
| section | Section name |
| tags | Page tags |
Client-Side Implementation
Using Fuse.js
Add to your template:
<script src="https://cdn.jsdelivr.net/npm/fuse.js@7.0.0"></script>
<script>
let searchIndex = [];
// Load index
fetch('/search.json')
.then(res => res.json())
.then(data => {
searchIndex = data;
});
// Initialize Fuse.js
function search(query) {
const fuse = new Fuse(searchIndex, {
keys: ['title', 'content', 'description', 'tags'],
threshold: 0.3
});
return fuse.search(query);
}
</script>
Search Form
<form id="search-form">
<input type="search" id="search-input" placeholder="Search...">
</form>
<div id="search-results"></div>
<script>
const input = document.getElementById('search-input');
const results = document.getElementById('search-results');
input.addEventListener('input', (e) => {
const query = e.target.value;
if (query.length < 2) {
results.innerHTML = '';
return;
}
const matches = search(query);
results.innerHTML = matches
.slice(0, 10)
.map(m => `
<a href="${m.item.url}">
<h3>${m.item.title}</h3>
<p>${m.item.description || ''}</p>
</a>
`)
.join('');
});
</script>
Excluding Pages
Front Matter
Exclude individual pages from search with front matter:
+++
title = "Terms of Service"
in_search_index = false
+++
Configuration
Exclude entire sections or paths using config.toml:
[search]
exclude = ["/private", "/drafts"]
Field Selection
Control which fields appear in the search index by specifying fields:
[search]
enabled = true
fields = ["title", "description", "tags", "url"]
Available fields: title, content, description, tags, url, section.
Omitting content from fields significantly reduces the index file size for large sites.
Performance Tips
Large Sites
For sites with many pages:
- Remove
"content"fromfieldsto reduce index size - Use Fuse.js
ignoreLocationoption - Implement debounced search
function debounce(fn, delay) {
let timeout;
return (...args) => {
clearTimeout(timeout);
timeout = setTimeout(() => fn(...args), delay);
};
}
input.addEventListener('input', debounce((e) => {
// search logic
}, 200));
Lazy Loading
Load index only when search is focused:
let indexLoaded = false;
input.addEventListener('focus', async () => {
if (indexLoaded) return;
const res = await fetch('/search.json');
searchIndex = await res.json();
indexLoaded = true;
});
Alternative: Pagefind
For larger sites, consider Pagefind:
# After build
npx pagefind --site public
Add to config as post-build hook:
[build]
hooks.post = ["npx pagefind --site public"]