Claude
Skills
Sign in
Back

algolia-search

Included with Lifetime
$97 forever

Expert patterns for Algolia search implementation, indexing strategies, React InstantSearch, and relevance tuning

Web Dev

What this skill does


# Algolia Search Integration

Expert patterns for Algolia search implementation, indexing strategies, React InstantSearch, and relevance tuning

## Patterns

### React InstantSearch with Hooks

Modern React InstantSearch setup using hooks for type-ahead search.

Uses react-instantsearch-hooks-web package with algoliasearch client.
Widgets are components that can be customized with classnames.

Key hooks:
- useSearchBox: Search input handling
- useHits: Access search results
- useRefinementList: Facet filtering
- usePagination: Result pagination
- useInstantSearch: Full state access

### Code_example

// lib/algolia.ts
import algoliasearch from 'algoliasearch/lite';

export const searchClient = algoliasearch(
  process.env.NEXT_PUBLIC_ALGOLIA_APP_ID!,
  process.env.NEXT_PUBLIC_ALGOLIA_SEARCH_KEY!  // Search-only key!
);

export const INDEX_NAME = 'products';

// components/Search.tsx
'use client';
import { InstantSearch, SearchBox, Hits, Configure } from 'react-instantsearch';
import { searchClient, INDEX_NAME } from '@/lib/algolia';

function Hit({ hit }: { hit: ProductHit }) {
  return (
    <article>
      <h3>{hit.name}</h3>
      <p>{hit.description}</p>
      <span>${hit.price}</span>
    </article>
  );
}

export function ProductSearch() {
  return (
    <InstantSearch searchClient={searchClient} indexName={INDEX_NAME}>
      <Configure hitsPerPage={20} />
      <SearchBox
        placeholder="Search products..."
        classNames={{
          root: 'relative',
          input: 'w-full px-4 py-2 border rounded',
        }}
      />
      <Hits hitComponent={Hit} />
    </InstantSearch>
  );
}

// Custom hook usage
import { useSearchBox, useHits, useInstantSearch } from 'react-instantsearch';

function CustomSearch() {
  const { query, refine } = useSearchBox();
  const { hits } = useHits<ProductHit>();
  const { status } = useInstantSearch();

  return (
    <div>
      <input
        value={query}
        onChange={(e) => refine(e.target.value)}
        placeholder="Search..."
      />
      {status === 'loading' && <p>Loading...</p>}
      <ul>
        {hits.map((hit) => (
          <li key={hit.objectID}>{hit.name}</li>
        ))}
      </ul>
    </div>
  );
}

### Anti_patterns

- Pattern: Using Admin API key in frontend code | Why: Admin key exposes full index control including deletion | Fix: Use search-only API key with restrictions
- Pattern: Not using /lite client for frontend | Why: Full client includes unnecessary code for search | Fix: Import from algoliasearch/lite for smaller bundle

### References

- https://www.algolia.com/doc/api-reference/widgets/react
- https://www.algolia.com/doc/libraries/javascript/v5/methods/search/

### Next.js Server-Side Rendering

SSR integration for Next.js with react-instantsearch-nextjs package.

Use <InstantSearchNext> instead of <InstantSearch> for SSR.
Supports both Pages Router and App Router (experimental).

Key considerations:
- Set dynamic = 'force-dynamic' for fresh results
- Handle URL synchronization with routing prop
- Use getServerState for initial state

### Code_example

// app/search/page.tsx
import { InstantSearchNext } from 'react-instantsearch-nextjs';
import { searchClient, INDEX_NAME } from '@/lib/algolia';
import { SearchBox, Hits, RefinementList } from 'react-instantsearch';

// Force dynamic rendering for fresh search results
export const dynamic = 'force-dynamic';

export default function SearchPage() {
  return (
    <InstantSearchNext
      searchClient={searchClient}
      indexName={INDEX_NAME}
      routing={{
        router: {
          cleanUrlOnDispose: false,
        },
      }}
    >
      <div className="flex gap-8">
        <aside className="w-64">
          <h3>Categories</h3>
          <RefinementList attribute="category" />
          <h3>Brand</h3>
          <RefinementList attribute="brand" />
        </aside>
        <main className="flex-1">
          <SearchBox placeholder="Search products..." />
          <Hits hitComponent={ProductHit} />
        </main>
      </div>
    </InstantSearchNext>
  );
}

