mcpField
S
search-autocomplete

search-autocomplete

github.com/finsilabs開発6083 回インストール5 日前に更新
リポジトリ

search-autocomplete について

EC ストアに入力中の検索候補を実装するスキルです。既存またはカスタムのストアに検索候補を追加する開発者向けで、入力のデバウンス、あいまい検索、商品表示ルール、構造化されたドロップダウンを扱います。

主な機能

  1. 01検索リクエストのデバウンス
  2. 02あいまい検索と誤字対応
  3. 03商品とカテゴリの候補表示
  4. 04同義語と商品表示ルール
  5. 05アクセシブルなドロップダウン設計

活用シーン

  1. 01ストアへの入力中検索の追加
  2. 02誤字があっても商品を探せるようにする
  3. 03候補内で特定商品を優先表示
  4. 04多言語検索の同義語への対応

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/finsilabs/awesome-ecommerce-skills --skill search-autocomplete

インストール後、Claude Code で/search-autocompleteを入力して読み込まれたか確認します。

同梱ファイル

SKILL.md と一緒にインストールされる参考資料やスクリプト。

tile.json
evals/algolia-index-configuration-and-merchand/
criteria.json
task.md
evals/backend-autocomplete-endpoint/
criteria.json
task.md
evals/frontend-autocomplete-component/
criteria.json
task.md

詳細

検索オートコンプリート

概要

買い物客が入力するにつれて、商品、カテゴリー、コンテンツの候補を表示する検索サジェスト機能を実装します。クライアント側のデバウンス処理とサーバー側のあいまい検索を組み合わせ、マーチャンダイジングルール(ブースト、ピン留め、同義語)を適用して、コンバージョン率の向上を測定できる構造化されたドロップダウンを表示します。

このスキルを使用する場面

  • 買い物客がブラウズナビゲーションだけでは商品を見つけられない場合
  • サイト内検索のクリック率が検索数の30%を下回っている場合
  • 既存の検索エンドポイントに、入力中の検索体験を追加する場合
  • サードパーティーの検索サービス(Algolia、Elasticsearch、Typesense)を統合する場合
  • 検索結果でプロモーション対象の商品を上位表示するマーチャンダイジングルールを実装する場合
  • 同義語や音声的なマッチングを必要とする多言語ストアフロントをサポートする場合

基本手順

ステップ1:マーチャントのプラットフォームを特定し、適切な方法を選択する
プラットフォーム推奨される方法理由
ShopifySearch & Discovery アプリ(Shopify 提供、無料)で同義語やブーストを設定し、Searchie または Boost Commerce アプリで完全なオートコンプリートのドロップダウンを実装しますSearch & Discovery は同義語や商品のブーストにより、Shopify 組み込みの検索機能を強化します。Boost Commerce(月額19ドル)は、カテゴリー結果やマーチャンダイジングルールを備えた、スタイル設定済みのオートコンプリートのドロップダウンを追加します
WooCommerceFiboSearch – AJAX Search for WooCommerce(無料/有料)または SearchWP と SearchWP Live Search 拡張機能をインストールしますFiboSearch は、商品画像、価格、カテゴリー結果を含む即時の AJAX オートコンプリートのドロップダウンを WooCommerce の検索バーに追加します。カスタムコードは不要です
BigCommerceStorefront → Search 設定で Search Suggestions を有効にし、Klevu Smart Search または Searchspring をインストールしますBigCommerce のネイティブ検索には基本的なオートコンプリート機能があります。Klevu(月額449ドル以上)と Searchspring は、AI を活用したオートコンプリート、同義語管理、マーチャンダイジングルールを追加します
カスタム/ヘッドレスAlgolia InstantSearch.js(推奨)またはセルフホスト可能な Typesense を使用して構築し、デバウンス処理を行う入力欄、競合状態に対応する AbortController、ARIA コンボボックスパターンを実装しますAlgolia は、手厚い無料プラン(月間1万件の検索)を備え、開発者にとって最も優れた開発体験を提供します。Typesense はセルフホスト可能な代替サービスです
ステップ2:プラットフォームで検索オートコンプリートを設定する

Shopify

Search & Discovery アプリ(必須の基本設定 — 無料):

  1. Shopify App Store から Search & Discovery をインストールします
  2. Apps → Search & Discovery → Synonyms に移動し、ビジネスで使う同義語を追加します:
    • 双方向:「sneakers」 ↔ 「trainers」 ↔ 「shoes」
    • 一方向:「tv」 → 「television」、「flat screen」
  3. Boosts で、特定の検索語句に対して優先度の高い商品やコレクションをピン留めし、検索結果の先頭に表示します
  4. Filter settings で、検索結果とあわせてフィルターとして表示する属性を設定します
  5. このアプリは、OS2.0 テーマの検索バーで使われる Shopify ネイティブの予測検索 API を強化します

