Skip to main content
Technology & EngineeringSearch Services257 lines

Algolia

"Algolia: instant search, faceted search, InstantSearch.js/React, indexing, ranking, search analytics"

Quick Summary18 lines
Algolia is a hosted search-as-a-service platform built for speed and relevance. Its core tenets are:

## Key Points

- **Sub-50ms responses** — search results return in milliseconds regardless of dataset size.
- **Typo tolerance by default** — users get results even with misspellings.
- **Relevance out of the box** — tie-breaking ranking criteria (textual, then custom) ensure the best results surface first.
- **Frontend-first architecture** — queries go directly from the browser to Algolia's API, bypassing your backend for search reads.
- **Configuration over code** — ranking, synonyms, and rules are configured declaratively, not through custom scoring logic.
- **Use the search-only API key on the frontend.** Never expose the admin key in client code. Generate scoped keys for multi-tenant apps with `client.generateSecuredApiKey()`.
- **Push full records, not deltas.** When reindexing, use `replaceAllObjects` for zero-downtime atomic swaps of the entire index.
- **Keep records small.** Only index fields you search or filter on. Store large blobs elsewhere and reference them by ID.
- **Set searchableAttributes explicitly.** Without this, Algolia searches all fields, diluting relevance.
- **Use `filterOnly` for attributes you filter but never display as facets.** This saves processing.
- **Leverage query rules** for merchandising instead of hardcoding promotions in application logic.
- **Send analytics events** (clicks, conversions) to enable Algolia's AI re-ranking and Recommend features.
skilldb get search-services-skills/AlgoliaFull skill: 257 lines
Paste into your CLAUDE.md or agent config

Algolia

Core Philosophy

Algolia is a hosted search-as-a-service platform built for speed and relevance. Its core tenets are:

  • Sub-50ms responses — search results return in milliseconds regardless of dataset size.
  • Typo tolerance by default — users get results even with misspellings.
  • Relevance out of the box — tie-breaking ranking criteria (textual, then custom) ensure the best results surface first.
  • Frontend-first architecture — queries go directly from the browser to Algolia's API, bypassing your backend for search reads.
  • Configuration over code — ranking, synonyms, and rules are configured declaratively, not through custom scoring logic.

Treat Algolia as the read layer for search. Your backend pushes data into indices; your frontend queries them directly using API keys with restricted permissions.

Setup

Install the JavaScript API client and the React InstantSearch library:

// npm install algoliasearch react-instantsearch

import algoliasearch from "algoliasearch";

const client = algoliasearch("YOUR_APP_ID", "YOUR_ADMIN_API_KEY");
const index = client.initIndex("products");

// Push records to the index
async function indexProducts(products: Product[]) {
  // Every record needs a unique objectID
  const records = products.map((p) => ({
    objectID: p.id,
    name: p.name,
    description: p.description,
    price: p.price,
    categories: p.categories,
    rating: p.rating,
    inStock: p.inStock,
  }));

  const { objectIDs } = await index.saveObjects(records);
  console.log(`Indexed ${objectIDs.length} records`);
}

Configure index settings for ranking and faceting:

async function configureIndex() {
  await index.setSettings({
    // Attributes users can search against
    searchableAttributes: [
      "name",           // highest priority
      "description",    // second priority
      "categories",     // third priority
    ],
    // Attributes available for faceted filtering
    attributesForFaceting: [
      "categories",
      "filterOnly(inStock)",   // filter only, not displayed as facet
      "searchable(brand)",     // facet that is also searchable
    ],
    // Custom ranking after textual relevance
    customRanking: [
      "desc(rating)",
      "desc(salesCount)",
    ],
    // Attributes returned in results
    attributesToRetrieve: ["name", "description", "price", "image"],
    attributesToHighlight: ["name", "description"],
  });
}

Key Techniques

Direct Backend Search

async function searchProducts(query: string, filters?: string) {
  const { hits, nbHits, processingTimeMS } = await index.search<Product>(query, {
    filters: filters ?? "",
    hitsPerPage: 20,
    page: 0,
    attributesToRetrieve: ["name", "price", "image"],
    attributesToHighlight: ["name"],
  });

  console.log(`Found ${nbHits} results in ${processingTimeMS}ms`);
  return hits;
}

// Faceted search
async function searchWithFacets(query: string, category?: string) {
  return index.search<Product>(query, {
    facets: ["categories", "brand"],
    filters: category ? `categories:"${category}"` : "",
  });
}

React InstantSearch Integration

