notebook

都内でWEB系エンジニアやってます。

AlgoliaのInstantsearchでページ読み込み時の初回リクエストを送らないようにする

AlgoliaのInstantsearchを用いて検索UIを作っていたがページ読み込み時に空のリクエストが発生するらしくリクエストを消費させたくないと思ったので調べて対応してみた

Algoliaの料金体系

自分はFreeプランしか使っていないが

10000req/月もしくは10000Record/月までは無料で使える

ページ読み込み時にクエリが走る状態だと無駄にRequestを消費してしまう

たとえばPVが増えれば増えるほどリクエストが送られる状態になってしまう

まぁそんなにPVあるかと言われると微妙だが対応できるなら対応するにこしたことはない

Instantsearch

Algoliaが提供する検索UIを構築するためのライブラリ

基本的にAlgoliaを使うのであればUI側はInstantsearchを使って開発することになるはず

さまざまなコンポーネントが最初から用意されているので凝った使い方をしないならさっと検索UIを作成できる

そしてさらにReact、Angular、Vueなどそれぞれに対応できるよう別々にライブラリを提供している

Reactについては2種類あり、旧版のreact-instantsearch-dom、新版のreact-instantsearch-hooks-webがある

react-instantsearch-hooks-webの方は、名前の通りReact Hooksが使えるようになっている

React Instantsearch(旧)のドキュメントを見に行くと「新たに使う場合はhooks版を使ってください」と書いてあったので基本的にはhooksの方を使えば良さそう

今回はアナウンスに従いReact Instantsearch Hooksを使った

公式ドキュメント

Conditional requests with React InstantSearch Hooks | Algolia

https://www.algolia.com/doc/guides/building-search-ui/going-further/conditional-requests/react-hooks/www.algolia.com

前置きが長くなったが、掲題を実現するための方法は公式ドキュメントに載っている

英語だがかなり丁寧に解説してくれている

空のクエリを検知して空の場合のみダミーのレスポンスを返すようにsearchClientをラップする

例だとJavaScriptなのでTypeScriptの実装例として(型注釈入れただけ)残しておく

tsx(一部抜粋)

ペライチのページでコードを書いてみた

InfiniteHitsのコンポーネントは今回の話の対象ではないので割愛している

import type { NextPage } from "next";

import {
  MultipleQueriesResponse,
  MultipleQueriesQuery,
} from "@algolia/client-search";
import algoliasearch from "algoliasearch/lite";
import {
  InstantSearch,
  SearchBox,
  Index,
  PoweredBy,
  InfiniteHits,
} from "react-instantsearch-hooks-web";
import type { SearchClient } from "instantsearch.js";

const Search: NextPage = () => {
  const indices = (process.env.NEXT_PUBLIC_ALGOLIA_INDICES || "").split(",");

  const algoliaClient = algoliasearch(
    process.env.NEXT_PUBLIC_ALGOLIA_APP_ID || "",
    process.env.NEXT_PUBLIC_ALGOLIA_SEARCH_KEY || ""
  );

  const searchClient: SearchClient = {
    ...algoliaClient,
    search: <SearchResponse,>(requests: Readonly<MultipleQueriesQuery[]>) => {
      if (requests.every(({ params }) => !params?.query)) {
        return Promise.resolve<MultipleQueriesResponse<SearchResponse>>({
          results: requests.map(() => ({
            hits: [],
            nbHits: 0,
            nbPages: 0,
            page: 0,
            processingTimeMS: 0,
            hitsPerPage: 0,
            exhaustiveNbHits: true,
            query: "",
            params: "",
          })),
        });
      }

      return algoliaClient.search(requests);
    },
  };

  return (
    <InstantSearch searchClient={searchClient} indexName={indices[0]}>
      <SearchBox placeholder="Search"></SearchBox>
      {indices.map((index) => {
        return (
          <div key={index}>
            <Index key={index} indexName={index}>
              <h2>{index}</h2>
              <InfiniteHits
                hitComponent={PageHit}
              ></InfiniteHits>
            </Index>
          </div>
        );
      })}
      <PoweredBy />
    </InstantSearch>
  );
};

export default Search;

searchClient生成時にsearchプロパティだけ別の処理を差し込んでいる

params.queryにクエリ文字列が渡ってくるがクエリ文字列が空の場合はダミー用のレスポンスを返すようにしている

これを応用すれば他のパターンでもリクエスト送る前に独自の処理を差し込むことも可能である