Gatsby+microCMSサイトにAlgolia全文検索機能を実装

# Gatsby+microCMSサイトにAlgolia全文検索機能を実装

## 経緯と調査


### Bing Custom Searchの設置
サイト内検索機能として[自社サイトにBing Custom Searchを設置（サイト内検索）](https://qiita.com/atomyah/items/ceeeba84bbd7dc642997)のとおりBing Custom Searchを埋め込む予定だった。しかし、`<Helmet />`やhtml.js、Iflamely、様々なことを試したが駄目だった。表示されない。

### Markdownファイルを検索用データにする

以下のコマンドでマークダウン用のプラグインを追加、

```bash
$ yarn add gatsby-transformer-remark
```

そしてマークダウンファイルを作りローカルに保存。プラグインでそれを読み込んで検索するやり方がある。悪くない。しかし全文検索でなくキーワードに登録した単語での検索になる。マークダウンファイルの準備も必要。


参考：
[Gatsby製ブログでサイト内検索を実装しました](https://diff001a.netlify.app/gatsby-site-search/)
[Gatsby と Netlify で Jamstack 構成のブログサイトを作ろう](https://www.to-r.net/media/jamstack-demo/)

### JS Searchで実装

たぶん、同じくキーワードでの検索でありマークダウンファイルを準備しなければならない。

参考：
[GatsbyJS 検索機能を実装する（JsSearchを利用）](https://ichinari.work/JavaScript_GatsbyJS_20191110)


## Algoliaで実装
Algoliaの実装は「[Angular+Firebase+Algoliaで全文検索機能搭載方法](https://qiita.com/atomyah/items/4c7784a09ef215f256aa) 」でいちどやった。それなりに大変だったので忌避していたが、ちょっと調べたらGatsby用のプラグインあり。しかも「…The index will be synchronised with the provided index name on Algolia on the build step in Gatsby. 」
<strong>Gatsbyビルド時に自動的にAlgoliaのIndexを作成する⁉</strong>
えっえっ:heart_eyes_cat:

ということでAlgoliaできまり.

## まずはバックエンド側

### Algoliaダッシュボードでの作業
ログインできる状態にしておくだけ。
（下画面↓はすでにある別サイトのインデックススタティクス）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/307441/9bf7540b-337f-cf19-f2df-5bb4b6adedfd.png)

### Gatsby側の作業

#### プラグインをインストール

```bash
$  yarn add gatsby-plugin-algolia react-instantsearch-dom dotenv
```

#### .envファイルを作成。
プロジェクトルートに.env.productionファイルと.env.developmentファイルを作成。
それぞれに以下のように記述。（以下のキー名は例）。

```
GATSBY_ALGOLIA_APP_ID = KA4OJA9KAS
GATSBY_ALGOLIA_SEARCH_KEY=lkjas987ef923ohli9asj213k12n59a
ALGOLIA_ADMIN_KEY = lksa09sadkj1230asd09dfvj12309aj
```

各キーはAlgoliaダッシュボードのAPI Keysのページにある。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/307441/5c773731-e349-f9cb-b21a-ad6f86114058.png)



#### gatsby-config.jsを編集

以下のようにプラグインのコンフィグとクエリを記述。

```js
// gatsby-config.js

require('dotenv').config({
  path: `.env.${process.env.NODE_ENV}`,
});

module.exports = {
    siteMetadata: {
      title: `ほげほげサイト`,
      titleTemplate: `%s · ほげほげサイト`,
・・・略・・・
    },
  plugins: [
・・・略・・・
      {
        resolve: `gatsby-plugin-algolia`,
        options: {
          appId: process.env.GATSBY_ALGOLIA_APP_ID,
          apiKey: process.env.ALGOLIA_ADMIN_KEY,
          indexName: "hoge-site",
          queries: [
            {
              query: `{
                allMicrocmsArticles {
                  edges {
                    node {
                      num
                      title
                      body
                      category {
                        name
                      }
                    }
                  }
                }
              }`,
              transformer: ({ data }) => data.allMicrocmsArticles.edges.map(({ node }) => {
                return {
                  id: node.category[0].name + '-article/' + node.num,
                  body: sumarrize(node.body),
                  title: node.title,
　　　　　　　　　　category: node.category[0].name
                }
              })
            },
          ],
          chunkSize: 100000,
　　　　　 settings: {
            queryLanguages: ['ja']
          },
        },
      },
    ],
}
```

・indexNameは好きな名前。
・IDはAlgoliaのレコードにつけられる一意ID。一意になればなんでもいい。microCMSが自動アサインした記事IDでいい。わたしは好みの問題でスラグと同一になるようにした。またそうすればそのIDがLink先としてそのまま利用できる。
・記事ページの本文（長～い）をインデックスしたいのでチャンクサイズを100000にした。デフォルトは10000
・GitのマニュアルにはallSitePageで全ページ取得できるみたいに書いてあるがタイトルやコンテンツ（本文）のデータがそこにはない。allMarkdownRemarkにもない。わからない。静的ページもすべてインデックスしたかったがとりあえずmicroCMSでアップした動的ページだけでも、ということでallMicrocmsArticlesをインデックスするようにした。このへんはhttp://localhost:8000/___graphql ↓を見ながら試行錯誤。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/307441/8bf2afba-faa6-6870-35ab-d8b1fd22c93a.png)



#### Algoliaにインデックス

あとはgatsby buildするだけで……
おやエラー(ﾟДﾟ)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/307441/461e8b3d-3b81-ccad-47ef-c546b687b277.png)

サイズが大きすぎる、というエラー。
無料枠だと10kbが上限らしい。


これは仕方ないので、

```js
// gatsby-config.js
・・・略・・・
              transformer: ({ data }) => data.allMicrocmsArticles.edges.map(({ node }) => {
                return {
                  id: node.category[0].name + '-article/' + node.num,
                  body: sumarrize(node.body), // ← 3000字以内にトリミング
                  title: node.title,
                }
              })
            },
          ],
          chunkSize: 100000,
        },
      },
    ],
}

// トリミングする関数
let striptags = require('striptags');
function sumarrize(html) {
  const metaDescription = striptags(html).replace(/\r?\n/g, '').trim();
  return metaDescription.length <= 3000
    ? metaDescription
    : metaDescription.slice(0, 3000) + '...';
}

```

こんなふうにsumarrize()関数作って記事本文（body部分）が3000字以内に収まるようにトリミング。GraphQLの操作でもっとエレガントなやり方があると思うがわからない。

これでgatsby buildする。
40記事分が自動でほとんど時間かからずインデックス作成される。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/307441/79e435e4-c6e8-8271-7147-7571063f550e.png)

Algoliaダッシュボード上でテストできる。リアルタイムに検索結果が反映される。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/307441/30b292c6-6f76-8504-f8e7-0e3e9a113f7b.png)


