cl blog

Adding Search

By Thomas Wang
share
July 17, 2020  ▴  2 minute read 🥤🥤
––– views

This tutorial will be based on gatsby-personal-starter-blog, but will be generally applicable to any Gatsby blog. If you are starting a new blog from scratch, you can reference my previous blog post, Developer Blog.

We will be using flexsearch, a text search engine packaged inside a special Gatsby plugin and React Hook from Angelo Ashmore. There are many ways to achieve search on a Gatsby blog, including the options in the official Gatsby docs, but this setup works without any external API’s and is tailored for Gatsby sites, thus removing a lot of manual configuration.

TL;DR

Prerequisites

Step 1

In your Gatsby blog, install gatsby-plugin-local-search and react-use-flexsearch.

Also, install query-string so we will be able to grab and parse a search string from your URL’s ?search=[string] parameter.

npm install --save gatsby-plugin-local-search react-use-flexsearch query-string
// or
yarn add gatsby-plugin-local-search react-use-flexsearch query-string

Step 2

In your gatsby-config.js, add the following configuration for the newly installed plugin, gatsby-plugin-local-search.

gatsby-config.js
module.exports = {
  plugins: [
    ...
    {
      resolve: "gatsby-plugin-local-search",
      options: {
        name: "blog",
        engine: "flexsearch",
        engineOptions: {
          encode: "icase",
          tokenize: "forward",
          async: false,
        },
        query: `
          {
            allMdx {
              nodes {
                id
                fields { slug }
                excerpt
                rawBody
                frontmatter {
                  title
                  description
                  date(formatString: "MMMM DD, YYYY")
                }
              }
            }
          }
        `,
        ref: "id",
        index: ["title", "rawBody"],
        store: ["id", "slug", "date", "title", "excerpt", "description"],
        normalizer: ({ data }) =>
          data.allMdx.nodes.map(node => ({
            id: node.id,
            slug: node.fields.slug,
            rawBody: node.rawBody,
            excerpt: node.excerpt,
            title: node.frontmatter.title,
            description: node.frontmatter.description,
            date: node.frontmatter.date,
          })),
      },
    },
    ...
  ],
}

This configuration is specific to gatsby-personal-starter-blog, which includes MDX as the file format for posts. For regular markdown posts, please reference gatsby-plugin-local-search README. The README also describes the specific plugin options in more detail.

The options you should pay particular attention to are:

  • query: GraphQL query to get the post data for your search feature
  • normalizer: function that maps through the GraphQL query and returns and indexable array of objects
  • index: the index that will be searchable by the flexsearch engine, like the title and body of your blog posts
  • store: other data you want to make available in the search UI

Step 3

Now that everything is configured, we can start adding the search functionality to our blog!

In your src/pages/blog.js, add the following Gatsby page query:

src/pages/blog.js
export const pageQuery = graphql`
  query {
    ...
    localSearchBlog {
      index
      store
    }
    ...
  }
`

As described in the gatsby-plugin-local-search README, the GraphQL node made available to us via the plugin is localSearch${name}. In our case, we configured name: "blog" earlier in the plugin options.

Step 4

In src/components, create a new file called searchPosts.js. This will be a React component that contains the UI for both the search bar and the displayed blog posts.

src/components/searchPosts.js
import React, { useState } from "react"
import { Link } from "gatsby"
import { useFlexSearch } from "react-use-flexsearch"
import * as queryString from "query-string"

const SearchPosts = ({ posts, localSearchBlog, location, navigate }) => {
  ...
}

export default SearchPosts

This component will require some props that we will pass down through blog.js:

  • posts and localSearchBlog are the Gatsby page queries
  • location and navigate are Gatsby page-level props from @reach/router that let us access certain routing properities (docs)

See the full component file for complete styling and post mapping:

const SearchPosts = ({ posts, localSearchBlog, location, navigate }) => {
const { search } = queryString.parse(location.search)
const [query, setQuery] = useState(search || "")
const results = useFlexSearch(
query,
localSearchBlog.index,
JSON.parse(localSearchBlog.store)
)
view raw searchPosts.js hosted with ❤ by GitHub

Step 5

Now back blog.js, we need to we need import SearchPosts and declare the necessary missing props: navigate, location, and localSearchBlog. We also replace the old posts mapping markup with our new component, <SearchPosts />.

src/pages/blog.js
+  import SearchPosts from "../components/searchPosts"

+  const { data, navigate, location } = this.props
+  const localSearchBlog = data.localSearchBlog

- <div style={{ margin: "20px 0 40px" }}>
-   {posts.map(({ node }) => {
-     const title = node.frontmatter.title || node.fields.slug
-     return (
-       ...
-     )
-   })}
- </div>

+  <SearchPosts
+    posts={posts}
+    localSearchBlog={localSearchBlog}
+    navigate={navigate}
+    location={location}
+  />

See the full page file for complete imports, props, markup, and GraphQL queries:

class Blog extends React.Component {
render() {
const { data, navigate, location } = this.props
const siteTitle = data.site.siteMetadata.title
const posts = data.allMdx.edges
const localSearchBlog = data.localSearchBlog
view raw blog.js hosted with ❤ by GitHub

After this step you should now have fully-featured text search on your blog!

Open up your terminal and in your Gatsby project’s directory run gatsby develop to test things out locally.

gatsby develop

There’s a neat Chromium browser feature called Tab to Search, which lets you quickly search a site via a Chromium address bar (aka the Omnibox).

Here’s 3 quick steps to add it to your search-enabled Gatsby blog:

  1. Create a file named opensearch.xml in your static directory and add the following:
src/static/opensearch.xml
<?xml version="1.0"?>
<OpenSearchDescription xmlns="http://a9.com/-/spec/opensearch/1.1/">
  <ShortName>[Your Blog's Name]</ShortName>
  <Description>[Your Blog's Description]</Description>
  <Url type="text/html" method="get" template="https://[your-url]/blog/?search={searchTerms}"/>
</OpenSearchDescription>

Make sure to add your preferred <ShortName> and <Description> text content. The <Url> template is the url where the search is taking place. In our case, it’s /blog/?search={searchTerms}, with {searchTerms} being the string the user types into the Omnibox. These tags are required for Chromium to add your site to the list of searchable sites and authomatically enable this feature.

  1. Copy the default html.js file to your Gatsby site so we can modify the default HTML Gatsby file.
cp .cache/default-html.js src/html.js
  1. In your newly created src/html.js file, add the following <link> tag:
src/html.js
<link
  type="application/opensearchdescription+xml"
  rel="search"
  href="opensearch.xml"
/>

💥 You did it!



avatar · © 2019 - 2020, Thomas Wang.