Implementing a static search for Hugo website


I am hosting this website on Cloudflare Pages. The site is Hugo-based and it is static, but I wanted to find a solution so users have a good search experience. Everything for this search solution is client-side. Which, for a small site such as this, is no problem. This solution may not be a solution for larger websites.

You can test my implementation by clicking Search near the top of this website.

There are quite a few static libraries that can create a search experience on Hugo-based websites. Some of the libraries I explored were:

  1. Lunr.js : A lightweight, easy-to-use, full-text search engine library for JavaScript. Lunr.js is popular for static websites, and you can find a Hugo integration guide here: https://gohugo.io/tools/search/#client-side-search-with-lunrjs
  2. Fuse.js : A lightweight, fuzzy-search library for JavaScript. Fuse.js is excellent for simple searches and smaller datasets. You can find a tutorial on integrating Fuse.js with Hugo here: https://www.jameswright.dev/blog/2020-05-30-fuzzy-search-in-hugo-with-fuse-js
  3. FlexSearch : A fast full-text search library with support for features like tokenization, stemming, and stopword filtering. You can find a tutorial on integrating FlexSearch with Hugo here: https://davidsneighbour.com/blog/implementing-flexsearch-into-your-hugo-website/

Others…

In the end, I settled on a simple library implementation of:

Pagefind : PageFind is a lightweight, client-side JavaScript search library specifically designed for static websites. It offers full-text search capabilities with simple configuration and customization options. The library is easy to set up and integrates seamlessly into a Hugo website.

  • Full-text search for static websites
  • Easy to set up and configure
  • JSON-based index file
  • Supports pagination and search highlighting
  • Small footprint (only ~2.4KB minified and gzipped)

Adding Pagefind as a git module to the website and editing config.toml file.

In the root of your Hugo website run:

git submodule add https://github.com/gwongz/pagefind.git themes/pagefind

This will add the pagefind library for use on your website. Yes, I am using the themes directory, for no reason other than my convenience.

Then edit your config.toml and add these parameters to their corresponding sections:

[params]
  search_engine = "pagefind"

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

Creating json format for the search engine.

Create a new file named index.json in the layouts/_default directory of your Hugo website and add the following content:

{{- $pages := where .Site.RegularPages "Type" "not in"  (slice "json" "css" "js") -}}
{{- printf "{\"version\":\"%s\",\"pages\":[" (now.Format "20060102150405") -}}
{{- range $index, $page := $pages -}}
{{- if $index }},{{ end -}}
{{- printf "{\"title\":%q,\"description\":%q,\"url\":%q,\"content\":%q}" $page.Title $page.Description $page.Permalink $page.Plain -}}
{{- end -}}
{{- printf "]}" -}}

The above will create a minified JSON file. Smaller is better!

Add the search form to your site.

To allow users to search your site, add a search form to your site’s layout. You can put the search form in a partial template and include it in your header, footer, or any other place you want. In this example, we’ll create a new file called search-form.html in the layouts/partials directory and add the following code:

<form id="search-form">
  <input type="search" id="search-input" placeholder="Search" />
  <button type="submit">Search</button>
</form>

<div id="search-results"></div>

Now, include the search-form.html partial in your main layout file (e.g., layouts/_default/baseof.html). Add this line where you want the search form to appear:

{{ partial "search-form.html" . }}

Javascript, let’s make it work!

Next, you’ll need to add some JavaScript code to your site to implement the search functionality. Create a new file called pagefind.js in the static/js directory of your Hugo website and add the following code:

document.getElementById("search-form").addEventListener("submit", function (event) {
  event.preventDefault();

  const searchInput = document.getElementById("search-input");
  const searchResults = document.getElementById("search-results");

  fetch("/index.json")
    .then((response) => response.json())
    .then((data) => {
      const searchTerm = searchInput.value.toLowerCase();
      const matchingPages = data.pages.filter((page) => {
        return (
          page.title.toLowerCase().includes(searchTerm) ||
          page.description.toLowerCase().includes(searchTerm) ||
          page.content.toLowerCase().includes(searchTerm)
        );
      });

      searchResults.innerHTML = "";
      if (matchingPages.length > 0) {
        matchingPages.forEach((page) => {
          const resultItem = document.createElement("div");
          const resultTitle = document.createElement("h3");
          const resultLink = document.createElement("a");
          const resultDescription = document.createElement("p");

          resultLink.href = page.url;
          resultLink.textContent = page.title;
          resultTitle.appendChild(resultLink);
          resultDescription.textContent = page.description;

          resultItem.appendChild(resultTitle);
          resultItem.appendChild(resultDescription);

          searchResults.appendChild(resultItem);
        });
      } else {
        searchResults.innerHTML = "<p>No results found.</p>";
      }
    });
});

