Integrate Third-Party Search with Algolia

This guide shows you how to replace Catalyst’s built-in product search with Algolia. You’ll learn how to:

  • Connect your BigCommerce data with Algolia
  • Replace Catalyst’s default search experience
  • Use the Algolia JavaScript API client to fetch search results on the server

By the end of this guide, you’ll have a search experience powered by Algolia, with product and category results rendered in your Catalyst store’s existing search UI.

Algolia Demo

Prerequisites

  • An Algolia account.
  • A Catalyst project running locally. See Getting Started.
  • Products in your Catalyst channel’s catalog. If you don’t have any, choose Use sample data when you create your Catalyst channel.

Create an application in Algolia

In your Algolia dashboard, create a new application. You can start with the free Build plan.

Create new algolia application
  1. Choose a region based on your location (for example, US East)
  2. Click Next: Review & Confirm
  3. Click Create Application

You should see a Congratulations page which indicates your application was created successfully.

Connect Algolia to BigCommerce

Next, create an Algolia index for your Catalyst channel’s products. An index is where Algolia stores the data it searches. From the Algolia docs:

An index is the equivalent of a table in a database but optimized for search and discovery operations.

The Algolia Single-Click App for BigCommerce creates the index for you.

Due to the 10 KB limit on Algolia Record sizes for the Build plan, unassign the 3 Plant Bundle product from your Catalyst channel’s catalog if you chose Use sample data when creating your Catalyst channel. Otherwise, indexing fails.

  1. Open the Algolia Single-Click App
  2. Click Get this App and log into your BigCommerce store
  3. Click Install
  4. Review and accept the API scopes that the Algolia app requires to properly function
  5. Click Login With Algolia and log into your Algolia account
  6. Select the application you created from the dropdown and click Next step: Channel selection
  7. Choose your BigCommerce channel
  8. Name your Algolia index using Product level indexing as the Record type and click Save
  9. Click Yes, index now to start indexing your products

You can find your new index in Algolia under Data Sources > Indices.

View index in Algolia

Set environment variables

In your Algolia dashboard, navigate to the Overview tab and copy your Application ID and Search API Key.

Algolia application overview showing the Application ID and Search API Key

Add the following environment variables to your .env.local file:

# ...existing env variables...
ALGOLIA_APPLICATION_ID="<YOUR_ALGOLIA_APPLICATION_ID>"
ALGOLIA_SEARCH_API_KEY="<YOUR_ALGOLIA_SEARCH_API_KEY>"
ALGOLIA_INDEX_NAME="<YOUR_ALGOLIA_INDEX_NAME>"

These are used by the Algolia search client, which runs on the server.

Install Algolia dependencies

From the root of your Catalyst project, install the Algolia API client:

pnpm add algoliasearch

If your project uses the older monorepo structure, run this from the core/ directory, or use pnpm --filter "./core" add algoliasearch from the repository root. File paths in this guide are relative to that directory.

To replace Catalyst’s search with Algolia:

  1. Create an Algolia client.
  2. Modify the search results data transformer.
  3. Modify the search server action to fetch results from Algolia.

Create the Algolia client

Create a new directory lib/algolia and, inside, add a new file client.ts. Then, create and export a new Algolia client:

lib/algolia/client.ts
import { algoliasearch } from 'algoliasearch';
if (!process.env.ALGOLIA_APPLICATION_ID) {
throw new Error('ALGOLIA_APPLICATION_ID is required');
}
if (!process.env.ALGOLIA_SEARCH_API_KEY) {
throw new Error('ALGOLIA_SEARCH_API_KEY is not set');
}
const algoliaClient = algoliasearch(
process.env.ALGOLIA_APPLICATION_ID,
process.env.ALGOLIA_SEARCH_API_KEY
);
export default algoliaClient;

Modify the search results data transformer

Transform the data from Algolia into the shape Catalyst expects. For this, you need a type definition for the Algolia data and an algoliaResultsTransformer function to transform the data. Open data-transformers/search-results-transformer.ts and replace its contents with the following.

