検索
APIキー不要ですぐに使えるクライアントサイド検索。ドキュメントの成長に合わせて切り替えられる、ホスト型やセマンティック検索のアダプターも利用できます。
Blume は、ホストされたインフラも API キーも不要なローカル検索を標準搭載しています。ブラウザ内で動作し、blume dev と blume build の両方で機能し、実際のコンテンツのみをインデックス化します — ナビゲーションの装飾部分や除外されたページはスキップされます。ローカル検索では足りなくなったら、検索の見た目や挙動を変えることなく、ホスト型やセマンティックのバックエンドに切り替えられます — 変わるのは search に渡すアダプターだけです。
すべてのバックエンドは blume/search からインポートするアダプターです: Orama(デフォルト)、FlexSearch、Pagefind、Algolia、Orama Cloud、Typesense、Mixedbread。各アダプターは自身のオプション、ランタイム依存関係、必要なシークレットを管理します。Orama と Pagefind は Blume に同梱されています。それ以外のアダプターの SDK はオプションのピア依存関係で、自分でインストールする必要があるため、あるバックエンドを選んでも他のバックエンドが取り込まれることはありません。SDK がインストールされていない場合、blume build は Vite の実行前に停止し、そのパッケージ名とインストールするためのコマンドを表示します。blume doctor でも報告されます。
import { defineConfig } from "blume";
import { algolia } from "blume/search";
export default defineConfig({
search: algolia({
appId: "YOUR_APP_ID",
apiKey: "YOUR_SEARCH_ONLY_KEY",
indexName: "docs",
}),
});
アダプターはバックエンドを記述したプレーンな定義であり、稼働中のクライアントではありません — そのため Blume は、生成されるサイトやイジェクトしたプロジェクトにそれをインライン化できます。アダプターを直接渡すか、人気リンク、インデックス化、アナリティクスのオプションも併せて設定したい場合はオブジェクト形式を使ってください:
import { pagefind } from "blume/search";
search: {
provider: pagefind(),
popular: [{ href: "/guides/getting-started", icon: "rocket", label: "Getting started" }],
indexing: { includeCodeBlocks: true },
},
検索の使い方
ヘッダーの検索アイコンか ⌘K(または Ctrl K)で検索を開くか、入力フィールドにフォーカスしていないときに / を押します。Esc で閉じ、⌘J(または Ctrl J)で結果のプレビューペインを切り替えます。
クエリはページのタイトル、説明、本文にマッチし、タイトルへのマッチが最も高くランク付けされ、説明が本文より上位になります。
人気ページ
読者がクエリを入力する前に、検索ダイアログには人気リストが表示されます。デフォルトではサイドバーの最初の 6 ページですが、複数タブのサイトでは意図しないセクションが表示されがちです。代わりに表示したいリンクを固定しましょう:
search: {
popular: [
{ href: "/guides/getting-started", icon: "rocket", label: "Getting started" },
{ href: "/guides/install", icon: "download", label: "Install" },
{ href: "/concepts/overview", label: "Overview" },
],
},
各エントリは href(内部ルートまたは外部 URL)と label を取り、オプションで icon を指定できます — 組み込みアイコン名、画像パス/URL、またはインライン SVG(ナビアイコンと同じ入力形式)で、デフォルトはファイルのグリフです。popular を省略するか空のままにすると、サイドバーのフォールバックが維持されます。オブジェクト形式で provider を省略すると、デフォルトの Orama アダプターが使われます。
href はサイトがルートにマウントされているかのように書いてください — basePath は自動的に適用され、navigation.featured と同じ扱いです。外部 URL はそのまま通されます。
インデックス化される内容
Orama、FlexSearch、Algolia、Orama Cloud、Typesense — そして MCP サーバーの search_docs ツール — については、Blume は各ページのタイトル、説明、そしてプレーンテキストに変換された本文をインデックス化します: コードブロック、画像、マークアップは取り除かれるため、検索結果の関連性が保たれます。これらのインデックスはソースファイルから構築されるため、開発環境と本番環境で同一です。Pagefind は代わりにビルドされた HTML をインデックス化し、Mixedbread は生の Markdown を同期するため、どちらも常にコードも検索対象になります。
オプション名、メソッド名、エラー名といった検索可能な用語をコード例に頼っているドキュメントでは、フェンス付きコードをソースから構築されるインデックスにオプトインしてください:
search: {
indexing: {
includeCodeBlocks: true,
},
},
各フェンスの本文とタイトル(上記の blume.config.ts)が検索可能になります。言語やフェンスのマーカーは対象外です。.mdx ページでは、インデックスはコンポーネントを表示されるテキストとして読み取ります — Card のタイトル、Tab のラベル、TypeTable の説明 — これにはエージェント向けの出力と同じシリアライザーが使われるため、agents.markdownComponents のエントリは独自のコンポーネントにも適用されます。このオプションは Pagefind と Mixedbread には影響しません。フェンス付きコンテンツが増えるにつれてインデックスも大きくなることに注意してください — クライアントインデックスはすべての読者に配信され、ホスト型アダプターが保存する量も増え(Algolia の同期では長いページが複数のレコードに分割されます)、フェンス内でのヒットは検索結果の抜粋にフラット化されたコードとして表示されます。
バージョン管理されたサイトでは、結果はデフォルトで閲覧中のバージョンに絞り込まれ、ダイアログのフッターに「すべてのバージョン」の切り替えが表示されます(読者ごとに記憶されます)。他バージョンのヒットは行にそのバージョン名が表示されます。Orama、FlexSearch、Algolia、Typesense はこの絞り込みに対応しています — ホスト型のレコードは version ファセットを持ち、現在のドキュメントは "current" としてアップロードされます。Pagefind、Orama Cloud、Mixedbread はバージョンによる絞り込みを行いません: 結果はすべてのバージョンにまたがり、ダイアログには切り替えが表示されません。
i18n を設定したサイトでは、結果はデフォルトで閲覧中のページの言語に絞り込まれ、その横に「すべての言語」の切り替えが表示されます(こちらも読者ごとに記憶されます)。Orama と FlexSearch はブラウザ内でインデックスを絞り込み、Algolia、Orama Cloud、Typesense は各レコードが持つ locale で絞り込みます。Pagefind は言語ごとにインデックスを保持します: 閲覧中のページの言語のインデックスを検索し、「すべての言語」を選ぶと他の言語のインデックスも統合して、それらをページの言語の語幹処理で検索します。Mixedbread は言語による絞り込みを行いません: 結果はすべての言語にまたがり、ダイアログには切り替えが表示されません。
タグ
ページのフロントマターに search.tags を追加すると、ホスト型アダプター向けにそのページにラベルを付けられます。ページの最初のタグは、Algolia、Orama Cloud、Typesense のレコードに tag フィールドとしてアップロードされ、プロバイダーのダッシュボードや独自の検索 UI でそれを使って絞り込めます。Typesense は tag をファセットとして宣言します。Algolia では、インデックスの attributesForFaceting に自分で追加してください。同期後もその設定は維持されます。
search:
tags: [api, reference]
検索ダイアログはタグを読み取りません。ダイアログのフィルターは各ページが属するサイドバーのセクションごとに結果をグループ化し、インデックスに各ページのセクションが含まれる Orama と FlexSearch で表示されます。
ランキング
2 つのフロントマターフィールドで、ページのランク付けを調整できます。search.keywords には、ページの本文で使われていなくてもそのページがヒットするようにする追加の用語を列挙します。読者が旧名称、略語、競合製品の用語で探すページに適しています。search.boost はページの関連度に乗算されます: 1 より大きい値で上位に、1 より小さい値で下位に移動します。
search:
keywords: [install, setup, getting started]
boost: 3
ブーストは、クイックスタートや概要ページなど、読者が最も頻繁に必要とする少数のページに限定し、値は 2〜5 にしてください。レガシーなガイドのように、同程度にマッチする他のページより下位に表示すべきページには、0.5 のような 1 未満の値を使います。ブーストは 10 未満に抑えてください: それを超えるとブーストがページのマッチ度をかき消してしまい、ほとんどマッチしない検索でもそのページが最上位に来てしまいます。
ブーストによってページがどれだけ移動するかは、アダプターによって異なります:
- Orama(デフォルト)と FlexSearch は、並べ替えの前に各マッチにそのページのブーストを乗算するため、ブースト 3 はちょうど 3 倍として扱われます。MCP サーバーとアシスタントの検索も同じ方法でランク付けします。
- Pagefind はブーストされたページのテキストをより重く評価します。繰り返し出現するマッチは減衰されるため、上昇幅は乗数より小さくなります: ブースト 5 でページは上位になりますが、その効果は 2 倍程度にとどまります。Pagefind は 10 を超えるブーストを 10 として扱います。
- Algolia は、ほぼ同程度にマッチする結果の間でブーストによって並べ替えるため、ブーストは僅差の順位を決めるだけで、弱いマッチを強いマッチより上に持ち上げることはありません。同期によってインデックスのカスタムランキングに
desc(boost)が、自分で設定したランキングの後に追加されます。 - Typesense は関連度で並べ替えた後、関連度スコアが同じ結果の間でブーストによって並べ替えるため、ブーストは同点の順位を決めるだけで、弱いマッチを強いマッチより上に持ち上げることはありません。
- Orama Cloud はより広い範囲の結果を取得し、関連度にブーストを掛けた値で並べ替え直します。
- Mixedbread は自分で用意したストアを検索しますが、Blume はそこにアップロードしないため、ブーストとキーワードは反映されません。
キーワードは Mixedbread を除くすべてのアダプターで検索対象になります。
検索アナリティクス
アナリティクスアダプターを設定すると、読者が何を探しているかが検索から報告されます。クエリは確定した時点 — 1 秒間入力がなかったとき、または読者が結果を選択するかダイアログを閉じたとき — に、query、results の件数、検索が行われた path を含む search イベントとして記録されます。結果のないクエリは results: 0 として記録されるため、これで絞り込めばドキュメントがまだカバーしていない内容を一覧できます。結果を選択すると、クリックスルーの計測用に、query、1 から始まる結果の position、その url、選択が行われた path を含む search_select イベントが送信されます。
クエリは入力されたまま、最大 100 文字まで送信されます。アナリティクスからは他のページの検索語と同じように見えるため、読者による入力として扱ってください: 読者が検索ボックスにメールアドレスやキーを貼り付けることもありますが、Google Analytics と Plausible は個人データの送信を禁止しています。クエリのテキストをプロバイダーに送らないようにするには、これをオフにしてください:
search: {
analytics: { queries: false },
},
その場合、search と search_select イベントは query の代わりに、クエリの長さである queryChars を持ちます。テキストは blume:track DOM イベントでのみ、detail.props 内の query として送られるため、自分で書いたリスナーで、適切と判断した送信先に転送できます — アシスタントが質問を扱うのと同じ方法です。Cookie の同意を有効にしている場合は、アナリティクスを許可した読者のみがカウントされます。
アダプター
クライアントサイドのアダプターはキー不要で、オプションも必要ありません — クライアントは i18n から(Pagefind の場合はビルドされた HTML から)設定されるため、不明なキーは無視されるのではなく、設定の検証で拒否されます。ホスト型のアダプターは公開クレデンシャル(ブラウザに配布しても安全)を受け取り、ビルド時に環境変数からシークレットの管理キーを読み取ります — シークレットが設定ファイルやクライアントバンドルに含まれることはありません。ホスト型アダプターに渡したオプションはすべてそのまま保持され、ブラウザ内の検索クライアント(Mixedbread の場合は検索エンドポイント)に渡されるため、Blume が名前を定義していないオプションも SDK — Algolia の liteClient、Typesense の Client、または OramaClient — に届きます。これらのオプションは JSON 値でなければなりません — ディスクリプターはリテラルとして生成されるプロジェクトにインライン化されるため、関数、undefined、bigint は途中で消えてしまうのではなく、パス付きで設定の検証に失敗します。
Algolia、Orama Cloud、Typesense は、blume build のたびに最後にページをそれぞれのインデックスにアップロードします。管理キーが設定されていない場合、ビルドは警告を出してアップロードをスキップするため、ローカルビルドのようにシークレットのないビルドも問題なく動作します。キーが設定されていてアップロードが失敗した場合(キーの誤り、ホストに到達できない、レコードが拒否されたなど)、更新されていないインデックスのままデプロイするのではなく、ビルドが BLUME_SEARCH_SYNC_FAILED で失敗します。
Orama(デフォルト)
Blume のデフォルトエンジンです。/blume-search.json で配信される JSON インデックスを構築し、ブラウザ内でクエリを実行します — 即座に動作するクライアントサイド検索で、blume dev では編集に合わせてライブで更新されます。キーもサービスも不要です。search を完全に省略するとこれが選択されます。明示したい場合にのみ記述してください:
import { orama } from "blume/search";
search: orama(),
非ラテン文字の文字体系
標準トークナイザーは、ラテン文字と数字しか保持しないため、それ以外の文字体系のテキスト — 日本語、中国語、韓国語、タイ語はもちろん、同様にロシア語、ギリシャ語、ヘブライ語、ヒンディー語も — は本来まったくマッチしません。Blume はこれを自動的に処理します: 非ラテン文字の文字体系に解決されるロケールのページは、単語分割トークナイザー(ブラウザと Node にネイティブな Intl.Segmenter をベースに構築)でインデックス化されます。サイトの言語を宣言するだけで済みます:
i18n: {
defaultLocale: "ja",
locales: [{ code: "ja", label: "日本語" }],
}
同じトークナイザーが検索ダイアログ、MCP サーバーの search_docs ツール、アシスタントのグラウンディングにも使われます。判断の基準になるのは言語名ではなく文字体系です — az-Cyrl は分割され、sr-Latn は分割されません。デフォルトが非ラテン文字の場合、すべてのページがデフォルトロケールのトークナイザーを共有します: ラテン文字の単語は分割処理を経ても無傷で残るため、英語のページはデフォルト言語のページと並んで検索可能なままです。デフォルトがラテン文字の場合、ラテン文字のページは標準トークナイザーのままで、非ラテン文字の各翻訳にはそれぞれのロケールのトークナイザーが使われます。そのため、英語サイトの日本語やヒンディー語のページは、それぞれの言語からの検索でも、すべての言語を横断する検索でも見つかります。標準トークナイザーはアクセントを取り除き、取り除くアクセントを持たない文字は別の綴りに置き換えます(ø は o、ł は l、ß は ss に)。そのため cafe で café が、lodz で Łódź が見つかり、検索ダイアログやアシスタントの抜粋でも、ページ内のアクセント付きの単語が見つかります。アクセントによって単語が区別される場合(スウェーデン語の ändra と andra、ベトナム語の声調)、クエリはアクセントだけが異なる単語にもマッチします。
日本語と中国語はさらに一歩進んだ処理が行われます。分割だけでは複合語がその構成要素としてインデックス化されてしまいます — 資金決済法が 資金、決済、法 になる — その結果、各構成要素をどこかで言及しているだけのページが、その用語を実際に扱っているページより上位に来てしまいます。そのため、漢字、ひらがな、カタカナはオーバーラップする文字ペアとしてインデックス化され、これらのインデックスに対するクエリは、用語のペアをまとめて含むページを優先し、すべてのペアを含むページがない場合は任意のペアのマッチに緩和されます。これにより、文全体を入力しても最も近いページが返されます。韓国語とタイ語は分割された単語のまま維持されます。
FlexSearch
キー不要のクライアントサイドのもう 1 つの選択肢です(flexsearch をインストールしてください)。Orama が配信するのと同じ /blume-search.json インデックスを再利用し、ブラウザ内で FlexSearch のドキュメントインデックスを構築します。blume dev と blume build の両方で動作します。
FlexSearch には同等の分割フックがないため、非ラテン文字の文字体系のサイトでは、Orama(デフォルト)か Pagefind を選ぶことをおすすめします。Pagefind の pagefind_extended バイナリは幅広い言語をインデックス化し、中国語、日本語、韓国語をネイティブに分割します。
import { flexsearch } from "blume/search";
search: flexsearch(),
Pagefind
非常に大規模なドキュメントには Pagefind を選択できます。ビルドされた HTML をインデックス化し、インデックスをシャード単位でオンデマンドに読み込むため、サイトがどれだけ大きくなっても初期ペイロードは小さいまま保たれます。
import { pagefind } from "blume/search";
search: pagefind(),
Pagefind は blume build の実行中のみ動作するため、このアダプターでは blume dev で検索を利用できません。各ドキュメントページの記事部分のみをインデックス化し、その周囲のヘッダー、サイドバー、フッターは対象外です — そのため、PageLayout で構築したカスタムページ、404 ページ、生成された変更履歴のインデックスは検索結果に含まれません。
Algolia
algoliasearch をインストールしてください。ブラウザが検索専用キーで Algolia に直接クエリを実行します。blume build のたびに、ALGOLIA_ADMIN_API_KEY の管理キーを使ってインデックスが置き換えられます(未設定の場合、ビルドは警告を出してアップロードをスキップします)。同期のたびにインデックス全体が置き換えられるため、削除やリネームしたページが古い検索結果として残ることはありません。indexName という名前のインデックスがまだ存在しない場合は同期によって作成されるため、名前を打ち間違えると、ビルドが失敗するのではなく 2 つ目のインデックスが作成されます。同期は Algolia がキューに入れた各インデックス化タスクを最大で約 7 分間待機し、それまでに完了しなかったタスクがあるとビルドが BLUME_SEARCH_SYNC_FAILED で失敗します。また同期では、自分で宣言したファセットを維持したまま、インデックスの attributesForFaceting に filterOnly(locale) と filterOnly(version) も追加されます。ダイアログは言語とドキュメントのバージョンで結果を絞り込みますが、Algolia ではファセット用に宣言されていない属性に対するフィルターは何にもマッチしないためです。
Algolia の Build プランと Grow プランではレコードの上限が 10 KB のため、同期ではそれより長いページを複数のレコードに分割します。各レコードには、ページのタイトル、説明、URL と本文の一部が含まれます。さらに、自分で設定していない限り、インデックスの attributeForDistinct を url に設定するため、分割されたページは検索結果に 1 回だけ、最もよくマッチするレコードの位置に表示されます。
import { algolia } from "blume/search";
search: algolia({
appId: "YOUR_APP_ID",
apiKey: "YOUR_SEARCH_ONLY_KEY", // public
indexName: "docs",
}),
Orama Cloud
ホスト型の Orama です(@oramacloud/client をインストールしてください)。ブラウザは公開 API キーを使ってインデックスのエンドポイントにクエリを実行し、blume build は ORAMA_PRIVATE_API_KEY を使ってレコードをインデックスにプッシュします。同期を有効にするには indexId を設定してください。設定しない場合、キーが設定されていてもビルドは警告を出して同期をスキップします。
import { oramaCloud } from "blume/search";
search: oramaCloud({
endpoint: "https://cloud.orama.run/v1/indexes/your-index",
apiKey: "YOUR_PUBLIC_API_KEY",
indexId: "your-index-id", // for the build-time sync
}),
Typesense
セルフホストまたはクラウドの Typesense です。typesense をインストールしてください。ブラウザは検索専用キーでコレクションにクエリを実行します。blume build のたびに、TYPESENSE_ADMIN_API_KEY を使ってページを新しいコレクションにインポートし、collection の名前を持つエイリアスをそのコレクションに向けてから、置き換えられたコレクションを削除します。新しいコレクションが完成するまで検索は以前のコレクションを読み続け、削除やリネームしたページが古い検索結果として残ることもありません。同期が失敗した場合はビルドも失敗し、エイリアスは最後に完了した同期の内容を提供し続けます。管理キーにはコレクションとエイリアスを管理する権限が必要です。最初の同期では、すでに collection の名前を持つコレクション(以前のバージョンの Blume で作成されたもの)がエイリアスに置き換えられ、その名前にスコープされた検索キーは引き続き機能します。コレクションに手動で調整した設定は次のコレクションに引き継がれないため、ビルド後に再適用してください。
import { typesense } from "blume/search";
search: typesense({
host: "xyz.a1.typesense.net",
collection: "docs",
apiKey: "YOUR_SEARCH_ONLY_KEY", // public
// port + protocol default to 443 / https
}),
Typesense のデフォルトのトークナイザーはスペースでテキストを分割するため、中国語、日本語、タイ語では、クエリはひと続きのテキストの先頭からしかマッチしません。locale にページの言語を 2 文字のコード(locale: "ja"、"zh"、"th")で設定すると、同期の際に検索対象のフィールドがその言語向けにトークン化されます。それらのフィールド内のラテン文字の単語も引き続きマッチします。1 つのコレクションにすべての言語が格納されるため、この設定はすべての言語に適用されます。
Mixedbread
Mixedbread によるセマンティック検索です(@mixedbread/sdk をインストールしてください)。クエリはキーを保持する生成された /api/search エンドポイントを経由してプロキシされるため、このアダプターはサーバー出力が必須です: blume/deploy の deployment: vercel() のようなホストアダプターを使用してください(サーバーレンダリングを参照)。エンドポイントは MIXEDBREAD_API_KEY を読み取ります。Blume はストアにアップロードしません: ビルドの一環として Mixedbread CLI でコンテンツフォルダーを同期してください。例: mxbai store sync <STORE_ID> ./docs --yes(CLI は MXBAI_API_KEY からキーを読み取ります)。
各結果は、CLI がファイルについて記録したパスをもとに、そのファイルがレンダリングするページにリンクします。そのため、同期はプロジェクトのルート、またはモノレポのルートのようなその上位のフォルダーから実行してください。1 つのページは最もよくマッチする箇所で 1 回だけ表示され、ページではないファイルや検索から除外されたページの箇所は結果に含まれません。
import { mixedbread } from "blume/search";
search: mixedbread({
storeId: "YOUR_STORE_ID",
}),
検索ダイアログは、読者が入力を止めるまで待ってからクエリを送信し、読者がその後も入力を続けたクエリは破棄します。送信された各クエリは、エンドポイントのレート制限にカウントされます。
検索の無効化
search: false,
indexing を MCP サーバーのインデックスには引き続き適用したい場合は、代わりにオブジェクト形式で provider: false を設定してください。
ページの除外
インデックス可能なページのみが検索対象になります。フロントマターで search.exclude を設定したページはインデックスから除外されます:
search:
exclude: true
非表示ページもデフォルトで除外されます。それでもインデックス化したい場合は、オプトインしてください:
search: {
indexing: { includeHiddenPages: true },
}