Adding a search to your Hugo website

Adding client-side-search to your Hugo website

Posted: 2024-06-04
Stats: 425 words / ~2 minutes

Introduction

In this tutorial I’ll show you how you can add a client-side-search page to your Hugo website.

This is done using Javascript and depends on fuse.js.

This tutorial is split in 5 parts:

  1. Copying fuse.js dependencies
  2. Configuring search layout
  3. Adding search page
  4. Editing hugo.yml
  5. Editing index.json

How it works

The search is processed by fuse.js. You can read more here more about the fuse.js Scoring theory or the API reference.

Getting started

Copying some fuse.js dependencies

First, you need to copy the fuse.js library:

wget https://cdn.jsdelivr.net/npm/fuse.js/dist/fuse.js

Copy it to static/js/search/fuse.js.

Configuring search layout

Next, we need to add a search layout. Paste the following to layouts/_default/search.html:

{{ define "main" }}
<body>
    <noscript>
        <div style="color: red;">Please enable JavaScript to use the search functionality.</div>
    </noscript>

    <div id="loading" style="display: block;">Loading...</div>
    <div id="search-container" style="display: none;">
        <input type="text" id="search-input" placeholder="Search...">
        <ul id="search-results"></ul>
    </div>

    <script src="/js/search/fuse.js"></script>
    <script>
        document.addEventListener('DOMContentLoaded', function() {
            const loadingElement = document.getElementById('loading');
            const searchContainer = document.getElementById('search-container');
            const searchInput = document.getElementById('search-input');
            const searchResults = document.getElementById('search-results');

            fetch('/index.json')
                .then(response => response.json())
                .then(data => {
                    loadingElement.style.display = 'none'; // Hide loading indicator
                    searchContainer.style.display = 'block'; // Show search container

                    const options = {
                        keys: ['title', 'tags', 'categories', 'contents']
                    };
                    const fuse = new Fuse(data, options);

                    const performSearch = () => {
                        const query = searchInput.value;
                        const results = fuse.search(query);

                        searchResults.innerHTML = '';
                        results.forEach(result => {
                            const item = result.item;
                            const li = document.createElement('li');
                            const a = document.createElement('a');
                            a.href = item.permalink;
                            a.textContent = item.title;
                            li.appendChild(a);
                            searchResults.appendChild(li);
                        });
                    };

                    searchInput.addEventListener('input', performSearch);

                    // Perform initial search if there's already a value in the search input
                    if (searchInput.value) {
                        performSearch();
                    }
                })
                .catch(error => {
                    loadingElement.style.display = 'none'; // Hide loading indicator
                    console.error('Error loading index.json:', error);
                });
        });
    </script>
</body>
{{ end }}

Adding search page

The last thing is to add a page. Just create a file named site.md in your contents/ directory. Paste the following content:

---
title: "Search"
layout: "search"
---

Editing hugo.yml

You need to tell Hugo that a index.json page should be generated. This is used by the script for the search results:

outputs:
  home:
    - html
    - json

Add the json line.

Editing index.json

Hugo needs a template for creating the JSON. Create a file called index.json in the layouts/_default directory. Paste the following content:

{{- $.Scratch.Add "index" slice -}}
{{- range .Site.RegularPages -}}
    {{- $.Scratch.Add "index" (dict "title" .Title "tags" .Params.tags "categories" .Params.categories "contents" .Plain "permalink" .Permalink) -}}
{{- end -}}
{{- $.Scratch.Get "index" | jsonify -}}

End

You’re done! Test the search, and let me know if something doesn’t work.

This is my 14th post at 100DaysToOffload challenge

You can contact me if you wish to comment or propose a correction.