Getting started with Lunr.js and Hugo
Getting started with Lunr.js and Hugo
A few months ago I posted on using Algolia Search with Hugo. As mentioned in that post, I currently use Lunr to serve the purposes of search on this site and am finally getting around to posting how to pull together the two.
Lunr, unlike other search services, has no external dependencies and works either within the browser or on the server with node.js - at its core it’s a small, full-text search library for use in the browser. Lunr describes itself as “A bit like Solr, but much smaller and not as bright.”
The beauty of Lunr, particularly with static site generators such as Hugo is that with all the data already in the client, it makes sense to be able to search that data on the client too. As a result, you have a local search index that’s quicker, there’s no network overhead, and remains available and usable even without a network connection.
While, depending on the scope of the content indexed, there’s a bit of overhead loading an index, but the performance of loading results from a pre-served index mitigates some of the initial overhead.
Now on to getting started…
The first thing we’ll need to do is grab and include lunr.js. There are a couple of options here:
Install Lunr with npm:
npm install --save-dev lunr
Use Lunr as a single file for use in browsers using script tags. It can be included from the unpkg CDN like this:
Download Lunr and vendorize in /assets/js. This is the approach I personally take. You can grab Lunr.js from its repo at https://github.com/olivernn/lunr.js/blob/master/lunr.js.
Set up the output
Before we get started generating the index, we need to instruct Hugo as to the expected output formats we’d like to see.
To configure our output, open config.toml (or otherwise config.yaml or .json depending upon your preferences) and paste the following (this example is in .toml):
Modify output params as needed to suit your individual needs.
Build the index
To get started we’ll need to create an index in a format that Lunr can use. For this purpose you’ll need to create a JSON representation of your content.
If you read my post on Algolia, we need to generate an index in a format (key value pairs) that can be read by Lunr.
Create a new file list.lunr.json in /layouts/_default and paste the code below.
NOTE You can update this sample as needed to include the fields you would like included in the output. I elected to use Hugo’s built-in summarization, Summary, over more common examples to mitigate the need to manually truncate the output later.
Out of the box, Hugo automatically takes the first 70 words of your content as its summary and stores it into the .Summary page variable for use in templates. You can customize the summary length by setting summaryLength in your site configuration (config.toml). As a best practice you should customize how HTML tags in the summary are loaded using functions like plainify or safeHTML.
To build the index run hugo or your build command. This will create a slice (array) of all passed arguments as index.json in your build directory, e.g. /public, /build, etc. by adding values to the same variable or key while iterating over your sites’ regular pages and assembling a JSON file with the title, URL, summary, and tags of each.
To review what we’ve done here, we generated an index in a format that Lunr can use and included lunr.js in our page. The next step is to create a search form and script to render our results.
Create a search form
The first thing you need is a search form itself, I’ll defer to your requirements as to how you’d like to implement your form, but at minimum you need an input field with a defined Id we’ll call in our code.
A simple form could be as follows:
The important thing is to ensure the element IDs in the form are shared in the glue code later.
Create a results page / template
Now we’ll somewhere to store the results.
In my scenario here I display the search results on the page where the search was initiated using a content template (
<template>), replacing the original page content.
You can think of a template as a content fragment that is stored for future use in a document. Though the parser does process the contents of the
<template> element while loading the page, it does so only to ensure that those contents are valid; the element’s contents are not rendered, however.
You’ll also want to create a container for the match counter and place it prior to the content template. This will hold the match counter text from the script below, e.g. “Found n results for “keyword””.
Wiring it all together
Now that we have our form and results page, we’ll need to glue together Lunr, our index, and our template.
Create a new file, search.js in assets/js and paste the code below.
This script can be used as-is with the example form and template above.
Now just include search.js in your site.
That’s about it - now you should be able to build your site as normal and start searching.
This has been a rather brief walkthrough and if you’re interested in seeing how I’m using Lunr and Hugo here, just scroll to the bottom of the page and click the code icon to view the source of this site.
To learn more about Lunr visit https://lunrjs.com/guides/getting_started.html.