> ## Documentation Index
> Fetch the complete documentation index at: https://docs.hiveku.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Add Search to Your Site

> Let visitors search your content with a fast, relevance-ranked search experience

Search is one of the most-used features on any content-heavy site. When it works well, visitors find exactly what they need in seconds. When it doesn't, they bounce. This guide covers the two main approaches — built-in database search and hosted search (Algolia) — plus the UX details that separate a good search box from a frustrating one.

## Three Paths

<Tabs>
  <Tab title="AI Chat (easiest)">
    Describe what you want:

    ```
    Add a search box to my site header that searches blog 
    posts and product pages. Show results as a dropdown 
    as the user types.
    ```

    The AI scaffolds the schema, search endpoint, and UI component.
  </Tab>

  <Tab title="Algolia">
    Hosted search service, fast, handles typos and ranking well. Best for larger sites (1000+ items), multi-language sites, or when you want features like faceted filtering out of the box.
  </Tab>

  <Tab title="Database full-text search">
    Built into PostgreSQL via `tsvector`. Free, no extra service. Good for smaller content libraries where you don't need typo-tolerance or advanced ranking.
  </Tab>
</Tabs>

## Database Full-Text Search (PostgreSQL)

<Steps>
  <Step title="Add a search column">
    ```sql theme={null}
    ALTER TABLE blog_posts ADD COLUMN search_vector tsvector;

    UPDATE blog_posts SET search_vector = 
      setweight(to_tsvector('english', coalesce(title, '')), 'A') ||
      setweight(to_tsvector('english', coalesce(body, '')), 'B');

    CREATE INDEX ON blog_posts USING gin(search_vector);
    ```

    The `setweight` call gives title matches more weight than body matches in ranking.
  </Step>

  <Step title="Keep it updated on insert/update">
    ```sql theme={null}
    CREATE TRIGGER blog_posts_search_trigger
    BEFORE INSERT OR UPDATE ON blog_posts
    FOR EACH ROW EXECUTE FUNCTION
    tsvector_update_trigger(
      search_vector, 'pg_catalog.english', title, body
    );
    ```
  </Step>

  <Step title="Write the search query">
    ```sql theme={null}
    SELECT id, title, ts_rank(search_vector, query) as rank
    FROM blog_posts, plainto_tsquery('english', 'your search term') query
    WHERE search_vector @@ query
    ORDER BY rank DESC LIMIT 10;
    ```
  </Step>

  <Step title="Expose via API route">
    ```typescript theme={null}
    export default async function handler(req: Request) {
      const url = new URL(req.url);
      const q = url.searchParams.get('q') ?? '';
      
      const results = await db.query(
        `SELECT id, title, slug, ts_rank(search_vector, query) as rank
         FROM blog_posts, plainto_tsquery('english', $1) query
         WHERE search_vector @@ query
         ORDER BY rank DESC LIMIT 10`,
        [q]
      );
      
      return Response.json({ results });
    }
    ```
  </Step>

  <Step title="Build the frontend component">
    A search input that calls the API as the user types (debounced 300ms), renders results in a dropdown below the input.
  </Step>
</Steps>

## Algolia Pattern

For larger sites or when you need typo-tolerance:

<Steps>
  <Step title="Sign up for Algolia">
    Go to [algolia.com](https://www.algolia.com), create an app. Free tier covers up to 10k searches/month.
  </Step>

  <Step title="Copy your keys">
    * **Application ID** — public identifier
    * **Search-only API key** — safe to expose in the browser
    * **Admin API key** — keep server-side only (used for indexing)
  </Step>

  <Step title="Install the SDK">
    ```bash theme={null}
    npm install algoliasearch react-instantsearch
    ```
  </Step>

  <Step title="Index your content">
    ```typescript theme={null}
    import algoliasearch from 'algoliasearch';

    const client = algoliasearch(APP_ID, ADMIN_KEY);
    const index = client.initIndex('blog_posts');

    const posts = await db.query('SELECT id, title, body, slug FROM blog_posts');
    await index.saveObjects(
      posts.map(p => ({ objectID: p.id, ...p }))
    );
    ```

    Run this on a schedule (or on content change) to keep Algolia in sync.
  </Step>

  <Step title="Add the search UI">
    ```tsx theme={null}
    import { InstantSearch, SearchBox, Hits } from 'react-instantsearch';
    import algoliasearch from 'algoliasearch/lite';

    const searchClient = algoliasearch(APP_ID, SEARCH_KEY);

    export default function Search() {
      return (
        <InstantSearch searchClient={searchClient} indexName="blog_posts">
          <SearchBox />
          <Hits hitComponent={Hit} />
        </InstantSearch>
      );
    }
    ```
  </Step>
</Steps>

## Search UX Best Practices

The plumbing is easy. The UX is where most search implementations fall down.

* **Results as user types.** Debounce 300ms so you're not hammering the API on every keystroke.
* **Highlight matched terms.** Bold the portions of each result that match the query. Makes relevance obvious.
* **Keyboard navigation.** Arrow keys to move through results, Enter to open, Escape to close.
* **Zero-results state.** Don't just say "No results." Suggest popular queries, link to browse pages, show recent posts.
* **Recent searches.** If the user has searched before, show recent queries when the box is empty. Saves typing.
* **Query tracking.** Log queries to analytics so you know what users look for. See [Track Analytics](/how-tos/track-analytics).

## Filter by Content Type

On sites with multiple content types (blog, products, docs), let users narrow:

```tsx theme={null}
<SearchFilters>
  <Filter value="all">All</Filter>
  <Filter value="blog">Blog</Filter>
  <Filter value="products">Products</Filter>
  <Filter value="docs">Docs</Filter>
</SearchFilters>
```

## Faceted Search

For catalogs with structured metadata, add facet filters:

* Category
* Tags
* Date range
* Price range
* Author

Both Algolia and PostgreSQL full-text search support this — Algolia handles it natively with InstantSearch UI components; in PostgreSQL you add WHERE clauses.

## For Docs Sites Specifically

If you're building a documentation site, [Algolia DocSearch](https://docsearch.algolia.com/) is free for open-source docs and purpose-built for the use case (good hierarchical ranking, keyboard nav, autocomplete). Apply and Algolia handles indexing for you.

<Tip>
  Search queries are one of your best sources of content and product ideas. Users are literally telling you what they want. Review top queries weekly and look for gaps — queries with zero results are a roadmap of missing content.
</Tip>

## Verify It Worked

<Steps>
  <Step title="Open your site">
    Find the search box (usually in the header).
  </Step>

  <Step title="Type a query">
    Use a term you know exists in your content. Results should appear within 300-500ms.
  </Step>

  <Step title="Click a result">
    Confirm it takes you to the right page.
  </Step>

  <Step title="Test a typo">
    If using Algolia, "webistes" should still return "websites" content. If using plain PostgreSQL, typos won't match — consider adding `pg_trgm` for similarity matching.
  </Step>

  <Step title="Test the empty state">
    Clear the box. Confirm your zero-query state (popular posts, suggestions) renders.
  </Step>
</Steps>

## Troubleshooting

<AccordionGroup>
  <Accordion title="No results for anything">
    The index is empty. For PostgreSQL: the `search_vector` column is null — run the UPDATE from Step 1 to backfill. For Algolia: you haven't pushed content yet — run the indexing script.
  </Accordion>

  <Accordion title="Search is slow (>1 second)">
    For PostgreSQL: missing GIN index on `search_vector`. Check with `\d+ blog_posts` and confirm an index on that column. Also look for N+1 queries — you might be fetching full rows per result when IDs would do.
  </Accordion>

  <Accordion title="Typos don't match">
    PostgreSQL full-text search doesn't handle typos out of the box. Add the `pg_trgm` extension and use `similarity()` or `%` operator for fuzzy matching. Or switch to Algolia, which handles typos automatically.
  </Accordion>

  <Accordion title="Results look random (no relevance)">
    Add ranking signals beyond raw match. Good options: recency (newer posts rank higher), popularity (view count, engagement), author authority, content type priority. Combine them into a composite score that you ORDER BY.
  </Accordion>

  <Accordion title="Algolia index out of sync with database">
    Make sure your content-update workflow pushes to Algolia. Add an action after any blog/product insert or update that calls `index.saveObject(...)`. For deletes, call `index.deleteObject(objectID)`.
  </Accordion>
</AccordionGroup>

## What's Next?

<CardGroup cols={2}>
  <Card title="Track Analytics" icon="chart-line" href="/how-tos/track-analytics">
    Log search queries so you can learn what users want
  </Card>

  <Card title="Structured Data" icon="code" href="/how-tos/add-structured-data">
    Help search engines index your content too
  </Card>
</CardGroup>