#### Algoliaインデックスのコンフィグを編集

できあがったインデックスのコンフィグを編集する。
Searchable Attributesに検索ワードに引っ掛けたいフィールドを追加する。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/307441/467a9977-0777-27a6-cc5f-6cd117cc6464.png)

あとは言語設定だろうか。Japaneseにした。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/307441/ee6e72a6-02e9-f0fc-3b79-cb7ea60958c1.png)



他にもいろいろなコンフィグがあるが基本的にこれだけでよいと思う。不思議だがこれでしばらく（1時間とか）放っておくと検索精度が最適化されていく。なので焦っていろいろなコンフィグをいじらないほうが良い。

これでバックエンド側は終了

### フロント側の作業

### 検索画面を設置

Algolia InstantsearchというモジュールでUIを作っていく。

#### インストール

不要。
react-instantsearch-domとgatsby-plugin-algoliaを最初にインストールしたので。

#### gatsby-config.jsの編集

不要


#### 設置方法

componentsフォルダの下にSearchフォルダを作成し、そこにindex.jsファイルを作成。

そして以下のコード。

```jsx
// components/Search/index.js

import React from 'react'
import algoliasearch from 'algoliasearch/lite';
import { 
  InstantSearch, 
  SearchBox, 
　Hits,
 } from 'react-instantsearch-dom';

const searchClient = algoliasearch(
   'KA4OJA9KAS', 'lkjas987ef923ohli9asj213k12n59a'
);

// InstantSearchのコンフィグとレンダリング
const Search = () => {

    return (
        <>
        <InstantSearch 
            searchClient={searchClient}
            indexName="hoge-site"
        >
        　<SearchBox />
        　<Hits />
        </InstantSearch>
        </>
    )
}
export default Search
```
`<InstantSearch />`タグにAlgoliaのApplication IDとSearch-Only API Key、それにindex名を設定し、`<InstantSearch />`タグで挟む形でサーチボックス`<SearchBox />`や検索結果を表示する`<Hits />`などを記述する。

