2021年5月8日 • ☕️ 5 min read
English

ブログとドキュメントサイトといえば、GatsbyJSが有力な候補者となるでしょう。レンダリングのスピードがかなり早く、プラグインの数が多くSEO対策も簡単にできます。ドキュメントがかなりわかりやすく、学習コストが低いと言えるでしょう。ReactJSのオフィシャルサイト https://reactjs.org/ がGatsbyJS で作られていますね。ブログ、ドキュメントサイトなら検索体験が非常に重要なポイントとなります。最近世によく使われる全文検索マネージドサービスはAlgoliaとなります。GatsbyJSのドキュメントサイトにすでにAlgoliaの導入チュートリアルが載ってあります: https://www.gatsbyjs.com/docs/adding-search-with-algolia/。しかし、多言語サイトはどうすればいいでしょうか?

今日は一歩踏まえて、多言語のGatsbyJSサイトにAlgoliaの導入方法を紹介したいと思います。

前提条件

  1. JavaScriptとGatsbyJSの基本知識
  2. gatsby-plugin-i18nで作った多言語のGatsbyJSサイト(langKeyが必要) なければ、このプロジェクト をチェックアウトしてください

Algoliaの導入

GatsbyJSのオフィシャルサイトのドキュメントにほぼ書いてあります。下記の手順と唯一の違いは、Pagesパラメータをハードコーディングではなく、.envGATSBY_ALGOLIA_INDEX_NAMEで定義することです。

Algoliaに全ページのインデックス作成

  1. プラグインのインストール
Copy
yarn add dotenv escape-string-regexp gatsby-plugin-algollia
  1. .envをプロジェクトのルートフォルダに追加し、下記のものを定義
.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. Algoliaのインデックスデータ生成用のスクリプトを作成。./src/utilsフォルダにalgoliaQueries.jsを作成。
./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. gatsby-plugin-algoliaプラグインの設定
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. Algoliaサイトから Application ID, Search-Only API Key, Admin API Keyを取得し、.envファイルに設定

    GatsbyJSのオフィシャルサイトのドキュメントを参考にしてください

  2. インデックス生成

Copy
# gatsby build
yarn build
  1. 完了

    全てのページがAlgoliaにインデックスズミとなります。次はビュー側のコンポーネント作成しましょう。

SearchBoxコンポーネントの作成

単純なReactコンポーネントの作成となりますが、Gatsbyのオフィシャルサイトドキュメントを参考にしてください。あるいはこちらのコミットをコピーしても構いません。

それでも手間かなと感じる方は、こちらのプロジェクトをチェックアウトし、ご利用ください。

多言語検索の実装

ここまで、SearchBoxでAlgoliaから検索できるようになりました。ただ、言語ごとの検索が有効にされていないため、言語とかかわらずに全てのページが出てきます。

algolia-default.png

これを直しましょう。

AlgoliaのインデックスにlangKeyの追加

言語ごと検索ができるように、Algoliaのインデックスに言語区別するためのキー(langKey)を追加し、フィルターをかけなければいけません。

まずAlgoliaのインデックス生成用のalgoliaQueries.jsを下記の通りに直しましょう。

  1. AlgoliaにlangKeyのインデックスを生成できるように、langKeyをpageQueryに追加
Copy
fields {
  slug
  langKey}
  1. Algoliaに多言語の設定を有効に
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'],}

編集済のalgoliaQueries.jsがこのようになります:

./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;

SearchBoxにフィルター機能を有効

Algoliaのフィルター関連のドキュメントがわかりやすく思いません。苦労したが、フィルター関連の設定方法のドキュメントに辿り着けました。

ドキュメントの通り、SearchBox(./src/components/Search/index.js)コンポーネントを下記のように直しましょう。

  1. Configureコンポーネントの導入
Copy
import { Configure } from 'react-instantsearch-dom';
  1. langKeyフィルターの追加
Copy
<Configure filters={`langKey:${lang}`} />

まとめると:

./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>
  );
}

インデックスの再生成

これで多言語の設定が終わりまして、langKey付きのインデックスを生成して、SearchBoxで検索をもう一度やってみましょう。

Copy
yarn build

algolia-multilingual.png

完璧!

完了

GatsbyJSサイトの多言語検索を実装するのに大変とはいえませんが、ドキュメントがまだ充実ともいえないでしょう。ご参考まで。では。

実装済のプロジェクトはこちらへ。

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


関連投稿

GatsbyJSのコードブロックにコピーボタンを追加する

2022年3月13日

ランディングページに問い合わせフォームのサードパーティ・ コードの遅延

2020年8月10日

ThunderMiracle

Blog part of ThunderMiracle.com