Use Cases (Mis à jour: 01/06/2026)

Implémenter une recherche avec Claude Code : Postgres, Meilisearch et Algolia

Guide pratique Claude Code pour la recherche : besoins, index, synchronisation, filtres, UI avec debounce, tests et rollout.

Implémenter une recherche avec Claude Code : Postgres, Meilisearch et Algolia

La recherche est une expérience produit

Une fonctionnalité de recherche prend une requête utilisateur, trouve des contenus candidats, les filtre, les trie et met en évidence les passages pertinents. Un simpleLIKE '%term%' peut suffire dans un outil interne, mais il ne suffit pas pour un blog monétisé, une base de connaissance ou un catalogue de formations. Une bonne recherche augmente les pages vues, remet en circulation les contenus durables et transforme les recherches sans résultat en idées éditoriales.

La leçon pratique de Masa est récurrente : commencer par l’interface crée de la dette. Il faut d’abord décider quels champs sont indexés, comment exclure les brouillons, comment isoler les langues, comment classer les résultats et comment relire les logs. Claude Code peut aller vite, mais il doit recevoir un cahier des charges précis.

À lire aussi : recherche Algolia avec Claude Code, développement API avec Claude Code et optimisation des performances avec Claude Code.

Clarifier les cas d’usage

Cas d’usageExemplesPrioritéBon choix
Recherche de contenublog, FAQ, documentationpoids du titre, résumé, tags, languePostgres plein texte ou Meilisearch
Recherche catalogueproduits, cours, templatesfacettes, tri, synonymes, analyticsMeilisearch ou Algolia
Recherche adminclients, factures, logsdroits, filtres exacts, auditPostgres d’abord
Recherche multilinguearticles localisésséparation par locale, mots-clés locauxMeilisearch ou Algolia

Pour un petit site déjà sur Postgres, la recherche plein texte est un bon départ. Quand les fautes de frappe, les facettes et la pertinence par défaut deviennent importantes, Meilisearch simplifie beaucoup. Quand la recherche est liée au chiffre d’affaires ou aux conversions, Algolia devient plus intéressant.

Prompt de cadrage pour Claude Code

Tu implémentes une recherche de production dans une application Next.js existante.

Objectif:
- Rechercher les articles publiés et améliorer la découverte de contenu.
- Supporter query, locale, category et tags.
- Chercher dans title, summary, tags et body, avec title comme champ le plus fort.
- Retourner les champs nécessaires aux cartes de résultat et au highlighting.

Contraintes:
- Ne jamais retourner brouillons, contenus privés, e-mails, notes internes ou données restreintes.
- Ne pas exposer de clé admin ou write dans le navigateur.
- Utiliser un debounce de 300 ms et AbortController côté UI.
- Logger les recherches sans résultat, les recherches lentes et les clics.

Livrables:
- Note de choix entre Postgres full-text, Meilisearch et Algolia.
- Schéma d'index.
- Job de synchronisation.
- Route /api/search.
- UI React.
- Tests et checklist de rollout.

Demande à Claude Code de lire le schéma de données, le frontmatter MDX, les règles d’authentification et la structure d’URL avant de modifier les fichiers. En recherche, la fuite de données est plus grave qu’un mauvais classement.

Choisir le moteur de recherche

La documentation officielle PostgreSQL sur Full Text Search couvretsvector, tsquery et le ranking. Meilisearch propose un quick start et une page claire sur filtering, sorting and faceting. Algolia est très fort côté UI avec InstantSearch.js et React InstantSearch.

Schéma d’index 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);

L’idée centrale est la pondération : le titre doit compter plus que le corps. C’est ce qui rapproche le classement de l’intention réelle du lecteur.

Job de synchronisation Meilisearch

// 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: "fr_claude-code-search-functionality",
      title: "Implémenter une recherche avec Claude Code",
      summary: "Guide pratique sur le choix du moteur, l'index, l'UI, les tests et le rollout.",
      body: "Texte public extrait des fichiers MDX ou du CMS.",
      locale: "fr",
      status: "published",
      category: "use-cases",
      tags: ["Claude Code", "recherche", "plein texte"],
      url: "/fr/blog/claude-code-search-functionality",
      popularity: 18,
      updatedAtTimestamp: 1780272000
    }
  ],
  { primaryKey: "id" }
);

console.log(`Queued Meilisearch task ${task.taskUid}`);

Ne synchronise pas les champs privés en pensant les cacher ensuite dans l’UI. Si le champ est dans l’index, il peut ressortir par une API, un dashboard ou un log.

UI React avec debounce

// 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 = "fr" }: { 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, pièges et rollout

Les pièges concrets : indexer des brouillons, exposer une clé admin, envoyer des champs privés à un prestataire, ajouter trop de synonymes, transformer toutes les colonnes en facettes et ne jamais relire les recherches sans résultat.

// 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);
  });
});

Avant publication, vérifie l’index public-only, l’état 0 résultat, la latence p95, la limite de longueur des requêtes, l’absence de données personnelles dans les logs et le rendu mobile. Ensuite, relis chaque semaine les requêtes sans résultat et les faibles taux de clic pour améliorer titres, synonymes, liens internes et nouveaux articles.

ClaudeCodeLab accompagne la conception de recherche, la formation Claude Code et les revues d’implémentation. Pour un accompagnement structuré, la page formation et conseil est le bon point d’entrée.

Résumé

L’ordre robuste est : exigences, choix du moteur, schéma d’index, synchronisation, filtres et facettes, UI avec debounce, tests, rollout. En pratique, limiter tôt les champs indexés et les champs retournés réduit fortement les corrections, tandis que les recherches sans résultat donnent les meilleures idées de contenus pour augmenter les pages vues.

#Claude Code #recherche #recherche plein texte #Meilisearch #Algolia
Gratuit

PDF gratuit: cheatsheet Claude Code

Saisissez votre email et téléchargez une page avec commandes, habitudes de review et workflow sûr.

Nous protégeons vos données et n'envoyons pas de spam.

Masa

À propos de l'auteur

Masa

Ingénieur spécialisé dans les workflows pratiques avec Claude Code.