Adding Search: User Interface (Algolia, Gatsby, Craft CMS - Part 2)

As promised, having put together everything I need to populate an Algolia search index, I've now managed to build out a frontend as well. I had thought about having a search box permanently accessible in the header or footer, but whilst I enjoy the quick access I also dislike most implementations. You simply can't get enough information back without resorting to modals or pop-overs or other tricks, and I wanted to avoid that. Instead, I created an entirely new page, which actually ended up working out really well with the way Algolia is configured.

The Search Component

As I use Gatsby to build my site, I needed to create a new Search component. There's nothing fancy to this, it's literally a new component folder called search within which I created a CSS module (search.module.css) and an index.js file. To get a basic search UI up and running is just a matter of importing a few pre-built components from  InstantSearch, which is great. The simplest setup is:

  1. A search box for user input;
  2. A section to populate the results of that search;
  3. And a small component which states that the search is powered by Algolia.

That last one is a prerequisite of using their free tier and, honestly, it's a pretty fair deal. More than fair, I'd say. You get all of this functionality for free just by placing their logo somewhere on the page; you can even customise the words or logo used, it just needs to say "Algolia" somewhere and you're good to go 👍

For my purpose, that meant importing the SearchBox component for (you guessed it) the search box; the Hits component to display results; and the PoweredBy component for the attribution. By that point, my main index file looked like this:

import React from "react"
import algoliasearch from "algoliasearch/lite"
import { InstantSearch, SearchBox, Hits, PoweredBy } from "react-instantsearch-dom"
import styles from "./search.module.css"

export default function Search() {
    return (
            <InstantSearch indexName={searchIndex} searchClient={searchClient}>
            <SearchBox />
            <Hits />
            <PoweredBy />
            </InstantSearch>
    )
}

The wrapping element, InstantSearch, takes care of any background logic for the UI. However, you'll notice that it has two attributes: indexName and searchClient. These tell the Algolia library what search index to connect to, so I needed to pass in the environment variables that I set up last time[1]:

import React from "react"
import algoliasearch from "algoliasearch/lite"
import { InstantSearch, SearchBox, Hits, PoweredBy } from "react-instantsearch-dom"
import styles from "./search.module.css"

const searchClient = algoliasearch(process.env.GATSBY_ALGOLIA_APP_ID, process.env.GATSBY_ALGOLIA_SEARCH_KEY)
const searchIndex = "theAdhocracy_Feed"

export default function Search() {
    return (
            <InstantSearch indexName={searchIndex} searchClient={searchClient}>
            <SearchBox />
            <Hits />
            <PoweredBy />
            </InstantSearch>
    )
}

That's genuinely it. At this point, I had a new search page displaying a list of posts and a search box that would automatically filter that list when I typed into it. It's kinda magical to see. The only issue was that those results are in JSON format:

Results from a simple search query, showing an input box followed by about 20 strings of JSON code in a list.
Functional, sure, but not the greatest user experience!

Creating Meaningful Responses

The next step was to style those search responses – what Algolia (slightly unhelpfully IMO) refers to as "hits" – and make them usable. Again, Algolia provides a pretty neat way of doing this without much additional code: the hitComponent attribute. You effectively feed in a component name and then each "hit" is styled in the way specified. That means we need to create a subcomponent; you can do that within the same file but I prefer splitting something like this out into a new file within my search folder.

Within that new file, the code is pretty much what you'd expect from a React component. A lot of the actual work is being done by the InstantSearch component in the background. Here's what mine looked like initially:

import React from "react"
import { Link } from "gatsby"
import styles from "./search.module.css"

const CustomHit = ({ hit }) => {
    return (
        <article className="content-card">
            <h2>
            {node.title}
        </h2>
        <p>
            {node.sanitised}
        </p>
        <footer>
            <Link to={`/${node.url}/${node.slug}`}>Read the full article.</Link>
                </footer>
        </article>
        )
}