そしたらSearchコンポーネントをLayout.jsの適当な場所に埋め込んでみる。

```jsx
// components/layout.js
・・・略・・・
import Search from './Search'
・・・略・・・
<Search />
・・・略・・・
```

これだけで下図の通りいちおう機能としては使えるようになる。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/307441/7ba14624-1c32-2816-be8d-fb3e4eed8d3a.png)


あとは検索結果表示を調整したりスタイルを整える。

```jsx
// components/Search/index.js

import React from 'react'
import { Link } from "gatsby"
import algoliasearch from 'algoliasearch/lite';
import { 
  InstantSearch, 
  SearchBox, 
  Hits,
  Highlight,
  Stats,
  Pagination,
  SortBy
 } from 'react-instantsearch-dom';
import './Search.css'

const searchClient = algoliasearch(
   'KA4OJA9KAS', 'lkjas987ef923ohli9asj213k12n59a'
);

const Header = () => (
    <header className="header">
        <SearchBox
            className="search-bar"
            translations={{ placeholder: '検索ワード' }}
        />
    </header>
);

const Hit = ({ hit }) => (
    
        <div className="card">
          <div className="card-contents">
                <Highlight attribute="title" hit={hit} className="card-title" />
                <Link to={hit.id} >
                    <span className="link-to">記事本文へ</span>
                </Link>
                <Highlight attribute="body" hit={hit}  className="card-year"/>
                <div className="card-rating">Rating: {hit.rating}</div>
                <div className="card-genre"> <span>{hit.category}</span></div>
            </div>
        </div>
    
);

const Content = () => (
    <main>
        <div className="information">
            <Stats/>
        </div>
        <Hits hitComponent={Hit} />
        <div> <Pagination/> </div>
    </main>
);

// InstantSearchのコンフィグとレンダリング
const Search = () => {

    return (
        <>
        <InstantSearch 
            searchClient={searchClient}
            indexName="hoge-site"
        >
            <Header />
            <div className="body-content">
                <Content/>
            </div>

        </InstantSearch>
        </>
    )
}

export default Search
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/307441/9d0960e4-65a1-2341-6c9b-9265e0c1de45.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/307441/7ebd35d3-f9a4-f02a-d2cc-18f0c5db7be5.png)

ちなみにCSSは以下の通り。

```css
// components/Search/Search.css

.header{
    padding-top: 1em;
    width: 100%;
    display: flex;
    background-color: #dce2e9;
    height: 11vh
}
.search-bar{
    display: flex;
    justify-content: center;
    width: 100%
}
input{
    max-width: 500px;
    border: none;
    border-radius: .5em;
    padding: 10px

}
.ais-SearchBox-submit{
    width: 50px;
    border: none;
    padding: 10px;
    color: #c4c4c4
}
.ais-SearchBox-reset{
    width: 50px;
    border: solid;
    padding: 10px;
    color: #c4c4c4
}
main{
    width: 100%;
}
ul{
    width: 100%;
    display: flex;
    flex-wrap: wrap
}
li{
    list-style-type: none;
}
.ais-Hits-item{
    width: 100%;
}
.card{
    background-color: #f9f9f9;
    display: flex;
    border-radius: 10px;
    margin:20px;
    padding: 15px;
    box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19);
}
.card-contents{
    margin-left: 20px;
    display: flex;
    align-content: center;
    flex-direction: column;
    justify-content: space-between
}
.link-to{
    color: blue;
}
.card-title{
    font-weight: bold
}
.card-genre > span{
    font-size: 15px;
    width: 20px;
    padding: 4px;
    background-color: #c4c4c4
}
.information{
    padding-top: 10px;
    display: flex;
    justify-content: space-around;
    font-size: 11px
}
a{
    text-decoration: none
}
a:visited{
    color: black;
}
.ais-Pagination-list{
    display: flex;
    justify-content: center
}
.ais-Pagination-item{
    margin: 5px
}
```

参考：[API reference.](https://www.algolia.com/doc/api-reference/widgets/react/)

### さらに必要な実装

#### 検索結果画面の表示非表示スイッチ機能
このままでは常に検索結果が表示されている状態なので(全レコードが表示されつづける）、検索ワードがカラのときは`<Hits />`を非表示にする機能を実装する。

```jsx
//components/Search/index.js

