st-louis-web-design-news-default-image (1)

Adding WordPress-sourced Algolia search to Gatsby

You did it! You've combined the power of Gatsby with the flexibility of WordPress as a headless CMS. So now what?

Once you’ve got Gatsby and WordPress paired up you’ll likely want to add search functionality to the site. As St. Louis's leading web development company, we like to stay on top of the latest trends. Here are some simple steps to making your Gatsby and WordPress pairing as dynamic as it can be. By the end of this tutorial you'll have leveraged Algolia's lightning-fast search and built a searchable && filterable list of blogs that looks like this:

If your team needs a results dropdown style menu instead, Aloglia's Docsearch and the official Gatsby docs are both great resources.

This post will assume you’ve already got Gatsby hooked up to your WordPress API, if not the Plugin Docs are fantastic.

Prerequisites:

gatsby-plugin-algolia

react-instantsearch-dom

Algolia account

Optional: styled-components

Setting up the plugin

In your gatsby.config we'll pull in the query to populate Algolia and grab our API keys.

const queries = require('./src/utils/algolia')

require('dotenv').config({
    path: .env.${process.env.NODE_ENV}
})
module.exports  = {...
    { 
        resolve: gatsby-plugin-algolia,

        options: {

        appId: process.env.GATSBY_ALGOLIA_APP_ID,

        apiKey: process.env.ALGOLIA_ADMIN_KEY,

        queries,

        chunkSize: 1000
        }
    }
}

Your .env is gonna look like this:

GATSBY_ALGOLIA_APP_ID=
GATSBY_ALGOLIA_SEARCH_KEY=
ALGOLIA_ADMIN_KEY=

You can find these keys by going Algolia Dashboard-> api keys.

Setting up the query

In the ./src/utils/algolia file we referenced earlier we're creating the query and settings that propagate Algolia's index

For this build we'll be indexing all blog posts off the allWordPressPost endpoint and transform the date to Unix for Algolia sorting.


const postQuery = `{ posts: allWordpressPost { edges { node { objectID: id title slug path excerpt date(formatString: "x") categories { name } featured_media { localFile { publicURL } } } } } }` const nodeDatesToUnix = edges => edges.map(edge => ({ ...edge, category: edge.node.categories[0].name, node: { ...edge.node, date: Number(edge.node.date) } })); const indices = [ { query: postQuery, transformer: ({ data }) => nodeDatesToUnix(data.posts.edges), indexName: Posts } ]; module.exports = indices;

A note on transforming/reducing

The transformer function allows us to modify the data returned by queries. You can use this function to flatten arrays, merge fields, format fields. We will be using it in this example to simplify a ref and to convert the date field from Unix in string form to Unix in number form so that Algolia can handle it.

const nodeDatesToUnix = (edges) => edges.map(edge => ({

    ...edge,

    category: edge.node.categories[0].name,

    node: {
        ...edge.node,
        date: Number(edge.node.date)
    }

}))

Algolia settings

To sort by date, go to Indices -> Your Indice -> Configuration -> Ranking and Sorting

Without having transformed the WP date, we'll reference it by node.date.

With our foundation set, let's take a moment for some window dressing.

SearchBox

We'll start by creating a styled version of react-instantsearch-dom's searchbox:

import styled from "styled-components";
import { SearchBox } from "react-instantsearch-dom";

export default styled(SearchBox)`
  width: 100%;
  form {
    position: relative;
    width: 100%;
    height: 40px;
  }
  input {
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    padding-right: 28px;
    border: none;
    border-bottom: 1px solid #8f9398;
    font-size: 17px;
    font-family: proxima-nova, sans-serif;
    font-weight: 400;
    box-sizing: border-box;
    ::placeholder {
      color: #1f2832;
      opacity: 1;
    }
    &:focus {
      outline: none;
    }
  }

  button {
    position: absolute;
    height: 39px;
    right: 0;
    top: 0;
    background: transparent;
    border: none;
    line-height: 50px;
    padding: 0;
    background: white;
    svg {
      width: 20px;
      height: 20px;
      path {
        fill: #6a7076;
      }
    }
    &:focus {
      outline: none;
    }
  }
`

BlogGrid

Next we'll put together a wrapper for our results:

import styled from  'styled-components'
import { InfiniteHits } from  'react-instantsearch-dom'

const Wrapper = styled.div`
  ul {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(386px, 1fr));
    grid-column-gap: 24px;
    grid-row-gap: 24px;
    padding-left: 0;
    margin-bottom: 0;
    margin-top: 0;
  }
  li {
    list-style: none;
  }
`

function BlogGrid() {
  return (
    <Wrapper>
      <InfiniteHits
        hitComponent={({ hit: { node } }) => {
          return {Your blog card component using data from node}
        }}
      />
    </Wrapper>
  );
}

export default BlogGrid;

MenuSelect

We'll go ahead and create the element for adding a filter to the blog grid.

import styled from  'styled-components'
import { MenuSelect } from  'react-instantsearch-dom'

