Use Cases (Diperbarui: 1/6/2026)

Membangun Search Functionality dengan Claude Code: Postgres, Meilisearch, Algolia

Panduan praktis search dengan Claude Code: requirements, index, sync job, filter, debounce UI, testing, dan rollout.

Membangun Search Functionality dengan Claude Code: Postgres, Meilisearch, Algolia

Search bukan sekadar kotak input

Search functionality adalah pengalaman yang menerima input pengguna, menemukan kandidat yang cocok, lalu mengembalikan hasil dengan filter, sorting, dan highlight. Query sederhana sepertiLIKE '%term%' bisa cukup untuk tabel internal, tetapi tidak cukup untuk blog, knowledge base, katalog kursus, atau produk SaaS yang mengejar PV dan monetisasi.

Pengalaman praktis Masa: jika UI dibuat lebih dulu, pekerjaan ulang hampir pasti muncul. Field yang diindeks, batas data publik dan privat, locale, ranking, sync job, serta review log harus ditentukan sejak awal. Claude Code bisa menulis cepat, tetapi kualitas hasil tergantung pada brief yang jelas.

Baca juga Claude Code Algolia search, Claude Code API development, dan Claude Code performance optimization.

Mulai dari use case

Use caseContohYang pentingPilihan cocok
Content searchblog, FAQ, docsbobot judul, ringkasan, tag, localePostgres full-text atau Meilisearch
Catalog searchproduk, kursus, templatefacet, sorting, sinonim, analyticsMeilisearch atau Algolia
Admin searchcustomer, invoice, logpermission, filter presisi, auditPostgres dulu
Multilingual searchartikel id/en/japemisahan locale, keyword lokalMeilisearch atau Algolia

Untuk situs kecil yang datanya sudah ada di Postgres, full-text search adalah awal yang masuk akal. Jika butuh typo tolerance, facet, dan relevansi yang lebih siap, Meilisearch mudah dioperasikan. Jika search langsung memengaruhi revenue atau conversion, Algolia lebih kuat.

Requirements prompt untuk Claude Code

You are implementing production search in an existing Next.js app.

Goal:
- Search published articles and increase content discovery.
- Support query, locale, category, and tag filters.
- Search title, summary, tags, and body, with title ranked highest.
- Return fields needed for result cards and highlighting.

Constraints:
- Never return drafts, private records, emails, internal notes, or restricted content.
- Do not expose admin or write API keys to the browser.
- Use 300 ms debounce and AbortController in the UI.
- Log zero-result queries, slow searches, and clicked results.

Deliverables:
- Decision note comparing Postgres full-text, Meilisearch, and Algolia.
- Index schema.
- Sync job.
- /api/search route.
- React search UI.
- Tests and rollout checklist.

Minta Claude Code membaca schema, MDX frontmatter, aturan auth, dan struktur URL sebelum mengedit. Dalam search, risiko terbesar sering kali bukan ranking buruk, melainkan data privat ikut masuk index.

Dokumentasi resmi PostgreSQL untuk Full Text Search menjelaskantsvector, tsquery, dan ranking. Meilisearch menyediakan quick start serta panduan filtering, sorting, and faceting. Algolia unggul untuk UI dan analytics melalui InstantSearch.js dan 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);

Kuncinya adalah bobot: judul lebih kuat daripada body, summary dan tags berada di tengah. Ini membuat ranking lebih dekat dengan harapan pembaca.

Sync job Meilisearch

Database atau CMS tetap menjadi source of truth. Index search hanya menerima field publik.

// 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: "id_claude-code-search-functionality",
      title: "Membangun Search Functionality dengan Claude Code",
      summary: "Panduan backend, index, UI, testing, dan rollout untuk search.",
      body: "Teks artikel publik yang diekstrak dari MDX atau CMS.",
      locale: "id",
      status: "published",
      category: "use-cases",
      tags: ["Claude Code", "search functionality", "pencarian"],
      url: "/id/blog/claude-code-search-functionality",
      popularity: 18,
      updatedAtTimestamp: 1780272000
    }
  ],
  { primaryKey: "id" }
);

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

Facet jangan terlalu banyak. Untuk content search,category, tags, danlocale biasanya cukup. Filter permission harus dikunci di server atau search key terbatas.

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

Testing, rollout, dan jebakan umum

Jebakan umum: draft ikut terindeks, admin key masuk browser, field privat dikirim ke provider search, sinonim terlalu luas, semua kolom dijadikan facet, dan zero-result query tidak pernah direview.

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

Sebelum rollout, cek index hanya berisi data publik, UI untuk zero result ada, p95 latency masuk target, query panjang dipotong, log tidak menyimpan data personal, dan layout mobile rapi. Setelah launch, review zero-result dan low-click queries setiap minggu untuk memperbaiki judul, sinonim, internal link, dan ide artikel baru.

ClaudeCodeLab membantu desain search, training Claude Code, dan review implementasi. Untuk dukungan terstruktur, buka training and consultation page.

Ringkasan

Urutan yang aman adalah requirements, pilihan backend, index schema, sync job, filters/facets, debounce UI, tests, lalu rollout. Dari praktik, membatasi field yang diindeks dan dikembalikan sejak awal mengurangi revisi, sementara zero-result logs memberi ide paling konkret untuk pertumbuhan PV.

#Claude Code #search functionality #pencarian #Meilisearch #Algolia
Gratis

PDF gratis: cheatsheet Claude Code

Masukkan email dan unduh satu halaman berisi command, kebiasaan review, dan workflow aman.

Kami menjaga datamu dan tidak mengirim spam.

Masa

Tentang penulis

Masa

Engineer yang berfokus pada workflow Claude Code praktis dan adopsi tim.