Gatsbyでalgolia検索を導入する 2021.10.25
プラグインの導入と設定
公式プラグインがあるので、それを使う。
% npm install gatsby-plugin-algolia
環境変数を設定する必要があるので、まずalgoliaに登録。
登録を済ませると“Overview”に秘密情報などがあるので、それをコピーする。ALGOLIA_API_KEY
は“Admin API Key”。ローカルでbuildする場合でもproductionのほうに設定。
// .env.production
ALGOLIA_APP_ID=XXX
ALGOLIA_API_KEY=XXX
ALGOLIA_INDEX_NAME=XXX
gatsby-config.jsでプラグインの設定。コンテンツはremarkなのでクエリもremarkに合わせた。
require('dotenv').config({
path: `.env.${process.env.NODE_ENV}`,
});
// gatsby-config.js
const myQuery = `{
posts: allMarkdownRemark {
edges {
node {
fields {
slug
}
frontmatter {
tags
title
}
id
}
}
}
}`;
const queries = [
{
query: myQuery,
transformer: ({ data }) => {
return data.posts.edges.map(edge => edge.node)
},
},
];
module.exports = {
plugins: [
// ...
{
resolve: `gatsby-plugin-algolia`,
options: {
appId: process.env.ALGOLIA_APP_ID,
apiKey: process.env.ALGOLIA_API_KEY,
indexName: process.env.ALGOLIA_INDEX_NAME,
queries,
chunkSize: 10000, // default: 1000
},
},
],
};
ビルドタイムでコンテンツをalgoliaに送信する。
% gatsby build
...
success index to Algolia - 5.743s - Done!
できた。
UIを追加
まずは公式のReact InstantSearchを使ってみる。
% npm install react-instantsearch-dom algoliasearch
import { InstantSearch, SearchBox, Hits } from 'react-instantsearch-dom';
const searchClient = algoliasearch(
'<Application ID>',
'<Search-Only API Key>'
);
return (
<InstantSearch
indexName="<index name>"
searchClient={searchClient}
>
<SearchBox />
<Hits />
</InstantSearch>
)
公式のスタイルを入れてみるが、tailwindcss-typographyと外見的に競合してしまうので手入れが必要。
% npm install instantsearch.css
// Include only the reset
import 'instantsearch.css/themes/reset.css';
// or include the full Satellite theme
import 'instantsearch.css/themes/satellite.css';
React InstantSearchのカスタマイズ
React InstantSearchのドキュメントの、Basicsの各項目の最後に“Customize the UI”という項目があるので参考にする。
SearchBoxのカスタマイズ
createConnector
を使う。HOCは久しぶりに見た。
getProvidedProps
でprops
も渡しておいて、任意のpropsを使えるようにした。
useRef
で自動的にfocusするようにした。
import * as React from 'react'
import { createConnector } from 'react-instantsearch-dom'
const connectWithQuery = createConnector({
displayName: 'WidgetWithQuery',
getProvidedProps(props, searchState) {
const currentRefinement = searchState.attributeForMyQuery || ''
return { ...props, currentRefinement };
},
refine(props, searchState, nextRefinement) {
return {
...searchState,
attributeForMyQuery: nextRefinement,
}
},
})
const SearchBox = ({ show, currentRefinement, refine }) => {
const inputRef = React.useRef(null)
React.useEffect(() => {
if (inputRef.current && show) {
inputRef.current.focus()
}
}, [show, inputRef])
return (
<input
ref={inputRef}
id="search_keyword"
label="Search"
name="search_keyword"
className="..."
placeholder="Search"
value={currentRefinement}
onChange={e => refine(e.currentTarget.value)}
/>
)
}
export default connectWithQuery(SearchBox)
Hitsのカスタマイズ
connectHits
を使う。こちらは特段のことはない。
import * as React from 'react'
import { connectHits } from 'react-instantsearch-dom'
import PostDigest from '../post-digest'
const Hits = ({ hits }) => (
<div>
{hits.map(hit => (
<PostDigest
key={hit.id}
id={hit.id}
slug={hit.fields.slug}
title={hit.frontmatter.title}
date={hit.frontmatter.date}
tags={hit.frontmatter.tags}
timeToRead={hit.timeToRead}
/>
))}
</div>
)
export default connectHits(Hits)
検索ダイアログとして集約
カスタマイズしたパーツを集約。フルスクリーンモーダルの形にした。
searchClient
のsearch
をカスタマイズして、クエリが空のときリクエストしないようにした。
import * as React from 'react'
import algoliasearch from 'algoliasearch/lite'
import { XIcon } from '@heroicons/react/outline'
import { InstantSearch } from 'react-instantsearch-dom'
import SearchBox from './search-dialog/search-box'
import Hits from './search-dialog/hits'
const algoliaClient = algoliasearch(
process.env.GATSBY_ALGOLIA_APP_ID,
process.env.GATSBY_ALGOLIA_SEARCH_API_KEY,
)
const searchClient = {
...algoliaClient,
search(requests) {
if (requests.every(({ params }) => !params.query)) {
return Promise.resolve({
results: requests.map(() => ({
hits: [],
nbHits: 0,
nbPages: 0,
page: 0,
processingTimeMS: 0,
})),
});
}
return algoliaClient.search(requests)
},
}
const SearchDialog = ({ show, onClose = () => {} }) => {
return (
<div className="...">
<InstantSearch
indexName={process.env.GATSBY_ALGOLIA_INDEX_NAME}
searchClient={searchClient}
>
<div className="flex items-center pl-4">
<div className="h-6 w-6"></div>
<SearchBox show={show} />
<button className="p-4" onClick={onClose}>
<XIcon className="h-6 w-6 text-gray-800 dark:text-gray-50" />
</button>
</div>
<div className="px-4 pb-4 overflow-y-auto">
<div className="md:w-1/2 mx-auto">
<Hits />
</div>
</div>
</InstantSearch>
</div>
)
}
export default SearchDialog
これで、ブログにalgolia検索を導入できた。
そのほか、参考になったサイト。