Adding a simple search page to my personal website with searchmysite.net

Introduction

This post shows how I added a simple search page to my personal website with searchmysite.net. You can click on it and try it out via the Search link at the top of the page. Note that it is very simple.

I know I don’t really need search functionality given that my personal website only currently has 10 posts including this one, but I wanted to test the process out and demonstrate it.

As per my previous post, most interest in searchmysite.net so far has been in the site discovery functionality (i.e. search) rather than in the search as a service, but I still want to make sure the search as a service offering is complete. I know that some people have been experimenting with the searchmysite.net API, but as far as I’m aware no one has used it to add search functionality to their own site yet, so I thought I’d do it myself.

I’ve used vanilla JavaScript to try to make the process as clear as possible, and hopefully to make the solution as portable as possible. This of course isn’t the only way, so please feel to adapt. If I wasn’t primarily doing this for demonstration purposes, given my site is built with Hugo, I’d probably start by looking at Hugo’s data templates. Similarly, if I was already using a JavaScript framework on my site I’d probably look to use that. And given it is a very small static site, I’d also take a look at a pure client-side solution, e.g. lunr.js, with the index file generated as part of my build pipeline.

Implementation

The JavaScript and results container

Here’s the JavaScript that gets the search query from URL parameters (defaulting to *), constructs the API call, makes the API call, and iterates through the results to construct the <li><a href="${result.url}" class="title">${result.title}</a></li> for each result:

  // Construct the API query
  const apiEndpoint = 'https://searchmysite.net/api/v1/search/michael-lewis.com';
  let urlParams = new URLSearchParams(window.location.search);
  let queryParam = urlParams.get('q');
  if (queryParam == null || queryParam == '') { queryParam = '*' }
  let apiQuery = apiEndpoint.concat('?q=', queryParam);

  // Build the results (using fetch rather than XMLHttpRequest)
  fetch(apiQuery)
    .then((resp) => resp.json()) 
    .then(function(data) {
      let searchResults = data.results;
      document.getElementById('query').value = queryParam; // Set the value of the search box to the query
      if (searchResults && searchResults.length > 0) {
        return searchResults.map(function(result) {
          // Each result is going to be displayed as <li><a href="${result.url}" class="title">${result.title}</a></li>
          let li = document.createElement('li'), a = document.createElement('a');
          a.appendChild(document.createTextNode(`${result.title}`));
          a.href = `${result.url}`;
          a.classList.add('title');
          li.appendChild(a);
          // Each result is added to the <ul id="results"></ul>
          document.getElementById('results').appendChild(li);
        })
      }
      else {
        // If there are no results update the <h1 class="title" id="results-title">Results</h1>
        document.getElementById('results-title').innerText = 'No results';
      }
    })
    .catch(function(error) {
      console.log(error);
    });

This needs a form for the query and a container for the results:

      <form class="centered" style="font-family: sans-serif;">
        <input type="search" class="form-control" placeholder="Search..." name="q" id="query" aria-label="Search query">
        <input type="submit" value="Search">
      </form>
      <h1 class="title" id="results-title">Results</h1>

      <ul id="results"></ul>

Source for these is at https://gitlab.com/michael-lewis/website/-/blob/master/layouts/search/single.html.

Creating the search template and page in Hugo

First step was to create /layouts/search/single.html to provide a new template with the content area overridden. This is where the form and JavaScript shown above goes.

Second step was to create a new content page using this template at /content/search/index.md:

---
title: "Search michael-lewis.com"
tags: ["search"]
keywords: ["search"]
description: "Search michael-lewis.com"
categories: []
draft: false
type: "search"
---

There isn’t actually any content in this page beyond the frontmatter because everything that is needed is in the template.

The third step was to add a link to the search page in the config.toml.

I was going to override /layouts/partials/header.html so I could have my own search box on every page, but (i) I already have two customisations I need to upgrade whenever I update the hugo-coder submodule (see Building a website with Hugo and GitLab Pages) and I wanted to try and avoid a third if possible, (ii) I would have had to have spent a bit of time modifying the styles to make it look okay.

Conclusion

So there it is - a super simple site search page in 27 lines of JavaScript. It is pretty basic, but it works, and hopefully demonstrates how it works.

Note that if you publish a new page or modify an existing page, the changes won’t appear in the search results until either (i) the next scheduled reindex has completed (which could be up to 3.5 days), or (ii) you manually trigger a reindex (i.e. login to the Manage Site, click the Indexing tab, and click the Reindex button). It would be great if there was a simple API call that you could put into your static site build pipeline to trigger a reindex automatically - let me know if there is interest in this and I can build it.