data-transformers/search-results-transformer.ts
// 1. Remove the imports required by the existing searchResultsTransformer
import { getFormatter, getTranslations } from 'next-intl/server';
import { SearchResult } from '@/vibes/soul/primitives/navigation';
// 2. Define the AlgoliaHit type returned by the Algolia API via the algoliaClient
export interface AlgoliaHit {
objectID: string;
name: string;
url: string;
product_images: Array<{
description: string;
is_thumbnail: boolean;
url_thumbnail: string;
}>;
categories_without_path: string[];
default_price: string;
prices: Record<string, number>;
sales_prices: Record<string, number>;
calculated_prices: Record<string, number>;
retail_prices: Record<string, number>;
}
// 3. Implement the algoliaResultsTransformer function
export async function algoliaResultsTransformer(
hits: AlgoliaHit[]
): Promise<SearchResult[]> {
// 4. Get the formatter and translations for the current locale
const format = await getFormatter();
const t = await getTranslations('Components.Header.Search');
// 5. Transform the Algolia hits into SearchResult objects
const products = hits.map((hit) => {
// Algolia stores prices per currency. This example formats the calculated
// USD price as a single display price.
const amount = hit.calculated_prices.USD ?? parseFloat(hit.default_price);
const price = format.number(amount, { style: 'currency', currency: 'USD' });
return {
id: hit.objectID,
title: hit.name,
href: hit.url,
price,
image: {
src:
hit.product_images.find((image) => image.is_thumbnail)
?.url_thumbnail ?? '',
alt: hit.product_images[0]?.description ?? '',
},
};
});
// 6. Create the product results SearchResult object
const productResults: SearchResult = {
type: 'products',
title: t('products'),
products,
};
// 7. For the returned product results, create a unique list of categories.
// For example, if you had two products, one with categories ['Electronics',
// 'Computers'] and another with ['Electronics', 'Cameras'], this step would
// produce ['Electronics', 'Computers', 'Cameras'].
const uniqueCategories = Array.from(
new Set(hits.flatMap((product) => product.categories_without_path))
);
// 8. Create the category results SearchResult object
const categoryResults: SearchResult = {
type: 'links',
title: t('categories'),
links: uniqueCategories.map((category) => {
return {
label: category,
href: `/${category.toLowerCase().replace(/\s+/g, '-')}`,
};
}),
};
// 9. Create an array to hold the final results
const results = [];
// 10. If there are any categories, add them to the results
if (categoryResults.links.length > 0) {
results.push(categoryResults);
}
// 11. If there are any products, add them to the results
if (products.length > 0) {
results.push(productResults);
}
return results;
}

Modify the search server action

To use the new transformer, update the components/header/_actions/search.ts file, which handles search on the server. Open that file and replace its contents with the following:

components/header/_actions/search.ts
'use server';
import { BigCommerceGQLError } from '@bigcommerce/catalyst-client';
import { SubmissionResult } from '@conform-to/react';
import { parseWithZod } from '@conform-to/zod';
import { strict } from 'assert';
import { getTranslations } from 'next-intl/server';
import { z } from 'zod';
import { SearchResult } from '@/vibes/soul/primitives/navigation';
// 1. Import required Algolia dependencies, including the transformer and client
import {
AlgoliaHit,
algoliaResultsTransformer,
} from '~/data-transformers/search-results-transformer';
import algoliaClient from '~/lib/algolia/client';
export async function search(
prevState: {
lastResult: SubmissionResult | null;
searchResults: SearchResult[] | null;
emptyStateTitle?: string;
emptyStateSubtitle?: string;
},
formData: FormData
): Promise<{
lastResult: SubmissionResult | null;
searchResults: SearchResult[] | null;
emptyStateTitle: string;
emptyStateSubtitle: string;
}> {
const t = await getTranslations('Components.Header.Search');
const submission = parseWithZod(formData, {
schema: z.object({ term: z.string() }),
});
const emptyStateTitle = t('noSearchResultsTitle', {
term: submission.status === 'success' ? submission.value.term : '',
});
const emptyStateSubtitle = t('noSearchResultsSubtitle');
if (submission.status !== 'success') {
return {
lastResult: submission.reply(),
searchResults: prevState.searchResults,
emptyStateTitle,
emptyStateSubtitle,
};
}
if (submission.value.term.length < 3) {
return {
lastResult: submission.reply(),
searchResults: null,
emptyStateTitle,
emptyStateSubtitle,
};
}
try {
// 2. Ensure the Algolia index name is set
strict(process.env.ALGOLIA_INDEX_NAME);
// 3. Send the search term from the form submission to Algolia instead of
// BigCommerce
const algoliaResults = await algoliaClient.searchSingleIndex<AlgoliaHit>({
indexName: process.env.ALGOLIA_INDEX_NAME,
searchParams: {
query: submission.value.term,
// 4. Add any filters you want to apply to the search results
filters: 'is_visible:true',
},
});
return {
lastResult: submission.reply(),
// 5. Transform the Algolia hits into SearchResult objects
searchResults: await algoliaResultsTransformer(algoliaResults.hits),
emptyStateTitle,
emptyStateSubtitle,
};
} catch (error) {
// eslint-disable-next-line no-console
console.error(error);
if (error instanceof BigCommerceGQLError) {
return {
lastResult: submission.reply({
formErrors: error.errors.map(({ message }) => message),
}),
searchResults: prevState.searchResults,
emptyStateTitle,
emptyStateSubtitle,
};
}
if (error instanceof Error) {
return {
lastResult: submission.reply({ formErrors: [error.message] }),
searchResults: prevState.searchResults,
emptyStateTitle,
emptyStateSubtitle,
};
}
return {
lastResult: submission.reply({
formErrors: [t('somethingWentWrong')],
}),
searchResults: prevState.searchResults,
emptyStateTitle,
emptyStateSubtitle,
};
}
}

Check your work

Save your changes, then run the development server:

pnpm dev

Open http://localhost:3000 and search for Plant. Your storefront shows the search results from Algolia.

Algolia Demo

Next steps