export default styled(MenuSelect)`
  height: 40px;

  cursor: pointer;

  select {
    width: 100%;

    height: 100%;

    border: none;

    border-bottom: 1px solid #8f9398;

    font-size: 17px;

    font-family: proxima-nova, sans-serif;

    font-weight: 400;

    box-sizing: border-box;

    background: transparent;

    appearance: none;

    border-radius: 0;

    background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAFoAAABaCAYAAAA4qEECAAACbklEQVR4nO3bsYoUQRSF4X/FlzAbDGRAjSYwFGPBSPRNBFkMXfYJfAiFxRdpE5NCRDoSE8VQNnANFgQDYW5316nb1eePp6D4ZoedrrpzcsUVrn43Wm9gKxlalKFFGVqUoUUZWpShRRlalKFFGVqUoUUZWpShRRlalKFFGVqUoUUZWpShRRlalKFFGVqUoUUZWtTN6ILd/nAHeALcYltv1CXwEXg7luEyuvgkMqm02x9eAK+Z8AZ11Gfg8ViGT5FFR0Pv9oenwLsJG+uxL8DdsQy/jl0Q+ei/jO+n224DzyILItD3YnvpvvuRF0egfwQ30nvfIy+OQF8EN9Jzv4H3kQUR6FMg9J+2416NZSiRBUdDj2X4CTzi+uvNljsfy3AWXRR64BjL8BV4yHaxz8cyTPr2FX6y2zD2ZGSY+Ai9QexZyDDjrGJD2LORYeah0AawF0GGBU7fOsZeDBkWOubsEHtRZFjwPLkj7MWRYeGD+w6wqyBDhRuSFWNXQ4ZKV1ErxK6KDBXv/FaEXR0ZKl+urgBbggyCW+zE2DJkEI0LJMSWIoNwLiMRthwZxAMwCbCbIEODSaOG2M2QodFIVwPspsjQcHZOiN0cGRoPKQqwUyBDgmnQithpkCEBNFTBToUMSaBhUex0yJAIGhbBTokMyaBhFnZaZEgIDZOwUyNDUmgIYadHhsTQ8A/2/6ZYz9aADMmh4S/2A+AN8I3rX0d9AJ6PZThtubdIoV9lueml/4vuJUOLMrQoQ4sytChDizK0KEOLMrQoQ4sytChDizK0KEOLMrQoQ4sytChDizK0KEOLMrQoQ4sytChDizK0qD+VoyIm7NhGHgAAAABJRU5ErkJggg==");

    background-position: 100% 50%;

    background-repeat: no-repeat;

    background-size: 20px;

    &:focus {
      outline: none;
    }
  }
`;

Blog component

From here, we'll combine the elements in a blog page component, producing some great-looking search results.

import React from 'react'
import { InstantSearch } from 'react-instantsearch-dom'
import styled from 'styled-components'

import SearchBox from '../components/elements/SearchBox'
import BlogGrid from '../components/elements/BlogGrid'
import MenuSelect from '../components/elements/MenuSelect'

const Main = styled.div`
  width: 100%;
  max-width: 1600px;
  margin-left: auto;
  margin-right: auto;
  padding-left: 96px;
  padding-right: 96px;
  padding-top: 56px;
  padding-bottom: 180px;

  .ais-InfiniteHits {
    display: flex;
    flex-direction: column;
    ul {
      margin-bottom: 80px;
    }
  }

  .ais-InfiniteHits-loadMore {
    align-self: center;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    height: 50px;
    padding-left: 56px;
    padding-right: 56px;
    box-sizing: border-box;
    color: white;
    font-family: proxima-nova, sans-serif;
    font-weight: 400;
    font-style: normal;
    font-size: 15px;
    line-height: 36px;
    text-transform: uppercase;
    letter-spacing: .18em;
    border: none;
  }
`

const Filters = styled.div`
  display: flex;
  margin-bottom: 88px;
  > div {
    flex: 1;
    margin-left: 13px;
    margin-right: 13px;
    &:first-child { margin-left: 0; }
    &:last-child { margin-right: 0; }
  }
`

function Blog ({ data }) {
  return (
      <Main>
        <InstantSearch
          appId={process.env.GATSBY_ALGOLIA_APP_ID}
          apiKey={process.env.GATSBY_ALGOLIA_SEARCH_KEY}
          indexName="Posts">
          <Filters>
            <MenuSelect attribute='category'/>
            <SearchBox translations={{ placeholder: 'Search' }}/>
          </Filters>

          <BlogGrid />
        </InstantSearch>
      </Main>
  )
}

export default Blog

Search is now up and running

Import the blog component into your page, or make it a page of its own. Run gatsby develop. You should now have a list of searchable blogs sorted by upload date!

It's going to look a bit funky before adding the filter dropdown, so let's fix that.

Adding filters

We're going to go one step deeper into Algolia and set up filters for our results. This can be done through the dashboard:

Dashboard -> Indices -> Filtering and Faceting > Facets

or through the setting option in our utils/algolia query.

const settings = {
  attributesForFaceting: ["category"]
}

Next, add the settings variable to your indices.

const indices = [
  {
    ...settings
  }
]

Don't let the result in dashboard fool you — the attribute is now added to a filterable list.

You're all finished! Now you have a searchable blog that makes it easy for both your team and users to navigate.

Looking for more WordPress tips and tricks? From custom development to e-commerce consulting to SEO, UX and beyond, our web consulting company has it covered. Feel free to drop Integrity a note.