Ruddra.com

Add Search Functionality in Hugo

Add Search Functionality in Hugo

Adding search functionality make your website more accessible and usable to the stakeholders. In this article, I am going to share on how you can add this in a static site generated by hugo.

Expose JSON output

In the first step, we need to expose the json output for the files. For that, we need to change in config.yaml:

outputs:
  home:
    - HTML
    - RSS
    - JSON # add this change

Same configuration for config.toml:

[outputs]
    home = [ "HTML", "JSON", "RSS"]

Then create a file named index.json at the root of your project and add the following code:

{
    "items" : [
    {{ range $.Site.RegularPages -}}
    {
        
        "url" : "{{ .Permalink }}",
        "title" : "{{ .Title }}",
        "content": {{ .Content | plainify | jsonify }},
        
    {{ end -}}
    ]
}

Then if you run the server, you should be able to access localhost:1313/index.json file and it should return a json file containing the url, title, and contents of your posts. It should look something like this:

json output

Create layout

The next step is to add a page where we will be showing the search content. For that we need to make two changes.

  1. Create a content/search folder and add a _index.md file in content/search folder.
  2. Go to the layouts folder and create layouts/section/search.html file.

Folder structure should look like this:

Project
├── contents
├── content
│   └── search
│       └── _index.md
├── layouts
│   ├── partials
│   └── section
│       └── search.html
├── static
├── themes
├── index.json
└── config.toml

Build the search page

Now let us build the search page. For that we can use the same layout structure as your theme’s any single.htmlfile at the layouts/_default folder. For example, if you use Anake theme, you can use it’s single.html file as template for rendering the page. Here is an example:

{{ define "main" }}
{{end}}

Apart from that, update the _index.md file mentioned above with the following code:

+++
title = "Search"
description = "This is the search page for this website."
date ="2022-01-28T00:00:00+00:00"
+++

Now we can go to localhost:1313/search page, where we should be able to see a blank page.

Add lunr.js file

Now, you need to add the lunr.js file to your project. Add it by using:

<body>
    ...
    <script src="https://cdnjs.cloudflare.com/ajax/libs/lunr.js/2.0.2/lunr.js"></script>
</body>

Add search functionality

Add HTML

Now our structure has been placed. We can build the search functionality on top of it. First we need to add an input field to enter the search query. Let us do that in search.html file:

{{ define "main" }}
<div class="container content">
    <section class="post">
        <h1 class="post-title">Search:</h1>
        <input type="text" id="search-input" placeholder="" oninput="showSearchResults()">
        <br />
        <ul id="list"></ul> <!-- this list is for rendering the results -->
    </section>
</div>
{{end}}

Add JavaScript

In the code, you can see that we wrote oninput="showSearchResults(), which means on each input, the showSearchResults() function will be executed. Let’s write that function in a separate javascript file, for example like in static/js/search.js:

var searchElem = document.getElementById("search-input");
var posts;
function loadSearch() { 
    // call the index.json file from server by http get request
    var xhr = new XMLHttpRequest();
    xhr.onreadystatechange = function () {
        if (xhr.readyState === 4) {
            if (xhr.status === 200) {
                var data = JSON.parse(xhr.responseText);
                if (data) {
                    posts = data.items; // load json data
                }
            } else {
                console.log(xhr.responseText);
            }
        }
    };
    xhr.open('GET', "../index.json");
    xhr.send();
}
loadSearch(); // call loadsearch to load the json file
function showSearchResults() {
    var query = searchElem.value || ''; // get the value from input
    var searchString = query.replace(/[^\w\s]/gi, ''); // clear white spaces
    var target = document.getElementById('list'); // target the ul list to render the results
    var postsByTitle = posts.reduce((acc, curr) => { // map lunr search index to your articles
        acc[curr.title] = curr;
        return acc;
    }, {}
    );
    // build lunr index file
    var index = lunr(function () {
        this.ref('title')
        this.field('content')
        posts.forEach(function (doc) {
            this.add(doc)
        }, this)
    });
    // search in lunr index
    if (searchString && searchString != '') {
        var matches = index.search(searchString);
        var matchPosts = [];
        matches.forEach((m) => {
            matchPosts.push(postsByTitle[m.ref]);
        });
        if (matchPosts.length > 0) {
            // match found with input text and lunr index
            target.innerHTML = matchPosts.map(function (p) {
                if (p != undefined) {
                    return `<li>
                        ${p.date} -
                        <a href="${p.url}"> ${p.title}</a>
                        </li>`;
                }
            }).join('');
        } else {
            // if no results found, then render a general message
            target.innerHTML = `<br><h2 style="text-align:center">No search results found</h2>`;
        };
    } else {
        target.innerHTML = ''
    }
};

Now let us add this into the html of hugo (in footer file preferably):

 <script src="/js/search.js"></script>

Cool, now our search functionality should be working and the output should look like this localhost:1313/search/ uri.

hugo search output

In conclusion

With help of Lunr and Hugo’s own functionality, we have added search functionality in the static site. Hope you like it and if you have any comments, please share below.

--

If you like this article, you can buy me a coffee. Thanks!

Last updated: Nov 06, 2022


← Previous
Rollout your Google fonts faster with Cloudflare Cache

Serve your favourite Google fonts faster with Cloudflare; it even works with custom fonts.

Next →
Use NVIDIA Cuda in Docker for Data Science Projects

Make ML process faster with the power of GPU inside Docker.

Share Your Thoughts
M↓ Markdown