- チュートリアル
- 検索
AlgoliaとNext.jsを利用して、高度な全文検索を実現する
このチュートリアルでは、Algolia と Next.js 利用して、高度な全文検索を実現する手順を紹介します。
前提条件
- Algoliaにサインアップしていること
- Next.jsの API Routes について理解していること
- Newt CDN APIと newt-client-js を利用して、公開済みのコンテンツを取得する方法について理解していること
概要
以下の4ステップにわけて、チュートリアルを進めていきます。
- 全文検索を実現する
- 全文検索をカスタマイズする
- ソートを追加する
- ファセット検索を追加する
最終的に、以下の検索ページを作成します。
各コンポーネントの機能は以下の通りです。
作成したページは公開しています。実際に機能を触ってご確認いただくことも可能です。
※データについては、デモ用のデータなので、正確でないものもあります。
https://newt-algolia-nextjs.vercel.app/
完成時のコード
また、完成時のコードを以下に公開しています。実装時の参考として、ご覧いただけます。
Newt-Inc/newt-algolia-nextjs
検索するデータのモデル
このチュートリアルでは、静的サイトジェネレータの検索ページを作成します。静的サイトジェネレータは、以下のような情報を持つモデルとします。
モデル: ジェネレーター
フィールド名 | フィールドID | フィールドタイプ | オプション |
---|---|---|---|
タイトル | title | テキスト | 必須 |
ロゴ | logo | 画像 | 必須 |
説明 | description | マークダウン | 必須 |
URL | url | テキスト | 必須 |
タグ | tags | 選択(子要素: テキスト) | 必須・複数値 |
スター | star | 数字 | 必須 |
1. 全文検索を実現する
まず、Algoliaを利用して、全文検索が可能な検索ページを作成します。
ここでは、以下のことを行います。
- Newtからのデータ取得
- Algoliaへのデータ連携
- React InstantSearch Hooks を利用したUIの作成
Newtからのデータ取得とAlgoliaへのデータ連携を行うために、Next.jsのAPIルートを利用します。
1-1. Newtからのデータ取得
はじめに、Newtで定義した ジェネレーター
のデータを取得する getGenerators
を実装します。description
フィールドはマークダウンタイプであるため、デフォルトではHTMLの値が返却されますが、ここではテキスト形式でデータを受け取るものとします。
// lib/api.ts
import { createClient } from 'newt-client-js'
import { Generator } from '../types/generator'
const client = createClient({
spaceUid: process.env.NEXT_PUBLIC_NEWT_SPACE_UID + '',
token: process.env.NEXT_PUBLIC_NEWT_CDN_TOKEN + '',
apiType: 'cdn',
})
export const getGenerators = async (): Promise<Generator[]> => {
const { items } = await client.getContents<Generator>({
appUid: process.env.NEXT_PUBLIC_NEWT_APP_UID + '',
modelUid: process.env.NEXT_PUBLIC_NEWT_MODEL_UID + '',
query: {
description: { fmt: 'text' },
},
})
return items
}
getContents
の返り値の型として Promise<Generator[]>
を指定しています。この Generator
の型は、Newtの管理画面で設定したモデル情報をもとに定義します。
コードの詳細は types/generator.ts をご確認下さい。
環境変数として、以下の値を定義します。Newtの管理画面での値をもとに定義して下さい。
NEXT_PUBLIC_NEWT_SPACE_UID=スペースUID
NEXT_PUBLIC_NEWT_APP_UID=AppUID
NEXT_PUBLIC_NEWT_MODEL_UID=モデルUID
NEXT_PUBLIC_NEWT_CDN_TOKEN=Newt CDN APIトークン
1-2. Algoliaへのデータ連携
以下の手順でデータを連携します。
- APIキーの確認
- データのフォーマット
- データの連携
1-2-1. APIキーの確認
はじめに、AlgoliaのAPIキーを確認しておきます。
Algoliaの管理画面に入り、「API Keys」のページから確認できます。
上記で確認した、AlgoliaのApplication IDとAdmin API Keyの値を環境変数として追加します。
Algolia Primary Indexについては、お好きな名前で定義して下さい(このチュートリアルでは generator_relevance
としています)。
※ ALGOLIA_ADMIN_API_KEY
については、後述するクライアントサイドでの処理から参照しないため、NEXT_PUBLIC_
を外します。
NEXT_PUBLIC_ALGOLIA_APPLICATION_ID=Algolia Application ID
NEXT_PUBLIC_ALGOLIA_PRIMARY_INDEX=Algolia Primary Index
ALGOLIA_ADMIN_API_KEY=Algolia Admin API Key
1-2-2. データのフォーマット
続いて、取得したデータをAlgoliaの求める形式にフォーマットします。
Algoliaでは、各オブジェクトを一意の objectID
で識別するため、Newtから取得したコンテンツの _id
情報をもとに、objectID
を設定します。
const generators = await getGenerators()
const formattedGenerators = generators.map((generator) => {
return {
objectID: generator._id,
...generator,
}
})
1-2-3. データの連携
Algoliaの JavaScript APIクライアント を利用します。
詳細については、Algoliaの Send and Update Your Data のドキュメントをご確認ください。
以下のようなコードとなります。
import algoliasearch from 'algoliasearch'
// Algoliaとの接続と認証
const algolia = algoliasearch(
process.env.NEXT_PUBLIC_ALGOLIA_APPLICATION_ID + '',
process.env.ALGOLIA_ADMIN_API_KEY + ''
)
// インデックスの作成
const index = algolia.initIndex(
process.env.NEXT_PUBLIC_ALGOLIA_PRIMARY_INDEX + ''
)
// (中略)formattedGeneratorsの取得
// レコードの保存
await index.saveObjects(formattedGenerators)
これらのデータ連携の処理を実行するため、Next.jsのAPIルートを利用します。
まとめると、以下のようになります。
// api/algolia/save.ts
import algoliasearch from 'algoliasearch'
import { NextApiRequest, NextApiResponse } from 'next'
import { getGenerators } from '../../../lib/api'
const algolia = algoliasearch(
process.env.NEXT_PUBLIC_ALGOLIA_APPLICATION_ID + '',
process.env.ALGOLIA_ADMIN_API_KEY + ''
)
const index = algolia.initIndex(
process.env.NEXT_PUBLIC_ALGOLIA_PRIMARY_INDEX + ''
)
export default async function handler(
req: NextApiRequest,
res: NextApiResponse
) {
try {
const generators = await getGenerators()
const formattedGenerators = generators.map((generator) => {
return {
objectID: generator._id,
...generator,
}
})
await index.saveObjects(formattedGenerators)
res.status(200).json({ message: 'Success' })
} catch (err: any) {
res.status(400).json({ message: err?.message })
}
}
ローカル環境で、このメソッドを実行する場合、以下にリクエストを送ります。
http://localhost:3000/api/algolia/save
データが連携されると、Algoliaの管理画面で、以下のようにインデックスが表示されます。
1-2-4. 本番環境からWebhookを利用してデータを連携する場合
本番環境からWebhookを利用する場合、以下の検証を追加します。
- Newtの Webhook から実行することを想定して、POSTメソッドのみを受け付ける
- リクエストが有効なものか、クエリパラメータのsecretの値で検証する(ここでは
ALGOLIA_SECRET_TOKEN
という環境変数を利用する)
if (req.method !== 'POST') {
return res.status(405).json({ message: `Method not allowed` })
}
if (req.query.secret !== process.env.ALGOLIA_SECRET_TOKEN) {
return res.status(401).json({ message: 'Invalid token' })
}
1-2-5. データ連携の方針
Algoliaでは、データの変更に合わせてインデックスを最新に保つ必要があります。インデックスを更新する方法としては、以下の3つの方法が考えられます。
- Full reindexing
- Full record updates
- Partial record updates
このチュートリアルでは saveObjects のメソッドを利用して、2のFull record updatesの形式でデータを同期していますが、ユースケースに応じて、適切なデータ連携方針を選択するようご注意ください。
1-3. React InstantSearch Hooksを利用したUIの作成
Algoliaでは、検索インターフェースを素早く構築するために、いくつかのライブラリが用意されています。このチュートリアルでは、Next.jsを利用するため、React InstantSearch Hooks を利用します。
他にも Vue InstantSearch や Angular InstantSearch などがあります。
ここでは、React InstantSearch Hooksで定義済みのUIコンポーネントを利用して、検索画面を作成します。
InstantSearch, SearchBox, Hits, PoweredBy の4つを利用します。順に説明します。
1-3-1. InstantSearch
InstantSearch はReact InstantSearch Hooksを使い始めるためのルートコンポーネントです。
引数として、indexName
と searchClient
を渡します。
searchClientにはAlgoliaの Application ID
と Search-Only API Key
を渡します。
Application ID
は、1-2-2で設定した環境変数を利用して指定します。
Search-Only API Key
は、Algoliaの管理画面に入り、「API Keys」のページから確認できます。
確認した、Search-Only API Key
の値を環境変数として追加します。
NEXT_PUBLIC_ALGOLIA_SEARCH_ONLY_API_KEY=Algolia Search-Only API Key
以下のようなコードとなります。
// pages/index.tsx
import algoliasearch from 'algoliasearch/lite'
import { InstantSearch } from 'react-instantsearch-hooks-web'
const searchClient = algoliasearch(
process.env.NEXT_PUBLIC_ALGOLIA_APPLICATION_ID + '',
process.env.NEXT_PUBLIC_ALGOLIA_SEARCH_ONLY_API_KEY + ''
)
const Home = () => {
return (
<InstantSearch
indexName={process.env.NEXT_PUBLIC_ALGOLIA_PRIMARY_INDEX + ''}
searchClient={searchClient}
>
{/* Widgets */}
</InstantSearch>
)
}
1-3-2. SearchBox
SearchBox は、ユーザーがテキストベースのクエリを実行するためのウィジェットです。
InstantSearchの下層に配置します。
pages/index.tsx
を以下のように修正します。
import algoliasearch from 'algoliasearch/lite'
- import { InstantSearch } from 'react-instantsearch-hooks-web'
+ import { InstantSearch, SearchBox } from 'react-instantsearch-hooks-web'
// (中略)
const Home = () => {
return (
<InstantSearch
indexName={process.env.NEXT_PUBLIC_ALGOLIA_PRIMARY_INDEX + ''}
searchClient={searchClient}
>
- {/* Widgets */}
+ <SearchBox />
</InstantSearch>
)
}
1-3-3. Hits
Hits は、検索結果の一覧を表示するためのウィジェットです。hitComponent
のpropsを利用することで、各検索結果の表示をカスタマイズできます。
import algoliasearch from 'algoliasearch/lite'
- import { InstantSearch, SearchBox } from 'react-instantsearch-hooks-web'
+ import { InstantSearch, SearchBox, Hits } from 'react-instantsearch-hooks-web'
+ import { Hit } from '../components/Hit'
// (中略)
const Home = () => {
return (
<InstantSearch
indexName={process.env.NEXT_PUBLIC_ALGOLIA_PRIMARY_INDEX + ''}
searchClient={searchClient}
>
<SearchBox />
+ <Hits hitComponent={Hit} />
</InstantSearch>
)
}
各検索結果をハイライトしたい場合、Highlight を使います。
例えば title
属性と description
属性をハイライトしたい場合、以下のように記載します。
// components/Hit.tsx
import type { Hit as HitType } from 'instantsearch.js'
import { Highlight } from 'react-instantsearch-hooks-web'
import { Generator } from '../types/generator'
export const Hit = ({ hit }: { hit: HitType & Generator }) => {
return (
<div>
<Highlight attribute="title" hit={hit} />
<Highlight attribute="description" hit={hit} />
</div>
)
}
また、検索結果が0件の場合に表示をカスタマイズすることも可能です。
useInstantSearch()
フックを使用します。
// components/NoResults.tsx
import { useInstantSearch } from 'react-instantsearch-hooks-web'
export const NoResultsBoundary = ({ children, fallback }: any) => {
const { results } = useInstantSearch()
if (!results.__isArtificial && results.nbHits === 0) {
return (
<>
{fallback}
<div hidden>{children}</div>
</>
)
}
return children
}
export const NoResults = () => {
const { indexUiState } = useInstantSearch()
return (
<div className="ais-Hits_Empty">
<p>
No results for <q>{indexUiState.query}</q>.
</p>
</div>
)
}
pages/index.tsx
は以下のようになります。
// pages/index.tsx
import algoliasearch from 'algoliasearch/lite'
import { InstantSearch, SearchBox, Hits } from 'react-instantsearch-hooks-web'
import { Hit } from '../components/Hit'
+ import { NoResultsBoundary, NoResults } from '../components/NoResults'
// (中略)
const Home = () => {
return (
<InstantSearch
indexName={process.env.NEXT_PUBLIC_ALGOLIA_PRIMARY_INDEX + ''}
searchClient={searchClient}
>
<SearchBox />
- <Hits hitComponent={Hit} />
+ <NoResultsBoundary fallback={<NoResults />}>
+ <Hits hitComponent={Hit} />
+ </NoResultsBoundary>
</InstantSearch>
)
}
1-3-4. PoweredBy
Algoliaの無料プランを利用する場合、Search by Algolia
のロゴを入れる必要があります。
PoweredBy のウィジェットを利用します。
pages/index.tsx
を以下のように修正します。
// pages/index.tsx
import algoliasearch from 'algoliasearch/lite'
- import { InstantSearch, SearchBox, Hits } from 'react-instantsearch-hooks-web'
+ import {
+ InstantSearch,
+ SearchBox,
+ Hits,
+ PoweredBy,
+ } from 'react-instantsearch-hooks-web'
import { Hit } from '../components/Hit'
import { NoResultsBoundary, NoResults } from '../components/NoResults'
// (中略)
const Home = () => {
return (
<InstantSearch
indexName={process.env.NEXT_PUBLIC_ALGOLIA_PRIMARY_INDEX + ''}
searchClient={searchClient}
>
<SearchBox />
+ <PoweredBy />
<NoResultsBoundary fallback={<NoResults />}>
<Hits hitComponent={Hit} />
</NoResultsBoundary>
</InstantSearch>
)
}
1-3-5. スタイル
ウィジェットのスタイルをカスタマイズするには「既存のクラスに従ってスタイルを作成する」「InstantSearchのテーマを利用する」など、いくつかの方法があります。
ここでは、既存のクラスに従って独自のスタイルを作成しています。
定義の詳細は styles/globals.css のファイルをご確認下さい。
まとめ
ヘッダーやフッターの追加、スタイルも追加して、各ファイルは以下のように定義します。
// pages/index.tsx
import type { NextPage } from 'next'
import Head from 'next/head'
import algoliasearch from 'algoliasearch/lite'
import {
InstantSearch,
SearchBox,
Hits,
PoweredBy,
} from 'react-instantsearch-hooks-web'
import styles from '../styles/Home.module.css'
import { Hit } from '../components/Hit'
import { NoResultsBoundary, NoResults } from '../components/NoResults'
const searchClient = algoliasearch(
process.env.NEXT_PUBLIC_ALGOLIA_APPLICATION_ID + '',
process.env.NEXT_PUBLIC_ALGOLIA_SEARCH_ONLY_API_KEY + ''
)
const Home: NextPage = () => {
return (
<div className={styles.Wrapper}>
// (中略)
<InstantSearch
indexName={process.env.NEXT_PUBLIC_ALGOLIA_PRIMARY_INDEX + ''}
searchClient={searchClient}
>
<header className={styles.Header}>
// (中略)
<h1>Static Site Generators 😉</h1>
<div className="ais-Search_Wrapper">
<SearchBox />
<span className="ais-Search_Icon">
<img src="/search.svg" alt="" width="19" height="19" />
</span>
<PoweredBy className="ais-Search_Logo" />
</div>
</header>
<div className={styles.Container}>
<main className={styles.Main}>
<NoResultsBoundary fallback={<NoResults />}>
<Hits hitComponent={Hit} />
</NoResultsBoundary>
</main>
</div>
// (中略)
</InstantSearch>
// (中略)
</div>
)
}
export default Home
// components/Hit.tsx
import type { Hit as HitType } from 'instantsearch.js'
import { Highlight } from 'react-instantsearch-hooks-web'
import { Generator } from '../types/generator'
export const Hit = ({ hit }: { hit: HitType & Generator }) => {
return (
<>
<div className="ais-Hits-item_Logo">
<img
src={hit.logo.src}
alt={hit.logo.fileName}
width="40"
height="40"
/>
</div>
<div className="ais-Hits-item_Data">
<div className="ais-Hits-item_Header">
<h2 className="ais-Hits-item_Name">
<a href={hit.url} rel="noreferrer noopener" target="_blank">
<Highlight attribute="title" hit={hit} />
</a>
</h2>
<p className="ais-Hits-item_URL">{hit.url}</p>
</div>
<p className="ais-Hits-item_Description">
<Highlight attribute="description" hit={hit} />
</p>
<div className="ais-Hits-item_Footer">
<div className="ais-Hits-item_Tags">
{hit.tags.map((tag: string) => {
return <span key={tag}>{tag}</span>
})}
</div>
<div className="ais-Hits-item_Star">
<img src="/star.svg" alt="" width="16" height="15" />
<span>{hit.star}</span>
</div>
</div>
</div>
</>
)
}
以上でシンプルな全文検索を行えるようになりました。
2. 全文検索をカスタマイズする
次に、全文検索のカスタマイズを行います。具体的には以下のことを行います。
- 検索に利用するフィールドの指定と優先順位付け
- デフォルトの並び順の指定
2-1. 検索に利用するフィールドの指定と優先順位付け
1では、すべてのフィールドを検索対象としていましたが、指定したフィールドのみが検索対象となるように設定を行います。
Algoliaでは、どのフィールドを検索対象に含めるか設定できるため、URLやロゴなど、表示のみに使用するフィールドは検索対象から除外することができます。
また、どのフィールドとマッチした場合に、関連性が高いと判断するか、明示的に指定できます。
ここでは、タイトル・タグ・説明のどれかと一致する場合、検索結果として表示することとし、優先順位は「タイトル > タグ > 説明」の順番とします。
検索対象の属性の指定は、ダッシュボード経由でもAPI経由でもできますが、ここではAPI経由で指定します。Next.jsのAPIルートを利用します。
API経由で指定する場合、インデックスに searchableAttributes という属性を設定します。
// api/algolia/setup.ts
import algoliasearch from 'algoliasearch'
import { NextApiRequest, NextApiResponse } from 'next'
const algolia = algoliasearch(
process.env.NEXT_PUBLIC_ALGOLIA_APPLICATION_ID + '',
process.env.ALGOLIA_ADMIN_API_KEY + ''
)
const primaryIndex = algolia.initIndex(
process.env.NEXT_PUBLIC_ALGOLIA_PRIMARY_INDEX + ''
)
export default async function handler(
req: NextApiRequest,
res: NextApiResponse
) {
try {
// 検索対象の指定
await primaryIndex.setSettings({
searchableAttributes: ['title', 'tags', 'description'],
})
res.status(200).json({ message: 'Success' })
} catch (err: any) {
res.status(400).json({ message: err?.message })
}
}
ローカル環境で、このメソッドを実行する場合、以下にリクエストを送ります。
http://localhost:3000/api/algolia/setup
データが連携されると、Algoliaの管理画面で Searchable attributes
が以下のように表示されます。
これで、検索対象フィールドの指定と、優先順位付けができました。
2-2. デフォルトの並び順の指定
続いて、デフォルトの並び順を指定します。これを指定することで、検索文字列が入力されていなかった場合や、同じ関連度の場合の並び順が決定します。
ここでは、スターの降順で表示することとします。
API経由で指定する場合、インデックスに customRanking という属性を設定します。
api/algolia/setup.ts
を以下のように修正します。
await primaryIndex.setSettings({
searchableAttributes: ['title', 'tags', 'description'],
+ customRanking: ['desc(star)'],
})
このメソッドを実行し、データが連携されると、Algoliaの管理画面の Ranking and Sorting
にカスタムランキングが追加されます。
これで、デフォルトの並び順が指定されました。
3. ソートを追加する
次に、ソートの機能を追加します。具体的には以下のことを行います。
- インデックスの追加(レプリカの作成と設定)
- ソートUIの追加
Algoliaでは検索結果を明示的にソートすることが可能です。
大きく2タイプのソートが提供されていて、Exhaustive sorting(指定された属性に基づく厳密なソート) と Relevant sorting(関連性によるソート) があります。
ここでは、Exhaustive sortingを利用して、指定された属性の値をもとに、ソートできるようにします。スターの降順ソートと、タイトルの昇順ソートを追加してみましょう。
最終的には、2で設定したソートに加え、以下の3つの選択肢からソート順を選べるようにします。
- Relevance(関連性によるソート。2で設定したもの)
- GitHub Stars(スターの降順)
- Title(タイトルの昇順)
3-1. インデックスの追加(レプリカの作成と設定)
Algoliaでは同じデータに対して、異なるランキングを提供する場合、それぞれ異なるインデックスを使用する必要があります。
追加されたインデックスは、レプリカと呼ばれます。
レプリカの作成・設定は、ダッシュボード経由でもAPI経由でもできますが、ここではAPI経由で指定します。Next.jsのAPIルートを利用します。
3-1-1. レプリカの作成
レプリカを作成するには、プライマリインデックスに setSettings メソッドを使用します。
レプリカにはstandard replicaとvirtual replicaの2種類がありますが、exhaustive sortingで利用するため、standard replicaとして作成します。
ここでは、スターの降順に並べるためのレプリカと、タイトルの昇順に並べるためのレプリカを定義します。
インデックスに replicas という属性を設定します。
api/algolia/setup.ts
を以下のように修正します。
await primaryIndex.setSettings({
searchableAttributes: ['title', 'tags', 'description'],
customRanking: ['desc(star)'],
+ replicas: [
+ process.env.NEXT_PUBLIC_ALGOLIA_REPLICA_INDEX_STAR + '',
+ process.env.NEXT_PUBLIC_ALGOLIA_REPLICA_INDEX_TITLE + '',
+ ],
})
また、環境変数にそれぞれのレプリカの名前を定義します。お好きな名前で定義して下さい(このチュートリアルでは generator_star
・generator_title
としています)。
NEXT_PUBLIC_ALGOLIA_REPLICA_INDEX_STAR=Algolia Replica Index (スター)
NEXT_PUBLIC_ALGOLIA_REPLICA_INDEX_TITLE=Algolia Replica Index (タイトル)
このメソッドを実行し、Algoliaの管理画面を確認すると、以下のようにインデックス(レプリカ)が追加されていることがわかります。
3-1-2. レプリカの設定
レプリカの設定を変更するには、以下の作業が必要です。
- レプリカの初期化
- setSettings を利用した設定変更
ここでは、スターの降順に並べるための replicaIndexStar
と、タイトルの昇順に並べるための replicaIndexName
を定義します。
それぞれ customRanking を利用して、カスタムランキングを設定します。
customRanking: ['desc(star)']
や customRanking: ['asc(title)']
のように指定します。
また ranking を利用して、ランキングの基準を設定します。
デフォルトでは、以下のようになっていますが、
ranking: [
'typo',
'geo',
'words',
'filters',
'proximity',
'attribute',
'exact',
'custom',
]
ここではカスタムランキングを優先するため、custom
を最上位に定義します。
ranking: [
+ 'custom',
'typo',
'geo',
'words',
'filters',
'proximity',
'attribute',
'exact',
- 'custom',
]
api/algolia/setup.ts
を以下のように修正します。
// api/algolia/setup.ts
import algoliasearch from 'algoliasearch'
import { NextApiRequest, NextApiResponse } from 'next'
const algolia = algoliasearch(
process.env.NEXT_PUBLIC_ALGOLIA_APPLICATION_ID + '',
process.env.ALGOLIA_ADMIN_API_KEY + ''
)
const primaryIndex = algolia.initIndex(
process.env.NEXT_PUBLIC_ALGOLIA_PRIMARY_INDEX + ''
)
+ const replicaIndexStar = algolia.initIndex(
+ process.env.NEXT_PUBLIC_ALGOLIA_REPLICA_INDEX_STAR + ''
+ )
+ const replicaIndexName = algolia.initIndex(
+ process.env.NEXT_PUBLIC_ALGOLIA_REPLICA_INDEX_TITLE + ''
+ )
export default async function handler(
req: NextApiRequest,
res: NextApiResponse
) {
try {
await primaryIndex.setSettings({
searchableAttributes: ['title', 'tags', 'description'],
customRanking: ['desc(star)'],
replicas: [
process.env.NEXT_PUBLIC_ALGOLIA_REPLICA_INDEX_STAR + '',
process.env.NEXT_PUBLIC_ALGOLIA_REPLICA_INDEX_TITLE + '',
],
})
+ await replicaIndexStar.setSettings({
+ searchableAttributes: ['title', 'tags', 'description'],
+ customRanking: ['desc(star)'],
+ ranking: [
+ 'custom',
+ 'typo',
+ 'geo',
+ 'words',
+ 'filters',
+ 'proximity',
+ 'attribute',
+ 'exact',
+ ],
+ })
+
+ await replicaIndexName.setSettings({
+ searchableAttributes: ['title', 'tags', 'description'],
+ customRanking: ['asc(title)'],
+ ranking: [
+ 'custom',
+ 'typo',
+ 'geo',
+ 'words',
+ 'filters',
+ 'proximity',
+ 'attribute',
+ 'exact',
+ ],
+ })
+
res.status(200).json({ message: 'Success' })
} catch (err: any) {
res.status(400).json({ message: err?.message })
}
}
このメソッドを実行し、Algoliaの管理画面を確認すると、レプリカインデックスの Ranking and Sorting
にカスタムランキングのみが設定されていることがわかります。
3-2. ソートUIの追加
SortBy を利用します。
3-1で追加したインデックスをSortByに渡します。
pages/index.tsx
を以下のように修正します。
// pages/index.tsx
import {
InstantSearch,
SearchBox,
Hits,
PoweredBy,
+ SortBy,
} from 'react-instantsearch-hooks-web'
// (中略)
const Home: NextPage = () => {
return (
// (中略)
<InstantSearch
indexName={process.env.NEXT_PUBLIC_ALGOLIA_PRIMARY_INDEX + ''}
searchClient={searchClient}
>
// (中略)
<div className={styles.Container}>
+ <nav className={styles.Nav}>
+ <h2>Sort</h2>
+ <SortBy
+ items={[
+ {
+ value: process.env.NEXT_PUBLIC_ALGOLIA_PRIMARY_INDEX + '',
+ label: 'Relevance',
+ },
+ {
+ value:
+ process.env.NEXT_PUBLIC_ALGOLIA_REPLICA_INDEX_STAR + '',
+ label: 'GitHub Stars',
+ },
+ {
+ value:
+ process.env.NEXT_PUBLIC_ALGOLIA_REPLICA_INDEX_TITLE + '',
+ label: 'Title',
+ },
+ ]}
+ />
+ </nav>
<main className={styles.Main}>
<NoResultsBoundary fallback={<NoResults />}>
<Hits hitComponent={Hit} />
</NoResultsBoundary>
</main>
</div>
// (中略)
</InstantSearch>
// (中略)
)
}
export default Home
これで、3種類のソート方法を選択できるようになりました。
4. ファセット検索を追加する
最後に、ファセット検索を追加します。具体的には以下のことを行います。
- ファセット検索で利用する属性の指定
- ファセット検索UIの追加
Algoliaのファセットを利用すると、選択した属性のグループに対してカテゴリーを作成し、ユーザーが検索結果を絞り込めるようになります。
ここでは、タグの情報をもとに、ユーザーが結果をフィルタできるようにします。
4-1. ファセット検索で利用する属性の指定
ファセット検索を利用するためには、事前に利用する属性を指定しておく必要があります。これは、ダッシュボード経由でもAPI経由でもできますが、ここではAPI経由で指定します。
attributesForFaceting を利用して、タグの情報がファセット検索で利用できるように指定します。
api/algolia/setup.ts
を以下のように修正します。
await primaryIndex.setSettings({
searchableAttributes: ['title', 'tags', 'description'],
customRanking: ['desc(star)'],
+ attributesForFaceting: ['tags'],
replicas: [
process.env.NEXT_PUBLIC_ALGOLIA_REPLICA_INDEX_STAR + '',
process.env.NEXT_PUBLIC_ALGOLIA_REPLICA_INDEX_TITLE + '',
],
})
await replicaIndexStar.setSettings({
searchableAttributes: ['title', 'tags', 'description'],
customRanking: ['desc(star)'],
+ attributesForFaceting: ['tags'],
ranking: [
'custom',
'typo',
'geo',
'words',
'filters',
'proximity',
'attribute',
'exact',
],
})
await replicaIndexName.setSettings({
searchableAttributes: ['title', 'tags', 'description'],
customRanking: ['asc(title)'],
+ attributesForFaceting: ['tags'],
ranking: [
'custom',
'typo',
'geo',
'words',
'filters',
'proximity',
'attribute',
'exact',
],
})
このメソッドを実行し、Algoliaの管理画面を確認すると、各インデックスの Facets
で tags
の属性が設定されていることがわかります。
4-2. ファセット検索UIの追加
RefinementList を利用します。
表示するファセットは最大10個、ファセットの順番は ['count:desc', 'name:asc']
とします。
pages/index.tsx
を以下のように修正します。
// pages/index.tsx
import {
InstantSearch,
SearchBox,
Hits,
PoweredBy,
SortBy,
+ RefinementList,
} from 'react-instantsearch-hooks-web'
// (中略)
const Home: NextPage = () => {
return (
// (中略)
<InstantSearch
indexName={process.env.NEXT_PUBLIC_ALGOLIA_PRIMARY_INDEX + ''}
searchClient={searchClient}
>
// (中略)
<div className={styles.Container}>
<nav className={styles.Nav}>
<h2>Sort</h2>
<SortBy
items={[
{
value: process.env.NEXT_PUBLIC_ALGOLIA_PRIMARY_INDEX + '',
label: 'Relevance',
},
{
value:
process.env.NEXT_PUBLIC_ALGOLIA_REPLICA_INDEX_STAR + '',
label: 'GitHub Stars',
},
{
value:
process.env.NEXT_PUBLIC_ALGOLIA_REPLICA_INDEX_TITLE + '',
label: 'Title',
},
]}
/>
+ <h2>Filter</h2>
+ <RefinementList
+ attribute={'tags'}
+ limit={10}
+ sortBy={['count:desc', 'name:asc']}
+ />
</nav>
<main className={styles.Main}>
<NoResultsBoundary fallback={<NoResults />}>
<Hits hitComponent={Hit} />
</NoResultsBoundary>
</main>
</div>
// (中略)
</InstantSearch>
// (中略)
)
}
export default Home
これで、ファセット検索ができるようになりました。
以上で、すべてのステップが終了となります。
ここまでで説明したコードは、すべて Newt-Inc/newt-algolia-nextjs に公開しています。
もし、どこかわかりにくいところがあれば、こちらのコードも参考にしていただければと思います。
Algoliaはここで紹介した以外にも様々な機能が充実しているので、ぜひ様々な機能を試してみてください。