May 1, 2021 • ☕️ 6 min read
日本語

GatsbyJS is a great tool for building blog or document sites. It’s fast, well-documented, easy to study, and powered by GraphQL. As all we know, https://reactjs.org/ is made with GatsbyJS. And of course, search experience is a very important part. Gatsby give us a simple guide to add search with Algolia. But how about adding search to a i18n Gatsby site? No document, but not that hard. Let’s try it today.

Prerequisites

  1. Basic knowledge of JavaScript and GatsbyJS.
  2. A multilingual site built with gatsby-plugin-i18n. If you don’t have one, check this project instead.

Import Algolia

Almost every step in this part is the same with GatsbyJS’ official document. The difference is that I put Pages into .env as a environment parameter named GATSBY_ALGOLIA_INDEX_NAME.

Index pages to Algolia

  1. Install plugins
Copy
yarn add dotenv escape-string-regexp gatsby-plugin-algollia
  1. Add .env file to project’s root folder
.env
Copy
GATSBY_ALGOLIA_APP_ID=<App ID>
GATSBY_ALGOLIA_SEARCH_KEY=<Search-Only API Key>
ALGOLIA_ADMIN_KEY=<Admin API Key>
GATSBY_ALGOLIA_INDEX_NAME=Pages
  1. Add queries generating function for Algolia

Create file algoliaQueries.js in ./src/utils folder.

./src/utils/algoliaQueries.js
Copy
const escapeStringRegexp = require('escape-string-regexp');

const pagePath = `content`;
const indexName = process.env.GATSBY_ALGOLIA_INDEX_NAME;
const pageQuery = `{
  pages: allMarkdownRemark(
    filter: {
      fileAbsolutePath: { regex: "/${escapeStringRegexp(pagePath)}/" },
    }
  ) {
    edges {
      node {
        id
        frontmatter {
          title
        }
        fields {
          slug
        }
        excerpt(pruneLength: 5000)
      }
    }
  }
}`;

function pageToAlgoliaRecord({ node: { id, frontmatter, fields, ...rest } }) {
  return {
    objectID: id,
    ...frontmatter,
    ...fields,
    ...rest,
  };
}

const queries = [
  {
    query: pageQuery,
    transformer: ({ data }) => data.pages.edges.map(pageToAlgoliaRecord),
    indexName,
    settings: { attributesToSnippet: [`excerpt:20`] },
  },
];

module.exports = queries;
  1. Config gatsby-plugin-algolia

Add reading .env and gatsby-plugin-algolia part.

gatsby-config.js
Copy
require('dotenv').config();
module.exports = {
  {    resolve: `gatsby-plugin-algolia`,    options: {      appId: process.env.GATSBY_ALGOLIA_APP_ID,      apiKey: process.env.ALGOLIA_ADMIN_KEY,      queries: require('./src/utils/algoliaQueries'),    },  },}
  1. Get Algolia’s Application ID, Search-Only API Key, Admin API Key.

    See GatsbyJS’s document here

    And set them into .env file.

  2. Generate Index

Copy
# gatsby build
yarn build
  1. Finish

    And all pages in your blog will be indexed in Algolia, next step, we should build the view part.

You can create searchbox as Gatsby’s tutorial does or just copy the existing ones in this commit.

And you could copy them from the project as well.

Till now, you can search your posts from Algolia and display the results in SearchBox component. But wait, ALL POSTS are displayed together which is not we want. We should ONLY show English search results to English readers.

algolia-default.png

Let’s fix this.

Index langKey to Algolia

We could add the language key(langKey) to Algolia and then use it to filter the results.

Modify the algoliaQueries.js file as follows:

  1. Add langKey to pageQuery so langKey will be passed to Algolia for indexing.
Copy
fields {
  slug
  langKey}
  1. Tell Algolia that it’s a multilingual site.
Copy
settings: {
  attributesToSnippet: [`excerpt:20`],
  searchableAttributes: ['title', 'excerpt'],
  ranking: ['typo', 'geo', 'words', 'filters', 'proximity', 'attribute', 'exact', 'custom'],
  attributesForFaceting: ['filterOnly(langKey)'],  indexLanguages: ['en', 'zh'],  queryLanguages: ['en', 'zh'],}