Now, include the pagefind.js script in your main layout file (e.g., layouts/_default/baseof.html). Add this line right before the closing </body> tag:

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

Prettify and responsive

At this point, if you tested your site, the search function should be functioning; but it will not look great. and will only show results in the exact place where you implemented the search box. Let’s fix that by making the search engine appear in a floating modal.

Update the search-form.html file in the layouts/partials directory with the following content:

<div id="search-modal" class="search-modal">
  <div class="search-modal-content">
    <span class="search-modal-close">&times;</span>
    <form id="search-form">
      <input type="search" id="search-input" placeholder="Search" />
      <button type="submit">Search</button>
    </form>
    <div id="search-results"></div>
  </div>
</div>

<button id="search-modal-open" class="search-modal-open">Search</button>

Create a new CSS file named search-modal.css in the static/css directory to style the search modal:

.search-modal {
    display: none;
    position: fixed;
    z-index: 1000;
    left: 0;
    top: 0;
    width: 100%;
    height: 100%;
    overflow: auto;
    background-color: rgba(0, 0, 0, 0.4);
  }
  
  .search-modal-content {
    background-color: #39424E;
    margin: 15% auto;
    padding: 20px;
    border: 1px solid #888;
    width: 50%;
  }
  
  .search-modal-close {
    color: #aaa;
    float: right;
    font-size: 28px;
    font-weight: bold;
    cursor: pointer;
  }
  
  .search-modal-close:hover,
  .search-modal-close:focus {
    color: #000;
    text-decoration: none;
    cursor: pointer;
  }
  
  .search-modal-open {
    position: fixed;
    top: 10px;
    right: 10px;
    z-index: 999;
    background-color: #000;
    color: #fff;
    border: none;
    padding: 10px 15px;
    font-size: 16px;
    cursor: pointer;
  }

@media screen and (max-width: 768px) {
  .search-modal-content {
    width: 80%;
  }
}

@media screen and (max-width: 480px) {
  .search-modal-content {
    width: 95%;
  }
}

#search-form {
  display: flex;
  align-items: center;
  justify-content: center;
  margin-bottom: 1rem;
}

#search-input {
  flex-grow: 1;
  padding: 0.5rem;
  font-size: 1rem;
  border: 1px solid #ccc;
  border-radius: 4px 0 0 4px;
  outline: none;
  transition: border 0.3s;
}

#search-input:focus {
  border-color: #007bff;
}

#search-form button {
  background-color: #000;
  color: #fff;
  border: none;
  padding: 0.5rem 1rem;
  font-size: 1rem;
  cursor: pointer;
  border-radius: 0 4px 4px 0;
  transition: background-color 0.3s;
}

#search-form button:hover {
  background-color: #535353;
}

#search-results h3 {
  margin-top: 0;
}

#search-results a {
  text-decoration: none;
  color: #007bff;
}

#search-results a:hover {
  text-decoration: underline;
}

Include the search-modal.css file in your main layout file (e.g., layouts/_default/baseof.html). Add this line inside the tag:

<link rel="stylesheet" href="/css/search-modal.css">

Update the pagefind.js file in the static/js directory to handle opening and closing the search modal:

const searchModal = document.getElementById("search-modal");
const searchModalOpen = document.getElementById("search-modal-open");
const searchModalClose = document.getElementsByClassName("search-modal-close")[0];

searchModalOpen.onclick = function () {
  searchModal.style.display = "block";
};

searchModalClose.onclick = function () {
  searchModal.style.display = "none";
};

window.onclick = function (event) {
  if (event.target == searchModal) {
    searchModal.style.display = "none";
  }
};

// The rest of the search functionality code

Mission Accomplished!

You now have a working, lightweight search function on your Hugo website in a few steps.

×
Page views: 0