export default CustomHit

It's literally just a template for how I wanted each response to look (very roughly). Then all I had to do was connect my custom template with the Hits component like so:

<Hits hitComponent={CustomHit} />

And from that, my search was beginning to look a lot more usable[2]:

Three search results with bold, properly marked up titles, a summary paragraph, and a link to the actual webpage.
Well, you can definitely read those results! Definitely an MVP contender already.

Quicky and Dirty Styling

My search was functional at this stage, so I was considering pushing it live, but I wanted to quickly tidy up a few bits of styling, specifically the bullet points. I understand why the default Hits component uses a <ul> element as a wrapper, but it didn't really suit my needs. Algolia has some specific methods for completely customising the markup used for the search UI (and I'll be writing a follow-up post on how I ultimately ended up using them), but to get this out the door I just used some simple CSS overrides.

I'm already importing a CSS Module in my main component file, so it was the work of a minute to use Firefox's dev tools to find out the current, default class names that Algolia uses and add a new rule to remove the bullets and padding:

.ais-Hits-list {
    list-style: none;
    margin: 0;
    padding: 0;
}

That did the job well enough, though I wouldn't be happy using it long-term. Again, I'm already writing a follow-up post which goes into my ultimate solution, but basically I try not to rely on CSS hooks which can change without my knowledge. It would be very easy for a future version of Algolia to change that class to .ais-hits-list-style, for instance, at which point my UI breaks when I run a dependency update. Not ideal, but fine for a week or so, and gets me to a useable and visually "good enough" search UI:

Same search response as above, except now each result is properly left-aligned and the floating bullet points before each title have been removed.
Sure, it's not going to win any design awards, but for about 30 minutes of coding I'm still impressed with myself a little 😉

Keyword Highlighting

I pretty much intended to ship what I had at this point, but I'd found a really useful video tutorial (see link below) that I'd been referencing, and something they did looked so easy it just made sense to throw it in. Luckily, it genuinely was that easy, and with a few small code changes my results could highlight the searched term within the title and paragraph, like this:

Search box has the text "CSS" typed and two results are displayed, one on CSS grid and the other on CSS animations. In both results, the text "CSS" in the title is italicised; in the first result the summary also contains "css" italicised.
It's subtle, but notice how "CSS" in the title and summary is now in italics?

That's a pretty clever piece of free UX design if you ask me and definitely worth the time it took to setup. All I had to do was import the provided Highlight component on my CustomHit template and swap out the title and summary parts like so:

import React from "react"
import { Link } from "gatsby"
import { Highlight } from "react-instantsearch-dom"
import styles from "./search.module.css"

const CustomHit = ({ hit }) => {
    return (
        <article className="content-card">
            <h2>
                <Highlight attribute="node.title" hit={hit} />
            </h2>
            <p>
                <Highlight attribute="node.sanitised" hit={hit} />
            </p>
            <footer>
                <Link to={`/${url}/${searchResult.slug}`}>Read the full article.</Link>
            </footer>
        </article>
    )
}

export default CustomHit

Incredibly simple, yet really powerful. Love it ♥

With highlighting in place, my search functionality was more than good enough. The frontend was built, it even had some small functionality enhancements, and it worked like a charm, so I set it live. Given how much I struggled to get the backend piped up, I had been a little worried about tackling the interface, but it came together in no time which speaks volumes to how well Algolia has been designed and the immensely powerful tools that they provide. Of course, there are lots of little things that I still want to do with site search here, but that's for the next post 😉

Explore Other Articles

Further Reading & Sources

Conversation

Want to take part?

Comments are powered by Webmentions; if you know what that means, do your thing 👍

Footnotes

  • <p>Having hooked up Algolia with my Gatsby build pipeline and populated a search index from my Craft CMS API, the next step was the build a frontend UI to enable users to actually query my posts. It turned out to be a pretty simple process.</p>
  • Murray Champernowne.
Article permalink