// For custom routing (URL synchronization)
import { history } from 'instantsearch.js/es/lib/routers';
import { simple } from 'instantsearch.js/es/lib/stateMappings';

<InstantSearchNext
  searchClient={searchClient}
  indexName={INDEX_NAME}
  routing={{
    router: history({
      getLocation: () =>
        typeof window === 'undefined'
          ? new URL(url) as unknown as Location
          : window.location,
    }),
    stateMapping: simple(),
  }}
>
  {/* widgets */}
</InstantSearchNext>

### Anti_patterns

- Pattern: Using InstantSearch component for Next.js SSR | Why: Regular component doesn't support server-side rendering | Fix: Use InstantSearchNext from react-instantsearch-nextjs
- Pattern: Static rendering for search pages | Why: Search results must be fresh for each request | Fix: Set export const dynamic = 'force-dynamic'

### References

- https://www.npmjs.com/package/react-instantsearch-nextjs
- https://www.algolia.com/developers/code-exchange/instantsearch-and-next-js-starter

### Data Synchronization and Indexing

Indexing strategies for keeping Algolia in sync with your data.

Three main approaches:
1. Full Reindexing - Replace entire index (expensive)
2. Full Record Updates - Replace individual records
3. Partial Updates - Update specific attributes only

Best practices:
- Batch records (ideal: 10MB, 1K-10K records per batch)
- Use incremental updates when possible
- partialUpdateObjects for attribute-only changes
- Avoid deleteBy (computationally expensive)

### Code_example

// lib/algolia-admin.ts (SERVER ONLY)
import algoliasearch from 'algoliasearch';

// Admin client - NEVER expose to frontend
const adminClient = algoliasearch(
  process.env.ALGOLIA_APP_ID!,
  process.env.ALGOLIA_ADMIN_KEY!  // Admin key for indexing
);

const index = adminClient.initIndex('products');

// Batch indexing (recommended approach)
export async function indexProducts(products: Product[]) {
  const records = products.map((p) => ({
    objectID: p.id,  // Required unique identifier
    name: p.name,
    description: p.description,
    price: p.price,
    category: p.category,
    inStock: p.inventory > 0,
    createdAt: p.createdAt.getTime(),  // Use timestamps for sorting
  }));

  // Batch in chunks of ~1000-5000 records
  const BATCH_SIZE = 1000;
  for (let i = 0; i < records.length; i += BATCH_SIZE) {
    const batch = records.slice(i, i + BATCH_SIZE);
    await index.saveObjects(batch);
  }
}

// Partial update - update only specific fields
export async function updateProductPrice(productId: string, price: number) {
  await index.partialUpdateObject({
    objectID: productId,
    price,
    updatedAt: Date.now(),
  });
}

// Partial update with operations
export async function incrementViewCount(productId: string) {
  await index.partialUpdateObject({
    objectID: productId,
    viewCount: {
      _operation: 'Increment',
      value: 1,
    },
  });
}

// Delete records (prefer this over deleteBy)
export async function deleteProducts(productIds: string[]) {
  await index.deleteObjects(productIds);
}

// Full reindex with zero-downtime (atomic swap)
export async function fullReindex(products: Product[]) {
  const tempIndex = adminClient.initIndex('products_temp');

  // Index to temp index
  await tempIndex.saveObjects(
    products.map((p) => ({
      objectID: p.id,
      ...p,
    }))
  );

  // Copy settings from main index
  await adminClient.copyIndex('products', 'products_temp', {
    scope: ['settings', 'synonyms', 'rules'],
  });

  // Atomic swap
  await adminClient.moveIndex('products_temp', 'products');
}

### Anti_patterns

- Pattern: Using deleteBy for bulk deletions | Why: deleteBy is computationally expensive and rate limited | Fix: Use deleteObjects with array of objectIDs
- Pattern: Indexing one record at a time | Why: Creates indexing queue, slows down process | Fix: Batch records in groups of 1K-10K
- Pattern: Full reindex fo

Related in Web Dev