Claude CodeでAlgolia検索を実装する実践ガイド
Claude CodeでAlgolia検索を実装する手順を、設計・APIキー・UI・分析まで実例コード付きで解説します。
Claude CodeでAlgolia検索を作る前に決めること
Algoliaは、検索専用のインデックスにデータを送り、ミリ秒単位のレスポンスで候補を返す検索SaaSです。RDBのLIKE検索で十分な段階もありますが、表記ゆれ、絞り込み、ランキング、クリック分析まで必要になると、アプリ側だけで作り込むほど保守が重くなります。
Claude Codeを使う価値は、単にコードを生成することではありません。既存のDBスキーマ、画面、権限、ログを読ませたうえで、検索レコードの形、インデックス設定、同期処理、UI、レビュー観点を一気通貫でそろえられる点にあります。ただし、Algoliaのキー設計やインデックス対象を間違えると、便利な検索機能が情報漏えいの入口になります。
この記事では、2026年6月時点のAlgolia JavaScript API Client v5を前提に、コピーして試せる実装例を載せます。v5では古いinitIndexパターンではなく、client.saveObjectsやclient.searchSingleIndexのように、クライアントにindexNameを渡す形へ移っています。公式の更新点はJavaScript API Client v5とAPI clientsも確認してください。
使いどころを3つに分ける
最初にユースケースを分けると、インデックス設計で迷いにくくなります。
| ユースケース | 典型データ | 重要な設定 | 注意点 |
|---|---|---|---|
| ドキュメント検索 | 記事、見出し、本文、タグ | searchableAttributes、同義語、ハイライト | 下書きや社内メモを混ぜない |
| EC・教材カタログ | 商品名、カテゴリ、価格、在庫、人気度 | facets、customRanking、Insights | 価格・在庫の同期遅延を監視する |
| 社内ナレッジ検索 | FAQ、チケット、設計メモ | secured API key、filters、権限フィールド | 個人情報と秘密情報を入れない |
ClaudeCodeLabなら、公開記事の検索、研修教材の検索、テンプレート販売ページの検索を同じ考え方で作れます。検索UIだけを急いで作るのではなく、「誰に、どの範囲を、どの順番で見せるか」を先に決めるのが安全です。
レコード設計は検索用に削る
Algoliaに送るレコードは、DBの行を丸ごとコピーしません。検索結果に必要な公開情報と、ランキング・絞り込みに必要な最小限の属性だけにします。email、住所、決済ID、内部メモ、未公開本文、APIレスポンスの生データは入れないでください。
{
"objectID": "article_ja_claude-code-algolia-search",
"title": "Claude CodeでAlgolia検索を実装する実践ガイド",
"summary": "インデックス設計、検索UI、分析、レビューまでをまとめた実装ガイド",
"content": "公開済み本文から抽出した検索対象テキスト",
"locale": "ja",
"section": "blog",
"category": "use-cases",
"tags": ["Claude Code", "Algolia", "全文検索"],
"visibility": "public",
"allowedTeams": [],
"slug": "claude-code-algolia-search",
"url": "/blog/claude-code-algolia-search",
"publishedAt": "2025-11-15",
"updatedAt": "2026-06-01",
"updatedAtTimestamp": 1780272000,
"popularity": 42,
"conversionScore": 7,
"readingMinutes": 12,
"thumbnail": "/images/hero/hero-090.png"
}
objectIDは安定させます。URL変更やタイトル変更のたびにIDが変わると、クリック分析とランキング改善の履歴が切れます。公開記事ならarticle_ja_slug、商品ならproduct_12345のように、元データの主キーとロケールを組み合わせると扱いやすいです。
インデックス設定と投入スクリプト
以下はNode.jsで動く最小構成です。npm install algoliasearch@5 dotenvを済ませ、.envにALGOLIA_APP_IDとALGOLIA_ADMIN_KEYを置いて実行します。管理キーは必ずサーバー側だけで使います。
// scripts/index-articles.ts
import "dotenv/config";
import { algoliasearch } from "algoliasearch";
type SearchRecord = {
objectID: string;
title: string;
summary: string;
content: string;
locale: "ja" | "en";
section: "blog" | "docs" | "product";
category: string;
tags: string[];
visibility: "public" | "restricted";
allowedTeams: string[];
slug: string;
url: string;
publishedAt: string;
updatedAt: string;
updatedAtTimestamp: number;
popularity: number;
conversionScore: number;
readingMinutes: number;
thumbnail: string;
};
const appId = process.env.ALGOLIA_APP_ID;
const adminKey = process.env.ALGOLIA_ADMIN_KEY;
const indexName = process.env.ALGOLIA_INDEX_NAME ?? "claudecodelab_articles";
if (!appId || !adminKey) {
throw new Error("ALGOLIA_APP_ID and ALGOLIA_ADMIN_KEY are required");
}
const client = algoliasearch(appId, adminKey);
const records: SearchRecord[] = [
{
objectID: "article_ja_claude-code-algolia-search",
title: "Claude CodeでAlgolia検索を実装する実践ガイド",
summary: "インデックス設計からレビューまでを扱う実践記事",
content: "公開本文から抽出した検索対象テキストだけを入れます。",
locale: "ja",
section: "blog",
category: "use-cases",
tags: ["Claude Code", "Algolia", "全文検索"],
visibility: "public",
allowedTeams: [],
slug: "claude-code-algolia-search",
url: "/blog/claude-code-algolia-search",
publishedAt: "2025-11-15",
updatedAt: "2026-06-01",
updatedAtTimestamp: 1780272000,
popularity: 42,
conversionScore: 7,
readingMinutes: 12,
thumbnail: "/images/hero/hero-090.png"
}
];
await client.setSettings({
indexName,
indexSettings: {
searchableAttributes: [
"unordered(title)",
"unordered(summary)",
"content",
"tags",
"category"
],
attributesForFaceting: [
"filterOnly(visibility)",
"filterOnly(locale)",
"filterOnly(allowedTeams)",
"searchable(category)",
"searchable(tags)",
"section"
],
customRanking: [
"desc(conversionScore)",
"desc(popularity)",
"desc(updatedAtTimestamp)"
],
attributesToRetrieve: [
"title",
"summary",
"locale",
"section",
"category",
"tags",
"url",
"updatedAt",
"thumbnail"
],
attributesToHighlight: ["title", "summary", "content"],
typoTolerance: true,
removeWordsIfNoResults: "lastWords"
}
});
await client.saveSynonyms({
indexName,
synonymHit: [
{
objectID: "claude-code-names",
type: "synonym",
synonyms: ["Claude Code", "claude code", "クロードコード"]
},
{
objectID: "search-ja",
type: "synonym",
synonyms: ["検索", "全文検索", "サイト内検索"]
}
],
clearExistingSynonyms: true
});
const { taskID } = await client.saveObjects({
indexName,
objects: records
});
await client.waitForTask({ indexName, taskID });
console.log(`Indexed ${records.length} records into ${indexName}`);
searchableAttributesは、上にある属性ほど強く評価されます。タイトル、要約、本文、タグの順にしておくと、本文にたまたま含まれる語よりも、タイトルに明示された語を上位に出しやすくなります。attributesForFacetingは絞り込み対象です。内部権限に使う属性はfilterOnlyにして、ユーザーが検索候補として拾わないようにします。
検索APIとsecured API key
公開検索だけならブラウザにsearch-only keyを置けます。管理キー、書き込みキー、Analytics管理キーは絶対にブラウザへ出しません。ログインユーザーごとに検索範囲を絞る場合は、サーバー側でsecured API keyを生成します。Algoliaのsecured API keyは親キーから派生する仮想キーで、制約をユーザー側で外せないようにするためにサーバーで作ります。詳しくはAPI keysを確認してください。
// app/api/search-key/route.ts
import { algoliasearch } from "algoliasearch";
import { NextResponse } from "next/server";
const appId = process.env.ALGOLIA_APP_ID!;
const searchKey = process.env.ALGOLIA_SEARCH_KEY!;
const indexName = process.env.ALGOLIA_INDEX_NAME ?? "claudecodelab_articles";
export async function GET() {
const user = { id: "user_123", teamIds: ["training"] };
const client = algoliasearch(appId, searchKey);
const securedApiKey = client.generateSecuredApiKey({
parentApiKey: searchKey,
restrictions: {
restrictIndices: indexName,
filters: `visibility:public OR allowedTeams:${user.teamIds[0]}`,
userToken: user.id,
validUntil: Math.floor(Date.now() / 1000) + 60 * 30
}
});
return NextResponse.json({ appId, indexName, apiKey: securedApiKey });
}
サーバー経由で検索結果だけ返す構成にしたい場合は、次のようなエンドポイントにします。入力を短く制限し、clickAnalyticsを有効にしてqueryIDを返すと、後続のクリック計測に使えます。検索APIの基本はSearch an indexが公式リファレンスです。
// app/api/search/route.ts
import { algoliasearch } from "algoliasearch";
import { NextRequest, NextResponse } from "next/server";
const client = algoliasearch(
process.env.ALGOLIA_APP_ID!,
process.env.ALGOLIA_SEARCH_KEY!
);
const indexName = process.env.ALGOLIA_INDEX_NAME ?? "claudecodelab_articles";
export async function GET(request: NextRequest) {
const query = request.nextUrl.searchParams.get("q")?.slice(0, 80) ?? "";
const locale = request.nextUrl.searchParams.get("locale") ?? "ja";
const result = await client.searchSingleIndex({
indexName,
searchParams: {
query,
filters: `visibility:public AND locale:${locale}`,
hitsPerPage: 10,
attributesToRetrieve: ["title", "summary", "url", "category", "tags"],
clickAnalytics: true
}
});
return NextResponse.json({
hits: result.hits,
queryID: result.queryID,
nbHits: result.nbHits
});
}
InstantSearchでUIを作る
検索UIは、Algoliaのウィジェットを組み合わせると短く作れます。InstantSearch.jsは、検索ボックス、絞り込み、ページネーション、ハイライトなどを組み合わせるための公式UIライブラリです。
// components/ArticleSearch.tsx
"use client";
import { liteClient as algoliasearch } from "algoliasearch/lite";
import {
Configure,
Highlight,
Hits,
InstantSearch,
Pagination,
RefinementList,
SearchBox,
Stats
} from "react-instantsearch";
type HitProps = {
hit: {
objectID: string;
title: string;
summary: string;
url: string;
category: string;
tags: string[];
updatedAt: string;
};
};
const searchClient = algoliasearch(
process.env.NEXT_PUBLIC_ALGOLIA_APP_ID!,
process.env.NEXT_PUBLIC_ALGOLIA_SEARCH_KEY!
);
function HitCard({ hit }: HitProps) {
return (
<article className="rounded border p-4">
<a href={hit.url} className="font-bold">
<Highlight attribute="title" hit={hit} />
</a>
<p className="mt-2 text-sm text-gray-600">
<Highlight attribute="summary" hit={hit} />
</p>
<p className="mt-2 text-xs text-gray-500">
{hit.category} ・ {hit.updatedAt}
</p>
</article>
);
}
export function ArticleSearch() {
return (
<InstantSearch searchClient={searchClient} indexName="claudecodelab_articles">
<Configure
hitsPerPage={8}
filters="visibility:public AND locale:ja"
clickAnalytics
/>
<SearchBox placeholder="Claude Codeの記事を検索" />
<Stats />
<div className="mt-6 grid gap-6 md:grid-cols-[220px_1fr]">
<aside>
<h2 className="text-sm font-bold">カテゴリ</h2>
<RefinementList attribute="category" searchable />
<h2 className="mt-4 text-sm font-bold">タグ</h2>
<RefinementList attribute="tags" searchable />
</aside>
<main>
<Hits hitComponent={HitCard} />
<Pagination className="mt-6" />
</main>
</div>
</InstantSearch>
);
}
Insightsとレビューで改善する
検索は作って終わりではありません。検索語、0件検索、クリック位置、コンバージョンを見て、ランキングや同義語を直す運用が必要です。clickAnalyticsを有効にした検索結果にはqueryIDが含まれるので、クリックイベントと結びつけます。
// lib/search-insights.ts
import aa from "search-insights";
aa("init", {
appId: process.env.NEXT_PUBLIC_ALGOLIA_APP_ID!,
apiKey: process.env.NEXT_PUBLIC_ALGOLIA_SEARCH_KEY!,
useCookie: true
});
export function trackSearchClick(params: {
indexName: string;
objectID: string;
queryID: string;
position: number;
}) {
aa("clickedObjectIDsAfterSearch", {
eventName: "Article Clicked",
index: params.indexName,
queryID: params.queryID,
objectIDs: [params.objectID],
positions: [params.position]
});
}
Claude Codeには、検索ログをそのまま渡すより、目的と評価基準を明示したプロンプトを使います。harnessのような専門語は「エージェントの足場」のように言い換えを指定すると、レビューの粒度がそろいます。
あなたはClaudeCodeLabの検索品質レビュアーです。
Algoliaの検索ログ、0件検索、上位10件、クリック率を見て、
次の形式で改善案を出してください。
前提:
- 対象読者はClaude Code初心者から実務導入担当者
- private fieldsはインデックスに追加しない
- 変更案はAlgolia settings、synonyms、record content、UIの4分類に分ける
出力:
| query | 問題 | 原因 | 変更案 | リスク | 優先度 |
特に見てほしい点:
- 期待記事が3位以内に入っているか
- 同義語で解決すべきか、本文やタイトルを直すべきか
- facetが多すぎて初心者を迷わせていないか
- 研修、テンプレート、相談への導線が検索意図に合っているか
失敗しやすい落とし穴
一つ目は、管理キーをフロントエンドに置くことです。NEXT_PUBLIC_を付けた環境変数はブラウザに出ます。ブラウザに置けるのはsearch-only keyか、サーバーで生成したsecured API keyだけです。
二つ目は、DBの全カラムを入れることです。検索に不要な秘密情報を入れると、UIで表示していなくても検索APIから取得される可能性があります。attributesToRetrieveを絞り、投入前の変換で不要属性を削ります。
三つ目は、ランキングを感覚で固定することです。最初はtitle優先、conversionScoreとpopularityで補正し、Insightsの数字を見て毎週見直すほうが安全です。
四つ目は、同義語を増やしすぎることです。「AI」「Claude」「ChatGPT」などを広く結びすぎると、検索意図がぼやけます。同義語は0件検索や表記ゆれの証拠があるものから追加します。
五つ目は、投入後にwaitForTaskを待たずに検証することです。反映前の状態で「検索できない」と判断すると、不要な修正を重ねてしまいます。
ClaudeCodeLabの導線に組み込む
検索結果は収益導線にも影響します。「Claude Code Algolia」で来た読者には実装記事を、「CLAUDE.md テンプレート」で来た読者にはCLAUDE.mdテンプレートを、「導入ルール」で来た読者には研修・相談を出すほうが自然です。検索機能の基礎は検索機能実装ガイド、速度面はパフォーマンス最適化も内部リンクとしてつなげます。
ClaudeCodeLabでは、検索導線の設計、Claude Code研修、プロンプト・CLAUDE.mdテンプレート、実装相談をまとめて扱えます。自社サイトや社内ポータルにAlgolia検索を入れるなら、最初に「公開してよい属性」「検索順位の評価軸」「問い合わせにつながる検索語」を棚卸ししてから実装すると、後戻りが少なくなります。
まとめ
Claude CodeとAlgoliaを組み合わせると、検索UIだけでなく、インデックス設計、APIキー管理、投入パイプライン、同義語、facets、Insights、レビュー運用まで一つの開発サイクルにできます。重要なのは、検索対象を削ること、キーを分けること、ログを見て改善することです。
この記事で紹介した内容を実際に試した結果、最初にレコード設計とattributesToRetrieveを絞った構成ほど、UI実装後の手戻りが少なくなりました。特にClaude Codeに検索ログを渡してレビューさせる流れは、0件検索の改善、同義語の追加、研修・テンプレート・相談ページへの導線見直しを同じ週次作業にまとめられるので、AdSense向けの記事品質改善にも相性が良いと感じました。
無料PDF: Claude Code はじめてのチートシート
まずは無料PDFで基本コマンドと最初の使い方をまとめて確認してください。登録後はそのままテンプレート集や導入相談にも進めます。
スパムは送りません。登録情報は厳重に管理します。
Claude Codeを仕事で使える形にしませんか?
無料PDFで基礎を固めたあと、すぐ使えるテンプレート集で試し、必要なら業務自動化や導入相談まで進められます。
この記事を書いた人
Masa
Claude Codeの実務活用、導入設計、収益導線改善を検証しているエンジニア。10言語の技術メディアを運営中。
関連書籍・参考図書
この記事のテーマに関連する書籍を楽天ブックスで探せます。
※ 当サイトは楽天市場のアフィリエイトプログラムに参加しています。上記リンクから商品をご購入いただくと、運営者に紹介料が支払われる場合があります。
関連記事
ObsidianメモをCLAUDE.mdに変えるClaude Code運用: 文脈を毎回説明しない仕組み
Obsidianの作業メモからCLAUDE.md用の運用ノートを作り、Claude Codeに安定した文脈を渡す方法。
Claude Code Revenue CTA Routing: 記事からPDF、Gumroad、相談へ送る設計
PVだけで終わらせず、読者の状態に合わせて無料PDF、Gumroad教材、導入相談へ分岐するCTA設計です。
Claude Codeチーム引き継ぎルール: レビュー、権限、収益導線まで渡す実務手順
Claude Codeの作業をチームで渡すための証拠、権限、ロールバック、無料PDF/Gumroad/相談導線の実務ルール。