Boost Commerce アプリ(完全なオートコンプリートのドロップダウン):

  1. Shopify App Store から Boost Commerce – Product Filter & Search をインストールします
  2. アプリのダッシュボードで Instant Search ポップアップを設定します:
    • 候補に商品画像と価格を表示します
    • カテゴリー/コレクションの候補を追加します
    • 商品結果の件数を設定します(5~8件を推奨)
  3. アプリで Merchandising rules を設定します:新着商品をブーストし、ベストセラーをピン留めし、在庫切れの商品を候補から除外します
  4. Design 設定で、テーマの色に合わせてポップアップの外観をカスタマイズします

WooCommerce

FiboSearch(おすすめ — 無料プランあり):

  1. WordPress.org から FiboSearch – AJAX Search for WooCommerce をインストールします
  2. FiboSearch → Settings → General に移動します
  3. 候補に表示する内容を設定します:
    • Products:名前、SKU、タグ(最良の結果を得るにはすべて有効にします)
    • Categories:カテゴリの候補を表示するには有効にします
    • Pages/Posts:ブログコンテンツがある場合は有効にします
  4. Fuzzy Search をオンに設定します。これにより、「adids」→「Adidas」のような入力ミスに対応できます
  5. Appearance でドロップダウンのレイアウトを設定します:商品画像+名前+価格、またはテキストのみのコンパクト表示
  6. FiboSearch は WooCommerce のデフォルトの検索ウィジェットを置き換えます。標準の検索入力欄、Elementor の検索ウィジェット、ほとんどのテーマの検索バーで動作します

SearchWP + Live Search 拡張機能:

  1. 高度なインデックス制御のために SearchWP(有料、年間 $99 から)をインストールします
  2. リアルタイムの自動補完機能を利用するために SearchWP Live Search 拡張機能をインストールします
  3. SearchWP の管理画面で、インデックスに登録する商品フィールドと重みを設定します(名前 > SKU > 説明)
  4. 仕様に基づく検索のために、カスタムフィールドと商品属性をインデックスで有効にします

BigCommerce
  1. BigCommerce のコントロールパネルで Storefront → Search に移動します
  2. Search Suggestions で、候補の種類として Products、Categories、Brands を有効にします
  3. 表示する候補数を設定します(商品は 5~8 件)
  4. Search as you type を設定し、2 文字の入力後に検索を開始します

Klevu Smart Search(高度な自動補完):

  1. BigCommerce App Marketplace からインストールします
  2. Klevu ダッシュボードで、同義語グループとブーストルールを設定します
  3. Klevu の自動補完ドロップダウンには、商品画像、価格、カテゴリ、トレンドの検索語が自動的に表示されます
  4. 特定の検索語に対して検索ドロップダウンに表示するカスタムバナーを追加します(例:「dress」と検索したときに「Summer Sale」バナーを表示します)

カスタム / ヘッドレス

AbortController を使用したデバウンス入力フック(競合状態を防止):

// useSearchAutocomplete.js
import { useState, useEffect, useRef, useCallback } from 'react';

function debounce(fn, delay) {
  let timer;
  return (...args) => { clearTimeout(timer); timer = setTimeout(() => fn(...args), delay); };
}

export function useSearchAutocomplete(minChars = 2) {
  const [query, setQuery] = useState('');
  const [results, setResults] = useState({ products: [], categories: [], suggestions: [] });
  const [loading, setLoading] = useState(false);
  const abortRef = useRef(null);

  const fetchSuggestions = useCallback(
    debounce(async (q) => {
      if (q.length < minChars) { setResults({ products: [], categories: [], suggestions: [] }); return; }
      if (abortRef.current) abortRef.current.abort();
      abortRef.current = new AbortController();
      setLoading(true);
      try {
        const res = await fetch(`/api/search/autocomplete?q=${encodeURIComponent(q)}&limit=5`,
          { signal: abortRef.current.signal });
        setResults(await res.json());
      } catch (err) { if (err.name !== 'AbortError') console.error(err); }
      finally { setLoading(false); }
    }, 250),
    [minChars]
  );

  useEffect(() => { fetchSuggestions(query); }, [query, fetchSuggestions]);
  return { query, setQuery, results, loading };
}

アクセシブルなコンボボックスドロップダウン(ARIA の combobox + listbox パターン):

// SearchAutocomplete.jsx
import DOMPurify from 'dompurify'; // sanitize server-provided highlight HTML

