Searching Static Site
Motivation
Statically generated sites are the easiest way to get your content website up and running. But as the amount of content increases it gets too cluttered to list everything in the form of a paginated list of links. Tags and categorization helps a bit but a dedicated fast lookup mechanism never hurts. Site wide search using a google search embed, algolia like service or a dedicated elastic search backend is trivial but may be an overkill for the small blog site you are running. The purpose of this piece is to implement a simple javascript based search mechanism that works completely on the client side and is fast and accurate enough to be useful.
Idea
Easiest way to implement this is to accumulate all content in the form of a javascript object and then do a keyword search and return the result. This might seem like it will create a lot of network payload but for a small blog site it translates to a few hundred articles and that much text content shouldn’t be an issue. Search on accumulated data can be implemented natively but it is better to use a mature library to do the keyword search and return ranked results.
In this article I’m using hugo
and fuse.js
to implement the search as a partial
html content which can be embedded into any page where search is required.
Hugo Partial
Official Documnetation for hugo partials.
For this article I’ll create a new partial called search.html
which will contain both the search logic and UI.
Accumulating Data
Using Hugo Page Variables I’m iterating over all relevant content that I want to make searchable and then creating an array of JSON objects that contains the relevant information for search.
var allPosts = [];
{{ $section := where .Site.RegularPages "Section" "in" (slice "posts" "others" ) }}
{{ range ($section) }}
allPosts.push(
{
'title': {{ .Title }},
'summary': {{ .Summary }},
'content': {{ .Content }},
'link': {{ .Permalink }}
}
);
{{ end }}
Search : fuse.js
Best explained in the demo link provided by fuse.js
, the data is then indexed and fuzzy searched using the search pattern provided by the user.
const fuse = new Fuse(allPosts, options);
// returns a list of items with data schema resembling allPosts
// pattern: received from input text field
let searchResult = fuse.search(pattern);
Skipping the input text field and button code.
Search Result
There are ways to configure the search experience but the defaults work great for me. Only thing I wanted was to limit the max number of search results which I easily enforced while creating the html
for the search result.
// using https://tachyons.io/ CSS
let htmlContent = "<h3>Search Result For : <i>" + pattern + "</i></h3>";
htmlContent += "<ul class='pl0 mr3'>";
// limiting max results
let maxResult = searchResult.length >= 5 ? 5 : searchResult.length;
for (var i = 0; i < maxResult; i++) {
htmlContent +=
"<li class='list f6 f4-ns fw4 dib pr3'><a class='makeall-title hover-black no-underline black' href='";
htmlContent += searchResult[i].item.link;
htmlContent += "'title='" + searchResult[i].item.title + "'page'>";
htmlContent += searchResult[i].item.title;
htmlContent += "</a></li><hr />";
}
htmlContent += "</ul>";
For my use case I render the htmlContent
inside a modal but you can place it wherever you want.