Hugo Frontend with Algolia - Part Two

This is part two (part one is here) and the final about how you can implement search functions in your static web site with Hugo and Algolia. You do need to have an index populated with Algolia to follow along below, but for the most part it’s HTML and JS to give your website a search page, call the Algolia API and display the results.
Your Layout
We’ll keep things pretty basic here as some of the layout depends on the theme you have in your current site. It’s likely you already have some sort of search layout and partials that you can modify (backed up or in a commit you can revert to).
Create a new file in layouts/_default/search.html which will give you a new page accessible in your browser. Remember you can run hugo server to get the live updates rendering in a browser window as you make changes. Populate the file with the content below.
{{ define "main" }}
<div id="searchBox" class="text-center" style="margin-top:30px;"></div>
<section class="section-sm">
<div class="container">
<div class="row no-gutters justify-content-center">
<div class="col-lg-12">
<div class="row">
<div id="searchResults"></div>
</div>
</div>
</div>
</div>
</section>
{{ partial "search-algolia.html" }}
{{ end }}Again, this depends on your current theme but will create a new page with some basic HTML components. There are two we need to focus on - searchBox and searchResults. It’s probably obvious but one will hold the HTML for input used for entering search terms and the latter will display the results that return from Algolia’s API.
At the bottom of that code block we have a new Hugo partial called search-algolia.html. This file is what does most of the work for our search processing and is all JS.
InstantSearch
Create this new file layouts/partials/search-algolia.html and paste in the code below.
<script src="https://cdn.jsdelivr.net/npm/algoliasearch@4.5.1/dist/algoliasearch-lite.umd.js" integrity="sha256-EXPXz4W6pQgfYY3yTpnDa3OH8/EPn16ciVsPQ/ypsjk=" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/instantsearch.js@4.8.3/dist/instantsearch.production.min.js" integrity="sha256-LAGhRRdtVoD6RLo2qDQsU2mp+XVSciKRC8XPOBWmofM=" crossorigin="anonymous"></script>
<script type="text/javascript">
const searchClient = algoliasearch('YOUR_APP_ID', 'YOUR_API_KEY');
const search = instantsearch({
indexName: 'YOUR_INDEX',
searchClient,
});
const hitTemplate =
'<div class="col-lg-12 col-md-12 mb-4">' +
'<article class="card post-card" id="summary-{{ safeHTML "{{{ key }}}" }}">' +
'{{ safeHTML "<img loading=\"lazy\" class=\"w-100 rounded\" style=\"height:200px;object-fit:cover;\"src=\"{{{ image }}}\" alt=\"{{{ name }}}\">" }}' +
'<div class="card-body">' +
'<h3 class="h4 mb-4">' +
'{{ safeHTML "<a class=\"post-title\" href=\"{{{ permalink }}}\">{{{ title }}}</a>" }}' +
'</h3>' +
'<p>{{ safeHTML "{{{ summary }}}" }}</p>' +
'</div>' +
'</article>' +
'</div>';
const missTemplate =
'<div class="col-lg-10 col-md-10 mb-4">' +
'<article class="card post-card">' +
'<div class="card-body">' +
'<h3 class="h4 mb-4">No results</h3>' +
'<p>There were no results matching your search terms {{ safeHTML "<em>{{{ query }}}</em>" }}.</p>' +
'</div>' +
'</article>' +
'</div>';
search.addWidgets([
instantsearch.widgets.searchBox({
container: '#searchBox',
placeholder: "Search...",
showReset: false,
showSubmit: false,
autofocus: true,
}),
instantsearch.widgets.hits({
container: "#searchResults",
templates: {
item: hitTemplate,
empty: missTemplate,
}
})
]);
search.start();
</script>The first thing to note is that we’re using the Algolia JS UI library called InstantSearch to help cut some corners here. It handles the sending of the search terms to the Algolia API, getting the results and then displaying them in your template. InstantSearch also offer “widgets” that extent the functionality to include things like filtering, pagination and stats. We’ll use a couple of the basics here for the search box and the results.
We’re using the CDN option here to pull in the libraries needed, but you can use NPM for this too if using build tools.
At the top of that code block there are three items that you need to update with your own Algolia information - app ID, API key and index name. They’re all in your Algolia settings and you used some of these in part one. With these values and the libraries, we instantiate the search objects that we’ll then configure a bit more.
The hitTemplate and missTemplate are snippets of HTML with values from the search results mixed in - the ones enclosed in {{{ }}}. As these overlap with Hugo’s own formatting, watch out for when you get creative here and try to use triple curly braces. The hit template is used for when there are results and each returned item will reuse this template. The miss template is when there are no results and just shows some basic info to guide the user to better results next time.
Using the .addWidgets() method on the object we instantiated, we add two sets of config, one to the search box input field and one to the hits where the results will show. By specifying the container attribute and the IDs of the two HTML divs they get targeted with the changes.
By default loading the search page will make a call to the Algolia API and return all results with a blank search field. You should see the results rendered below the search field and styled subject to your theme, the hitTemplate template and any other CSS you have. The InstantSearch UI library adds a HTML form, with input field and buttons to your #searchBox div. Those elements have classes named along the lines of ais-* applied which you probably want to re-style as you see fit. In our code example above we have the submit and reset buttons disabled.
It’s important that the fields you’re expecting to use in the hitTemplate actually exist in your results that come back from Algolia. Compare what’s in your template to what you configured to populate the search index file in part one. If there’s something missing from the results, it’s not a fatal error, but things won’t display as you expect. The image value expected above is a good example, where there will be an empty/missing image if the search index doesn’t include a valid URL.
Done
That’s it. Hopefully you now have a basic search page on your site that fetches search results from Algolia and renders them nicely for your users. As always if there’s questions, drop them in the comments below.
Also see Static search with Algolia and Hugo 2 from Harry Cresswell, which shows how to extend some of the search options with tag filters.