export function SearchAutocomplete() {
  const { query, setQuery, results, loading } = useSearchAutocomplete();
  const [activeIndex, setActiveIndex] = useState(-1);
  const inputRef = useRef(null);
  const allItems = [...results.categories, ...results.products];
  const isOpen = query.length >= 2 && allItems.length > 0;

  function handleKeyDown(e) {
    if (e.key === 'ArrowDown') { e.preventDefault(); setActiveIndex(i => Math.min(i + 1, allItems.length - 1)); }
    if (e.key === 'ArrowUp') { e.preventDefault(); setActiveIndex(i => Math.max(i - 1, -1)); }
    if (e.key === 'Enter' && activeIndex >= 0) window.location.href = allItems[activeIndex].url;
    if (e.key === 'Escape') { inputRef.current.blur(); setActiveIndex(-1); }
  }

  return (
    <div role="combobox" aria-expanded={isOpen} aria-haspopup="listbox" aria-owns="autocomplete-list">
      <input ref={inputRef} type="search" value={query}
        onChange={e => { setQuery(e.target.value); setActiveIndex(-1); }}
        onKeyDown={handleKeyDown}
        aria-autocomplete="list" aria-controls="autocomplete-list"
        aria-activedescendant={activeIndex >= 0 ? `item-${activeIndex}` : undefined}
        placeholder="Search products..." />
      {loading && <span aria-live="polite" className="sr-only">Loading suggestions</span>}
      {isOpen && (
        <ul id="autocomplete-list" role="listbox" className="autocomplete-dropdown">
          {results.categories.map((cat, i) => (
            <li key={cat.url} id={`item-${i}`} role="option" aria-selected={activeIndex === i}>
              <a href={cat.url}>Category: {cat.name} ({cat.product_count})</a>
            </li>
          ))}
          {results.products.map((product, i) => {
            const idx = i + results.categories.length;
            const highlighted = DOMPurify.sanitize(product._highlightResult?.name?.value ?? product.name);
            return (
              <li key={product.objectID} id={`item-${idx}`} role="option" aria-selected={activeIndex === idx}>
                <a href={product.url} className="product-suggestion">
                  <img src={product.image} alt="" width="40" height="40" />
                  <span dangerouslySetInnerHTML={{ __html: highlighted }} />
                  <span>${product.price}</span>
                </a>
              </li>
            );
          })}
          <li><a href={`/search?q=${encodeURIComponent(query)}`}>View all results for "{query}"</a></li>
        </ul>
      )}
    </div>
  );
}

Algolia のインデックス設定(入力ミス許容 + 同義語 + マーチャンダイジング):

await searchClient.setSettings({
  indexName: 'products',
  indexSettings: {
    searchableAttributes: ['name', 'brand', 'category', 'description'],
    customRanking: ['desc(popularity_score)', 'desc(conversion_rate)'],
    typoTolerance: 'min',
    minWordSizefor1Typo: 5,
    synonyms: [
      { objectID: 'shoes', type: 'synonym', synonyms: ['shoes', 'sneakers', 'footwear', 'trainers'] },
    ],
    optionalFilters: ['is_featured:true<score=2>', 'in_stock:true<score=1>'],
  },
});

ベストプラクティス

  • 200~300 ms でデバウンスする — 応答性とサーバー負荷のバランスを取ります。150 ms 未満にはしないでください
  • 実行中のリクエストをキャンセルする — ユーザーが素早く入力した際の競合状態を避けるため、AbortController を使用します
  • 一致した語句をハイライトする — 一致した部分文字列を <mark> で囲み、検索結果が表示された理由を買い物客に示します。描画前にサーバーから提供された HTML をサニタイズしてください
  • 「すべての結果を表示」リンクを表示する — 検索結果ページ全体に移動できる手段を必ず用意します
  • 頻繁に使われる検索語をキャッシュする — ほとんどのストアでは使用頻度の高い検索語は少数です。LRU キャッシュにより、バックエンドの負荷を大幅に軽減できます
  • 結果がない検索語を追跡する — 結果が 0 件の検索語を記録します。これは同義語の不足やカタログの欠落を直接示すものです
  • minChars を 2 に設定する — 1 文字の検索語はノイズを生み、コンバージョンにつながりません

よくある落とし穴

問題解決策
ユーザーが素早く入力すると結果が古くなる新しいリクエストを発行する前に、AbortController を使って前のリクエストをキャンセルします
ドロップダウンが固定ヘッダーの背後に表示されるドロップダウンに z-index を明示的に設定します。overflow:hidden を指定した祖先要素の内側にある場合は、ポータルを使用します
再描画時にキーボードナビゲーションのフォーカスが失われるDOM フォーカスではなく、コンポーネントの状態で activeIndex を追跡します。各描画時に aria-activedescendant を再設定します
あいまい検索で無関係な結果が返されるAlgolia では minWordSizefor1Typo: 5、Elasticsearch では prefix_length: 2 を設定し、あいまい検索を開始する前に単語の語幹が十分に確立されるようにします
マーチャンダイジングルールが適用されないルールは検索語が条件パターンに一致すると実行されます。部分一致には is ではなく anchoring: 'contains' を使用します

関連スキル

  • @faceted-navigation
  • @product-page-design
  • @accessibility-commerce
  • @storefront-theming

コメント