import React, { useState, useEffect } from 'react' // 変更
import { Link } from "gatsby"
import algoliasearch from 'algoliasearch/lite';
import { 
  InstantSearch, 
  SearchBox, 
  Hits,
  Highlight,
  Stats,
  Pagination,
  SortBy
 } from 'react-instantsearch-dom';
import './Search.css'

const searchClient = algoliasearch(
   'KA4OJA9KAS', 'lkjas987ef923ohli9asj213k12n59a'
);

// Headerコンポーネントは削除して<SearchBox />はSearchコンポーネントに移動
const Hit = ({ hit }) => ( 
    <div className="card">
        <div className="card-contents">
            <Highlight attribute="title" hit={hit} className="card-title" />
            <Link to={hit.id} >
                <span className="link-to">記事本文へ</span>
            </Link>
            <Highlight attribute="body" hit={hit}  className="card-year"/>
            <div className="card-rating">Rating: {hit.rating}</div>
            <div className="card-genre"> <span>{hit.category}</span></div>
        </div>
    </div>    
);

const Content = () => (
    <main>
        <div className="information">
            <Stats/>
        </div>
        <Hits hitComponent={Hit} />
        <div> <Pagination/> </div>
    </main>
);

// InstantSearchのコンフィグとレンダリング
const Search = () => {
    const [flag, setFlag] = React.useState(false) //追加

    return (
        <>
        <InstantSearch 
            searchClient={searchClient}
            indexName="hoge-site"
        >
{/* <header>...</header>を追加 */}
        <header className="header">
            <SearchBox
                className="search-bar"
                translations={{ placeholder: '検索ワード' }}
                onSubmit={(event) => {
                    event.preventDefault();
                    setFlag(true);
                }}
                onReset={(event) => {
                    event.preventDefault();
                    setFlag(false);
                }}
                onKeyUp={(event) => {
                    event.preventDefault();
                    setFlag(false);
                }}
            />
        </header>

            <div className={!flag ? 'input-empty' : 'input-value'}>
                <div className="body-content">
                    <Content/>
                </div>
            </div>

        </InstantSearch>
        </>
    )
}

export default Search
```

SearchBoxのイベントによりオフオンするbooleanステート変数flagを追加。flagがfalseであれば検索結果画面を表示しないようにする。
ステート変数をコンポーネント間でやりとりするのが面倒なので`<SearchBox />`はSearchコンポーネントに移動し、Headerコンポーネントは削除。

#### 本文表示をトリミング

検索結果に表示する記事本文が長すぎるので適当な分量にトリミングする。これは`<Highlight />`を`<Snippet />`に変えるだけでよい。

```jsx
//components/Search/index.js

import React, { useState, useEffect } from 'react'
import { Link } from "gatsby"
import algoliasearch from 'algoliasearch/lite';
import { 
  InstantSearch, 
  SearchBox, 
  Hits,
  Highlight,
  Stats,
  Pagination,
  Snippet                             // 追加
 } from 'react-instantsearch-dom';
import './Search.css'

const searchClient = algoliasearch(
   'KA4OJA9KAS', 'lkjas987ef923ohli9asj213k12n59a'
);

