Suchfunktion mit Claude Code implementieren: Postgres, Meilisearch und Algolia
Praxisleitfaden für Suche mit Claude Code: Anforderungen, Index, Sync-Job, Filter, Debounce-UI, Tests und Rollout.
Suche ist mehr als ein Eingabefeld
Eine Suchfunktion ist die Erfahrung, bei der eine Nutzereingabe passende Kandidaten findet und mit Filtern, Sortierung und Hervorhebung zurückgibt. Ein simplesLIKE '%term%' reicht vielleicht für eine interne Tabelle, aber nicht für ein Content-Portal, einen Kurskatalog oder eine SaaS-Dokumentation. Gute Suche erhöht Seitenaufrufe, bringt ältere Evergreen-Inhalte zurück und macht Null-Treffer-Suchen zu Themenideen.
Masas wichtigste Erfahrung aus Suchprojekten: Wer zuerst die Oberfläche baut, zahlt später mit Nacharbeit. Vor dem UI müssen indexierte Felder, öffentliche und private Daten, Locale-Regeln, Ranking, Sync-Jobs und Auswertung definiert sein. Claude Code schreibt schnell Code, aber nur ein präziser Auftrag führt zu einer stabilen Suche.
Ergänzend passen Claude Code Algolia Search, Claude Code API Development und Claude Code Performance Optimization.
Die wichtigsten Use Cases
| Use Case | Beispiele | Wichtig | Gute Wahl |
|---|---|---|---|
| Content- und Dokumentsuche | Blog, FAQ, Guides | Titelgewichtung, Snippets, Tags, Locale | Postgres Volltext oder Meilisearch |
| Katalogsuche | Produkte, Kurse, Templates | Facetten, Sortierung, Synonyme, Analytics | Meilisearch oder Algolia |
| Admin-Suche | Kunden, Rechnungen, Logs | Rechte, exakte Filter, Auditierbarkeit | Postgres zuerst |
| Mehrsprachige Suche | lokalisierte Artikel | Locale-Trennung, lokale Keywords | Meilisearch oder Algolia |
Für kleine Datenmengen und komplexe Berechtigungen ist Postgres oft der beste Start. Meilisearch lohnt sich, wenn Tippfehler, Facetten und bessere Standardrelevanz wichtig werden. Algolia ist sinnvoll, wenn Suche direkt mit Umsatz, Leads oder Kursverkäufen verbunden ist.
Prompt für Claude Code
Implementiere Production Search in einer bestehenden Next.js App.
Ziele:
- Veröffentlichte Artikel durchsuchen und Content-Discovery erhöhen.
- Query, Locale, Category und Tags als Filter unterstützen.
- Title, Summary, Tags und Body durchsuchen, Title am stärksten gewichten.
- Ergebnisdaten für Cards und Highlighting zurückgeben.
Constraints:
- Keine Drafts, Private Records, E-Mails, internen Notizen oder Restricted Content zurückgeben.
- Keine Admin- oder Write-Keys im Browser.
- UI mit 300 ms Debounce und AbortController.
- Zero-result queries, langsame Suchen und Klicks loggen.
Deliverables:
- Entscheidungsnotiz zu Postgres full-text, Meilisearch und Algolia.
- Index schema.
- Sync job.
- /api/search route.
- React search UI.
- Tests und Rollout-Checkliste.
Lass Claude Code zuerst Datenbankschema, MDX-Frontmatter, Auth-Regeln und URL-Struktur lesen. Suchfehler sind häufig Datenfreigabe-Fehler, nicht nur Ranking-Probleme.
Postgres, Meilisearch oder Algolia?
Die offizielle PostgreSQL-Dokumentation zu Full Text Search beschreibttsvector, tsquery und Ranking. Das ist ideal, wenn die Daten bereits in Postgres liegen. Meilisearch ist mit Quick Start und Filtering, Sorting and Faceting schnell produktiv. Algolia bietet mit InstantSearch.js und React InstantSearch starke UI-Bausteine und Analytics.
Index-Schema für Postgres
CREATE TABLE IF NOT EXISTS articles (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
slug text NOT NULL UNIQUE,
locale text NOT NULL,
status text NOT NULL CHECK (status IN ('draft', 'published', 'private')),
title text NOT NULL,
summary text NOT NULL,
body text NOT NULL,
category text NOT NULL,
tags text[] NOT NULL DEFAULT '{}',
popularity integer NOT NULL DEFAULT 0,
updated_at timestamptz NOT NULL DEFAULT now(),
search_vector tsvector GENERATED ALWAYS AS (
setweight(to_tsvector('simple', coalesce(title, '')), 'A') ||
setweight(to_tsvector('simple', coalesce(summary, '')), 'B') ||
setweight(to_tsvector('simple', coalesce(array_to_string(tags, ' '), '')), 'B') ||
setweight(to_tsvector('simple', coalesce(body, '')), 'C')
) STORED
);
CREATE INDEX IF NOT EXISTS articles_search_vector_idx
ON articles USING GIN (search_vector);
CREATE INDEX IF NOT EXISTS articles_locale_status_idx
ON articles (locale, status, updated_at DESC);
Die Gewichtung ist entscheidend: Ein Treffer im Titel sollte stärker zählen als ein einzelner Treffer tief im Body. Für deutsche Inhalte funktioniert das als solider Start; bei anspruchsvoller Sprachlogik sollte man Synonyme und externe Suchdienste prüfen.
Sync-Job für Meilisearch
Die Datenbank bleibt die Quelle der Wahrheit. Der Sync schickt nur öffentliche Felder an die Suchmaschine.
// scripts/sync-meilisearch.ts
import "dotenv/config";
import { MeiliSearch } from "meilisearch";
type ArticleRecord = {
id: string;
title: string;
summary: string;
body: string;
locale: string;
status: "published";
category: string;
tags: string[];
url: string;
popularity: number;
updatedAtTimestamp: number;
};
const client = new MeiliSearch({
host: process.env.MEILISEARCH_HOST ?? "http://127.0.0.1:7700",
apiKey: process.env.MEILISEARCH_ADMIN_KEY
});
const index = client.index<ArticleRecord>("articles");
await index.updateSettings({
searchableAttributes: ["title", "summary", "body", "tags"],
filterableAttributes: ["locale", "status", "category", "tags"],
sortableAttributes: ["updatedAtTimestamp", "popularity"],
displayedAttributes: ["id", "title", "summary", "locale", "category", "tags", "url"]
});
const task = await index.addDocuments(
[
{
id: "de_claude-code-search-functionality",
title: "Suchfunktion mit Claude Code implementieren",
summary: "Ein praktischer Leitfaden zu Backend-Auswahl, Index, UI, Tests und Rollout.",
body: "Öffentlicher Suchtext aus MDX oder CMS.",
locale: "de",
status: "published",
category: "use-cases",
tags: ["Claude Code", "Suchfunktion", "Volltextsuche"],
url: "/de/blog/claude-code-search-functionality",
popularity: 18,
updatedAtTimestamp: 1780272000
}
],
{ primaryKey: "id" }
);
console.log(`Queued Meilisearch task ${task.taskUid}`);
Beginne bei Facetten sparsam: category, tags und locale reichen für Content-Suche oft aus. Rechtefilter gehören serverseitig oder in eingeschränkte Search Keys, nicht in frei manipulierbare UI-Parameter.
Debounced React UI
// components/ArticleSearchBox.tsx
"use client";
import { useEffect, useMemo, useState } from "react";
type SearchHit = {
id: string;
title: string;
summary: string;
url: string;
category: string;
};
function useDebounce<T>(value: T, delayMs: number) {
const [debounced, setDebounced] = useState(value);
useEffect(() => {
const timer = window.setTimeout(() => setDebounced(value), delayMs);
return () => window.clearTimeout(timer);
}, [value, delayMs]);
return debounced;
}
export function ArticleSearchBox({ locale = "de" }: { locale?: string }) {
const [query, setQuery] = useState("");
const [category, setCategory] = useState("");
const [hits, setHits] = useState<SearchHit[]>([]);
const [loading, setLoading] = useState(false);
const debouncedQuery = useDebounce(query, 300);
const params = useMemo(() => {
const next = new URLSearchParams({ q: debouncedQuery, locale });
if (category) next.set("category", category);
return next;
}, [category, debouncedQuery, locale]);
useEffect(() => {
if (debouncedQuery.trim().length < 2) {
setHits([]);
return;
}
const controller = new AbortController();
setLoading(true);
fetch(`/api/search?${params.toString()}`, { signal: controller.signal })
.then((response) => {
if (!response.ok) throw new Error("Search request failed");
return response.json();
})
.then((data: { hits: SearchHit[] }) => setHits(data.hits))
.catch((error) => {
if (error.name !== "AbortError") console.error(error);
})
.finally(() => setLoading(false));
return () => controller.abort();
}, [debouncedQuery, params]);
return (
<section aria-label="Article search">
<input
aria-label="Search keywords"
type="search"
value={query}
onChange={(event) => setQuery(event.target.value)}
placeholder="Search Claude Code articles"
/>
<select aria-label="Category" value={category} onChange={(event) => setCategory(event.target.value)}>
<option value="">All</option>
<option value="use-cases">Use cases</option>
<option value="advanced">Advanced</option>
</select>
{loading && <p>Searching...</p>}
<ul>
{hits.map((hit) => (
<li key={hit.id}>
<a href={hit.url}>{hit.title}</a>
<p>{hit.summary}</p>
</li>
))}
</ul>
</section>
);
}
Tests, Rollout und typische Fehler
Typische Fehler sind Drafts im Index, Admin Keys im Browser, private Felder beim Suchanbieter, zu viele Synonyme, zu viele Facetten und keine Auswertung der Null-Treffer. Teste mindestens kurze Queries, Published-only-Filter, Kategorie-Filter und mobile Darstellung.
// tests/search-query.test.ts
import { describe, expect, it } from "vitest";
function shouldSearch(query: string) {
return query.trim().length >= 2 && query.length <= 80;
}
describe("search request rules", () => {
it("rejects empty and one-character queries", () => {
expect(shouldSearch("")).toBe(false);
expect(shouldSearch("a")).toBe(false);
expect(shouldSearch("api")).toBe(true);
});
});
Vor dem Rollout: nur öffentliche Daten im Index, lesbarer Null-Treffer-Zustand, p95-Latenz geprüft, lange Queries begrenzt, keine personenbezogenen Daten in Logs, natürliche interne Links zur Suche. Danach sollten Suchlogs wöchentlich in konkrete Aufgaben für Titel, Synonyme, interne Links und neue Inhalte übersetzt werden.
ClaudeCodeLab unterstützt bei Suchdesign, Claude Code Training und Implementierungsreviews. Für strukturierte Hilfe ist die Training- und Beratungsseite der passende Einstieg.
Fazit
Die robuste Reihenfolge lautet: Anforderungen, Backend-Wahl, Index-Schema, Sync, Filter und Facetten, Debounce-UI, Tests, Rollout. In der Praxis reduzieren früh begrenzte Index- und Rückgabefelder die spätere Nacharbeit deutlich, während Null-Treffer-Logs die besten Ideen für PV-Wachstum liefern.
Kostenloses PDF: Claude-Code-Cheatsheet
E-Mail eintragen und eine Seite mit Befehlen, Review-Gewohnheiten und sicheren Workflows herunterladen.
Wir schützen Ihre Daten und senden keinen Spam.
Über den Autor
Masa
Engineer für praktische Claude-Code-Workflows und Team-Einführung.
Ähnliche Artikel
Claude Code Workflow von Obsidian zu CLAUDE.md
Obsidian-Arbeitsnotizen in CLAUDE.md-Betriebsnotizen verwandeln und Kontext nicht ständig neu erklären.
Claude Code Revenue CTA Routing: Artikel zu PDF, Gumroad und Beratung führen
Ein Claude-Code-Ablauf, der Leser nach Absicht zu Gratis-PDF, Gumroad oder Beratung führt.
Claude-Code-Team-Handoff-Regeln: Belege, Berechtigungen, Rollback und Umsatzpfade
Ein praktisches Claude-Code-Handoff für Review-Belege, Berechtigungen, Rollback, Gratis-PDF, Gumroad und Beratung.