jahed.dev

Adding Search to a Static Website

My website is made up of static pages, there is no central database. How do I implement search functionality so users can find the content they're looking for?

One of the biggest downsides when building a static website is the inability to query your data. Sure you can tag and categorise pages and create yet another page that lists them, but that alone is extremely limiting for the user. If you want to give users the ability to search through your website using plain text or any combination of tags, you can't do so on a static website. Well, you can, by creating endless permutations of every possible user input but let's not get into that.

So, what's the solution? There's a few. None of them are perfect.

Option 1. Use a third-party service as your search engine

Google and others already provide site-specific search widgets for you to drop in and use. The downside is, you'll need to wait for their bots to crawl your pages for any changes. Also, functionality is restricted and you'll have a "Google" themed widget messing up your UI.

Algolia is a more recent engine that allows you to provide a more integrated experience.

Option 2. Run searches on the server-side

This one's pretty intuitive. Since your page requires user input, it makes sense to create a backend that can process those inputs and provide a result. The downside is, you lose some benefits of static generation, namely the reduced complexity in your infrastructure.

Option 3. Run searches on the client-side

This is the simplest option and works well with static generation. The down side is, the browser will need to support JavaScript and will need to download your entire dataset to run queries on it. I'll be going through this approach in this article.

Note: While initially I went with Option 3 for this article, I eventually switched to a mix of Option 1 and 2. Instead of a widget, it's a simple link to DuckDuckGo with a site filter. To me, this is a lot more flexible. It prevents the need for JavaScript and dedicated search engines are a lot more accurate.


Running searches on the client-side

So, we're going with Option 3. To get this working, we'll need 3 things:

  1. What you're searching. e.g. your blog posts.
  2. What the user's searching for. e.g. the search string.
  3. How you're searching for it. i.e. the search algorithm.

1. Getting what you're searching.

What do you want users to search? It's worth mentioning that the more you want your users to be able to search, the more data they'll have to download and process. Here's what I went with.

Next, is what you want to present in the search results. I wanted to show a preview of the post, which required

So now we have a complete set of attributes:

Now we need to generate the data to be consumed client-side. This is simple to do with most static site generators, just create a template that outputs to JSON. Here's an example in Middleman using ERB.

// posts.json.erb
<%= blog.articles.map{|article| {
  :title => article.title,
  :url => article.url,
  :tags => article.tags,
  :image => if article.data.image then image_path(article.data.image) else nil end,
  :date => article.date.iso8601
}}.to_json %>

This produces something like:

// https://your-website.com/search/posts.json
[
  {
    "id": "adding-search-to-a-static-website/index",
    "title": "Adding Search to a Static Website",
    "url": "/blog/2016/12/07/adding-search-to-a-static-website/",
    "tags": ["article", "webdev"],
    "image": null,
    "date": "2016-12-07T00:00:00Z"
  }
  //...
]

2. Getting what the user's searching for

This will just be a simple form. When the form is submitted, it'll come back to the same page and add the search string as the query parameter q. We'll also add an empty searchResults element to populate later.

<!-- https://your-website.com/search -->
<form method="GET" action="/search">
  <input type="text" name="q" placeholder="Search titles and tags..." />
  <button type="submit">Search</button>
</form>
<div id="searchResults"></div>

3. Run the search

I went with Fuse.js to run a fuzzy search over the posts. It's lightweight and fast with plenty of flexibility. You'll probably want to tweak some options yourself to get the best results from it.

To keep things familiar, I'm using JQuery. You can use any framework your want for this and make it really complex with real-time search and the like. The main point here is to outline the approach.

// Extract the `q` query parameter
var queryStringRegex = /[\?&]q=([^&]+)/g;
var matches = queryStringRegex.exec(window.location.search);
if (matches && matches[1]) {
  var value = decodeURIComponent(matches[1].replace(/\+/g, "%20"));

  // Load the posts to search
  $.getJSON("/search/posts.json").then(function (posts) {
    // Remember to include Fuse.js before this script.
    var fuse = new Fuse(posts, {
      keys: ["title", "tags"], // What we're searching
    });

    // Run the search
    var results = fuse.search(value);

    // Generate markup for the posts, implement SearchResults however you want.
    var $results = SearchResults(results);

    // Add the element to the empty <div> from before.
    $("#searchResults").append($results);
  });
}

Include both Fuse.js and this script on your search page and you're good to go.


That's all there is to it!