ブログとドキュメントサイトといえば、GatsbyJSが有力な候補者となるでしょう。レンダリングのスピードがかなり早く、プラグインの数が多くSEO対策も簡単にできます。ドキュメントがかなりわかりやすく、学習コストが低いと言えるでしょう。ReactJSのオフィシャルサイト https://reactjs.org/ がGatsbyJS で作られていますね。ブログ、ドキュメントサイトなら検索体験が非常に重要なポイントとなります。最近世によく使われる全文検索マネージドサービスはAlgoliaとなります。GatsbyJSのドキュメントサイトにすでにAlgoliaの導入チュートリアルが載ってあります: https://www.gatsbyjs.com/docs/adding-search-with-algolia/。しかし、多言語サイトはどうすればいいでしょうか?
今日は一歩踏まえて、多言語のGatsbyJSサイトにAlgoliaの導入方法を紹介したいと思います。
前提条件
- JavaScriptとGatsbyJSの基本知識
gatsby-plugin-i18n
で作った多言語のGatsbyJSサイト(langKeyが必要) なければ、このプロジェクト をチェックアウトしてください
Algoliaの導入
GatsbyJSのオフィシャルサイトのドキュメントにほぼ書いてあります。下記の手順と唯一の違いは、Pages
パラメータをハードコーディングではなく、.env
のGATSBY_ALGOLIA_INDEX_NAME
で定義することです。
Algoliaに全ページのインデックス作成
- プラグインのインストール
yarn add dotenv escape-string-regexp gatsby-plugin-algollia
.env
をプロジェクトのルートフォルダに追加し、下記のものを定義
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
- Algoliaのインデックスデータ生成用のスクリプトを作成。
./src/utils
フォルダにalgoliaQueries.js
を作成。
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;
gatsby-plugin-algolia
プラグインの設定
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'), }, },}
-
Algoliaサイトから
Application ID
,Search-Only API Key
,Admin API Key
を取得し、.env
ファイルに設定GatsbyJSのオフィシャルサイトのドキュメントを参考にしてください
-
インデックス生成
# gatsby build
yarn build
-
完了
全てのページがAlgoliaにインデックスズミとなります。次はビュー側のコンポーネント作成しましょう。
SearchBoxコンポーネントの作成
単純なReactコンポーネントの作成となりますが、Gatsbyのオフィシャルサイトドキュメントを参考にしてください。あるいはこちらのコミットをコピーしても構いません。
それでも手間かなと感じる方は、こちらのプロジェクトをチェックアウトし、ご利用ください。
多言語検索の実装
ここまで、SearchBoxでAlgoliaから検索できるようになりました。ただ、言語ごとの検索が有効にされていないため、言語とかかわらずに全てのページが出てきます。
これを直しましょう。
AlgoliaのインデックスにlangKey
の追加
言語ごと検索ができるように、Algoliaのインデックスに言語区別するためのキー(langKey
)を追加し、フィルターをかけなければいけません。
まずAlgoliaのインデックス生成用のalgoliaQueries.js
を下記の通りに直しましょう。
- Algoliaに
langKey
のインデックスを生成できるように、langKey
をpageQueryに追加
fields {
slug
langKey}
- Algoliaに多言語の設定を有効に
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
がこのようになります:
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
)コンポーネントを下記のように直しましょう。
Configure
コンポーネントの導入
import { Configure } from 'react-instantsearch-dom';
langKey
フィルターの追加
<Configure filters={`langKey:${lang}`} />
まとめると:
/* 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
で検索をもう一度やってみましょう。
yarn build
完璧!
完了
GatsbyJSサイトの多言語検索を実装するのに大変とはいえませんが、ドキュメントがまだ充実ともいえないでしょう。ご参考まで。では。
実装済のプロジェクトはこちらへ。