const Hit = ({ hit }) => ( 
    <div className="card">
        <div className="card-contents">
            <Highlight attribute="title" hit={hit} className="card-title" />
            <Link to={hit.id} >
                <span className="link-to">記事本文へ</span>
            </Link>
            <Snippet attribute="body" hit={hit} />       // 変更
            <div className="card-rating">Rating: {hit.rating}</div>
            <div className="card-genre"> <span>{hit.category}</span></div>
        </div>
    </div>    
);
・・・略・・・
```

しかし表示されない。。。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/307441/d18cc919-0786-1297-ca75-87956ea76a2d.png)

これはAlgoliaダッシュボードで次のようにSnippetingにbody属性を追加して解決。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/307441/4e763a94-6a11-219d-226b-cc7eb841f291.png)


100文字になった。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/307441/21b0a003-a32d-b7c9-4f58-57038033c40a.png)


####検索ヒットワードを強調

ヒットワードを強調表示にしたい。これは`<Snippet />`に`tagName=""`オプションをつけるだけ。

```jsx
<Snippet tagName="mark" hit={hit} attribute="body" />
```

markならこうなる。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/307441/f3df712b-ee57-3999-5b10-88b63230916d.png)

strongにすればこうなる
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/307441/224677fa-ab29-a459-4e81-7ac33d86bef1.png)


### ビルドにあたっての注意点

`gatsby build`により自動でAlgoliaにインデックスアップロードすると、ダッシュボードで設定したコンフィグSnippeting, Searchable attributes,およびIndex Languagesがリセットされてしまう。

これは`gatsby-config.js`でgatsby-plugin-algoliaプラグインのsettingsを追加してあげて解決。[API Parameters
](https://www.algolia.com/doc/api-reference/api-parameters/)

```js
//gatsby-config.js
・・・略・・・
      {
        resolve: `gatsby-plugin-algolia`,
          settings: {
            searchableAttributes: ['body','title'],
            indexLanguages: ['ja'],
            queryLanguages: ['ja'],
            attributesToSnippet: ['body:100']
          },
・・・略・・・
```

### 静的ページのインデックス
静的ページは手作業でインデックスを<strong>別のIndice</strong>にアップすれば良かろうと思ったが。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/307441/4a495ac6-bafb-11f8-adf0-c3e7f55b91f2.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/307441/4829244d-42cb-680c-2af1-f5b2da4e27c9.png)


しかしいまのところgatsby-plugin-algoliaでは複数Indiceに対応していないようだ。どうしたものか…。

※ <strong>同じIndice</strong>に追加した場合`gatsby build`すると手動追加したレコードは消えてしまう。

#### ワークアラウンド
アップするときの作業手順として、
① `gatsby build`
② `firebase deploy`
③ <strong>同じIndiceに</strong>静的ページインデックス（JSONファイル）を手動追加

③の作業をすることで回避。一時しのぎなのでスクリプト化はあえてしないこととする。


## 補足：パフォーマンス

普通Twitterシェアボタンなどを埋め込むとパフォーマンスは悪くなる。
以下はDisqusを埋め込んだページのパフォーマンス。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/307441/66fc4a3c-0b28-dbf5-448c-e41e60a5896b.png)

Algoliaの埋め込みはまったく影響ないように見える。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/307441/c8f3f018-c0e1-5671-297e-9d287905c134.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/307441/b080d557-6141-a4f8-5367-a4dfc2ad59d1.png)
どうやらロードにかかる時間はほんの5ミリセカンドのようだ。

# 本の宣伝
### Gatsbyバージョン５>>>>改訂２版

**前編**の『Gatsby５前編ー最新Gatsbyでつくるコーポレートサイト』と**後編**の『Gatsby５後編ー最新GatsbyとmicroCMSでつくるコーポレートサイト《サイト内検索機能付き》』を合わせ、次のようなデモサイトを構築します。
→ **https://yah-space.work**
<hr />


静的サイトジェネレーターGatsby**最新バージョン５**の基本とFile System Route APIを使用して動的にページを生成する方法を解説。またバージョン５の新機能《**Slicy API**》《**Script API**》《**Head API**》を紹介、実装方法も。《**Gatsby Functions**》での問い合わせフォーム実装やGatsby Cloudへのアップロード方法も！


<a href="https://www.amazon.co.jp/gp/product/B0BXNJTJMY?&linkCode=li3&tag=kokkahasan-22&linkId=93c5e3bf1f226141ddc807300e81b07a&language=ja_JP&ref_=as_li_ss_il" target="_blank"><img border="0" src="//ws-fe.amazon-adsystem.com/widgets/q?_encoding=UTF8&ASIN=B0BXNJTJMY&Format=_SL250_&ID=AsinImage&MarketPlace=JP&ServiceVersion=20070822&WS=1&tag=kokkahasan-22&language=ja_JP" ></a><img src="https://ir-jp.amazon-adsystem.com/e/ir?t=kokkahasan-22&language=ja_JP&l=li3&o=9&a=B0BXNJTJMY" width="1" height="1" border="0" alt="" style="border:none !important; margin:0px !important;" />
<a target="_blank" href="https://www.amazon.co.jp/gp/product/B0BXXBDJDJ?&linkCode=ll1&tag=kokkahasan-22&linkId=df7e600968c58c0090cd735681c6732c&language=ja_JP&ref_=as_li_ss_tl">Gatsby５前編ー最新Gatsbyでつくるコーポレートサイト ～基礎の基礎から応用、新機能の導入まで（書籍2,980円）</a>

<br />
<br />

**最新Gatsby５**とmicroCMSを組み合わせてのコーポレートサイト作成手順を解説。《**サイト内検索機能**》をGatsbyバージョン４からの新機能《**Gatsby Functions**》と《**microCMSのqパラメータ**》で実装。また、**SEOコンポーネントをカスタマイズ**して**microCMS APIをツイッターカードに表示**させる**OGPタグ**実装方法も解説。

<a href="https://www.amazon.co.jp/gp/product/B0BXN99WSV?&linkCode=li3&tag=kokkahasan-22&linkId=68f1905bf915f6bd365559a0c65bcdd1&language=ja_JP&ref_=as_li_ss_il" target="_blank"><img border="0" src="//ws-fe.amazon-adsystem.com/widgets/q?_encoding=UTF8&ASIN=B0BXN99WSV&Format=_SL250_&ID=AsinImage&MarketPlace=JP&ServiceVersion=20070822&WS=1&tag=kokkahasan-22&language=ja_JP" ></a><img src="https://ir-jp.amazon-adsystem.com/e/ir?t=kokkahasan-22&language=ja_JP&l=li3&o=9&a=B0BXN99WSV" width="1" height="1" border="0" alt="" style="border:none !important; margin:0px !important;" />
<a target="_blank" href="https://www.amazon.co.jp/dp/B0BXN99WSV?_encoding=UTF8&qid=&sr=&linkCode=li2&tag=kokkahasan-22&linkId=f02e2f9c50736e3f074b38b4cd73894a&language=ja_JP&ref_=as_li_ss_il">Gatsby５後編ー最新GatsbyとmicroCMSでつくるコーポレートサイト《サイト内検索機能付き》（書籍 2,790円）</a>

<br />

<br />

##参考：
[API reference.](https://www.algolia.com/doc/api-reference/widgets/react/)
[How to use Algolia for instant search](https://blog.logrocket.com/how-to-use-algolia-for-instant-search/)
[Tutorial: Algolia React InstantSearch implementation for a React Gatsby App](https://medium.com/@christinavhastenrath/tutorial-algolia-react-instantsearch-implementation-for-a-react-gatsby-app-120062f6f356)
[InstantSearch](https://www.algolia.com/doc/api-reference/widgets/instantsearch/react/)
[Gatsby製サイトにAlgoliaのサイト内検索を実装する](https://blog.leko.jp/post/on-site-search-in-gatsby-with-algolia/)
[algolia/gatsby-plugin-algolia](https://github.com/algolia/gatsby-plugin-algolia)
[Adding Search with Algolia](https://www.gatsbyjs.org/docs/adding-search-with-algolia/)