import React from "react";
import algoliasearch from "algoliasearch/lite"; // lightweight search-only client
import {
  InstantSearch,
  SearchBox,
  Hits,
  RefinementList,
  Pagination,
  Highlight,
  Configure,
  useSearchBox,
  useHits,
} from "react-instantsearch";

const searchClient = algoliasearch("YOUR_APP_ID", "YOUR_SEARCH_ONLY_KEY");

function ProductHit({ hit }: { hit: Product }) {
  return (
    <article>
      <h3>
        <Highlight attribute="name" hit={hit} />
      </h3>
      <p>
        <Highlight attribute="description" hit={hit} />
      </p>
      <span>${hit.price}</span>
    </article>
  );
}

function SearchPage() {
  return (
    <InstantSearch searchClient={searchClient} indexName="products">
      <Configure hitsPerPage={20} />
      <div className="search-layout">
        <aside>
          <h4>Categories</h4>
          <RefinementList attribute="categories" />
        </aside>
        <main>
          <SearchBox placeholder="Search products..." />
          <Hits hitComponent={ProductHit} />
          <Pagination />
        </main>
      </div>
    </InstantSearch>
  );
}

Synonyms and Rules

async function configureSynonyms() {
  await index.saveSynonyms([
    {
      objectID: "phone-synonyms",
      type: "synonym",
      synonyms: ["phone", "mobile", "cell", "smartphone"],
    },
    {
      objectID: "tv-oneway",
      type: "oneWaySynonym",
      input: "tv",
      synonyms: ["television", "flat screen"],
    },
  ]);
}

// Query rules — pin results or modify ranking for specific queries
async function configureRules() {
  await index.saveRule({
    objectID: "promo-banner",
    conditions: [{ anchoring: "contains", pattern: "sale" }],
    consequence: {
      userData: { banner: "Summer sale — 20% off everything!" },
      promote: [{ objectID: "featured-sale-item", position: 0 }],
    },
  });
}

Incremental Updates and Partial Updates

async function partialUpdate(productId: string, updates: Partial<Product>) {
  await index.partialUpdateObject({
    objectID: productId,
    ...updates,
  });
}

// Batch partial updates for price changes
async function bulkPriceUpdate(priceMap: Map<string, number>) {
  const updates = Array.from(priceMap.entries()).map(([id, price]) => ({
    objectID: id,
    price,
  }));
  await index.partialUpdateObjects(updates);
}

Search Analytics

// Send click and conversion events for AI re-ranking (Algolia Recommend)
import aa from "search-insights";

aa("init", { appId: "YOUR_APP_ID", apiKey: "YOUR_SEARCH_ONLY_KEY" });

function trackClick(queryID: string, objectID: string, position: number) {
  aa("clickedObjectIDsAfterSearch", {
    index: "products",
    eventName: "Product Clicked",
    queryID,
    objectIDs: [objectID],
    positions: [position],
  });
}

function trackConversion(queryID: string, objectID: string) {
  aa("convertedObjectIDsAfterSearch", {
    index: "products",
    eventName: "Product Purchased",
    queryID,
    objectIDs: [objectID],
  });
}

Best Practices

  • Use the search-only API key on the frontend. Never expose the admin key in client code. Generate scoped keys for multi-tenant apps with client.generateSecuredApiKey().
  • Push full records, not deltas. When reindexing, use replaceAllObjects for zero-downtime atomic swaps of the entire index.
  • Keep records small. Only index fields you search or filter on. Store large blobs elsewhere and reference them by ID.
  • Set searchableAttributes explicitly. Without this, Algolia searches all fields, diluting relevance.
  • Use filterOnly for attributes you filter but never display as facets. This saves processing.
  • Leverage query rules for merchandising instead of hardcoding promotions in application logic.
  • Send analytics events (clicks, conversions) to enable Algolia's AI re-ranking and Recommend features.

Anti-Patterns

  • Using Algolia as a primary database. It is a search index. Always keep a source-of-truth datastore and sync to Algolia.
  • Proxying every search through your backend. This adds latency. Let the frontend query Algolia directly with a search-only key.
  • Indexing HTML or markdown. Strip markup before indexing; raw tags hurt relevance and highlighting.
  • Ignoring attributesToRetrieve. Returning all fields wastes bandwidth. Specify only what the frontend renders.
  • Over-relying on filters for access control. Use secured API keys with embedded filters for multi-tenant isolation instead of trusting client-side filter parameters.
  • Updating single records in a loop. Use saveObjects or partialUpdateObjects in batches for bulk changes.

Install this skill directly: skilldb add search-services-skills

Get CLI access →