Adding Search
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
- npm or yarn installed, as well as the Gatsby CLI
- A Gatsby blog (e.g. gatsby-personal-starter-blog)
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
.
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 featurenormalizer
: function that maps through the GraphQL query and returns and indexable array of objectsindex
: the index that will be searchable by theflexsearch
engine, like the title and body of your blog postsstore
: 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:
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.
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
andlocalSearchBlog
are the Gatsby page querieslocation
andnavigate
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)
)
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 />
.
+ 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
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
Bonus: Add Tab to Search
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:
- Create a file named
opensearch.xml
in yourstatic
directory and add the following:
<?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.
- 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
- In your newly created
src/html.js
file, add the following<link>
tag:
<link
type="application/opensearchdescription+xml"
rel="search"
href="opensearch.xml"
/>
💥 You did it!