Use Cases (Atualizado: 01/06/2026)

Implementar busca com Claude Code: Postgres, Meilisearch e Algolia

Guia prático de busca com Claude Code: requisitos, índice, sincronização, filtros, debounce UI, testes e rollout.

Implementar busca com Claude Code: Postgres, Meilisearch e Algolia

Busca é uma experiência de descoberta

Uma funcionalidade de busca recebe a consulta do usuário, encontra candidatos, aplica filtros, ordena e destaca os trechos relevantes. UmLIKE '%term%' pode resolver uma tabela interna, mas não sustenta um blog monetizado, uma base de conhecimento ou um catálogo de cursos. Boa busca aumenta PV, recupera conteúdos antigos e transforma consultas sem resultado em pauta editorial.

A experiência prática de Masa é que começar pela UI costuma gerar retrabalho. Antes do componente visual, defina campos indexados, dados públicos e privados, locale, ranking, sincronização e logs. Claude Code implementa rápido, mas precisa de requisitos claros.

Leituras relacionadas: busca Algolia com Claude Code, desenvolvimento de API com Claude Code e otimização de performance com Claude Code.

Separe os casos de uso

Caso de usoExemplosO que importaMelhor opção
Busca de conteúdoblog, FAQ, docspeso do título, resumo, tags, idiomaPostgres full-text ou Meilisearch
Catálogoprodutos, cursos, templatesfacetas, ordenação, sinônimos, analyticsMeilisearch ou Algolia
Adminclientes, cobranças, logspermissões, filtros exatos, auditoriaPostgres primeiro
Conteúdo multilínguepáginas pt/en/jalocale, keywords locais, URL corretaMeilisearch ou Algolia

Se os dados já estão no Postgres e o volume é moderado, comece com full-text search. Quando precisar de tolerância a erros, facetas e relevância mais pronta, Meilisearch é uma evolução natural. Quando busca afeta receita, leads ou venda de cursos, Algolia tende a compensar.

Prompt de requisitos para Claude Code

Implemente busca de produção em uma aplicação Next.js existente.

Objetivo:
- Buscar artigos publicados e aumentar descoberta de conteúdo.
- Suportar query, locale, category e tags.
- Buscar em title, summary, tags e body, com maior peso em title.
- Retornar campos suficientes para cards de resultado e highlight.

Restrições:
- Não retornar drafts, registros privados, emails, notas internas ou conteúdo restrito.
- Não expor chaves admin ou write no navegador.
- Usar debounce de 300 ms e AbortController na UI.
- Registrar buscas sem resultado, buscas lentas e cliques.

Entregáveis:
- Nota comparando Postgres full-text, Meilisearch e Algolia.
- Schema de índice.
- Job de sincronização.
- Rota /api/search.
- UI React.
- Testes e checklist de rollout.

Peça para Claude Code ler schema, frontmatter MDX, regras de autenticação e estrutura de URL antes de editar. Em busca, vazamento de dados é mais grave do que relevância imperfeita.

Escolha do backend de busca

A documentação oficial de PostgreSQL sobre Full Text Search cobretsvector, tsquery e ranking. Meilisearch tem um quick start simples e documentação de filtering, sorting and faceting. Algolia oferece UI e analytics fortes com InstantSearch.js e React InstantSearch.

Schema 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);

O ponto principal é peso. Um termo no título deve valer mais do que uma ocorrência perdida no corpo. Isso aproxima o ranking da intenção real do leitor.

Sincronização com Meilisearch

O banco ou CMS continua sendo a fonte de verdade. O índice recebe só campos públicos.

// 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: "pt_claude-code-search-functionality",
      title: "Implementar busca com Claude Code",
      summary: "Guia prático sobre backend, índice, UI, testes e rollout.",
      body: "Texto público extraído de MDX ou CMS.",
      locale: "pt",
      status: "published",
      category: "use-cases",
      tags: ["Claude Code", "busca", "full-text"],
      url: "/pt/blog/claude-code-search-functionality",
      popularity: 18,
      updatedAtTimestamp: 1780272000
    }
  ],
  { primaryKey: "id" }
);

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

Não exagere nas facetas. Para conteúdo,category, tags elocale costumam bastar. Filtros de permissão devem ficar no servidor ou em chaves de busca restritas.

UI React com 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 = "pt" }: { 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>
  );
}

Testes, rollout e armadilhas

As armadilhas são específicas: indexar rascunhos, expor admin key, enviar campos privados para um provedor, adicionar sinônimos demais, criar facetas demais e nunca revisar buscas sem resultado. Teste queries curtas, filtropublished, filtro de categoria e layout mobile.

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

Antes do lançamento, confirme índice public-only, estado de zero resultado, p95 de latência, limite para queries longas, logs sem dados pessoais e links internos naturais. Depois, revise semanalmente buscas sem resultado e baixo CTR para melhorar títulos, sinônimos, links internos e novos artigos.

ClaudeCodeLab apoia desenho de busca, treinamento em Claude Code e revisão de implementação. Para suporte estruturado, a página de treinamento e consultoria é um bom próximo passo.

Resumo

A ordem segura é: requisitos, escolha do motor, schema de índice, sync job, filtros e facetas, UI com debounce, testes e rollout. Na prática, restringir cedo os campos indexados e retornados reduz correções; revisar buscas sem resultado gera ideias diretas para crescimento de PV.

#Claude Code #busca #busca full-text #Meilisearch #Algolia
Grátis

PDF grátis: cheatsheet do Claude Code

Informe seu e-mail e baixe uma página com comandos, hábitos de revisão e workflows seguros.

Cuidamos dos seus dados e não enviamos spam.

Masa

Sobre o autor

Masa

Engenheiro focado em workflows práticos com Claude Code.