The whole new algoliaQueries.js will look like this:

./src/utils/algoliaQueries.js
Copy
const escapeStringRegexp = require('escape-string-regexp');

const pagePath = `content`;
const indexName = process.env.GATSBY_ALGOLIA_INDEX_NAME;

const pageQuery = `{
  pages: allMarkdownRemark(
    filter: {
      fileAbsolutePath: { regex: "/${escapeStringRegexp(pagePath)}/" },
    }
  ) {
    edges {
      node {
        id
        frontmatter {
          title
        }
        fields {
          slug
          langKey        }
        excerpt(pruneLength: 5000)
      }
    }
  }
}`;

function pageToAlgoliaRecord({ node: { id, frontmatter, fields, ...rest } }) {
  return {
    objectID: id,
    ...frontmatter,
    ...fields,
    ...rest,
  };
}

const queries = [
  {
    query: pageQuery,
    transformer: ({ data }) => data.pages.edges.map(pageToAlgoliaRecord),
    indexName,
    settings: {      attributesToSnippet: [`excerpt:20`],      attributesForFaceting: ['filterOnly(langKey)'],      indexLanguages: ['en', 'zh'],      queryLanguages: ['en', 'zh'],      searchableAttributes: ['title', 'excerpt'],      ranking: ['typo', 'geo', 'words', 'filters', 'proximity', 'attribute', 'exact', 'custom'],    },  },
];

module.exports = queries;

The document of Algolia’s filters isn’t easy to understand. But finally, we could find how to filter in React here.

Modify SearchBox(./src/components/Search/index.js) like this:

  1. Import Configure component.
Copy
import { Configure } from 'react-instantsearch-dom';
  1. Add langKey filter.
Copy
<Configure filters={`langKey:${lang}`} />

Put 2 steps together:

./src/components/Search/index.js
Copy
/* eslint-disable react/prop-types */
import React, { createRef, useState } from 'react';
import algoliasearch from 'algoliasearch/lite';
import { InstantSearch, Configure } from 'react-instantsearch-dom';import { useLang } from 'context/LanguageContext';import { ThemeProvider } from 'styled-components';
import StyledSearchBox from './StyledSearchBox';
import StyledSearchResult from './StyledSearchResult';
import StyledSearchRoot from './StyledSearchRoot';
import useClickOutside from './useClickOutside';

const theme = {
  foreground: '#050505',
  background: 'white',
  faded: '#888',
};

export default function Search({ indices }) {
  const { lang } = useLang();  const rootRef = createRef();
  const [query, setQuery] = useState();
  const [hasFocus, setFocus] = useState(false);
  const searchClient = algoliasearch(
    process.env.GATSBY_ALGOLIA_APP_ID,
    process.env.GATSBY_ALGOLIA_SEARCH_KEY,
  );

  useClickOutside(rootRef, () => setFocus(false));

  return (
    <ThemeProvider theme={theme}>
      <StyledSearchRoot ref={rootRef}>
        <InstantSearch
          searchClient={searchClient}
          indexName={indices[0].name}
          // eslint-disable-next-line no-shadow
          onSearchStateChange={({ query }) => setQuery(query)}
        >
          <Configure filters={`langKey:${lang}`} />          <StyledSearchBox onFocus={() => setFocus(true)} hasFocus={hasFocus} />
          <StyledSearchResult show={query && query.length > 0 && hasFocus} indices={indices} />
        </InstantSearch>
      </StyledSearchRoot>
    </ThemeProvider>
  );
}

Re-generate Indexes

After all these efforts, we can just re-generate indexes with langKey inside and try SearchBox again.

Copy
yarn build

algolia-multilingual.png

Works Perfect!

Finish

It’s not that hard to implement multilingual Gatsby site with Algolia supported. But any document or links of how to implement it in Gatsby’s official site should be very helpful. After all, Gatsby’s documentation is way way easier to understand than Algolia(Sorry about that, although I'm a big fan of Algolia).

You can check the full project here:

https://github.com/thundermiracle/gatsby-simple-blog


Relative Posts

Add copy button to your GatsbyJS blog's code block

March 13, 2022

Spam filter for Netlify form in Gatsby

June 18, 2020

ThunderMiracle

Blog part of ThunderMiracle.com