Integrate a seamless search experience in an NextJS e-commerce application with Meilisearch
Tags: nextjs, meilisearch
Published: 2023-04-01 02:52:24

Drastically improve your users' product discovery experience with our easy-to-use and easy-to-deploy search engine.

### Introduction

Searching is a crucial feature of any e-commerce application which directly affects conversions. A good search experience requires quick and accurate search results. However, it requires investing time and developer resources to build. Here's when Meilisearch enters the picture—providing an open-source search engine that is lightning fast, hyper-relevant, and typo-tolerant with little to no setup time.

In this tutorial, we'll add a seamless search experience with Meilisearch to a light e-commerce application. We will import a list of 1,000 products to Meilisearch. Users can search through these products and benefit from advanced features like filtering and sorting options.

Upon completion, you will have an app similar to our demo application at [http://ecommerce.meilisearch.com](http://ecommerce.meilisearch.com/), which provides rapid search results on a catalog of over 1.4 million products.

## ****Prerequisites****

1. [NodeJs](https://nodejs.org)
2. [Next.js](https://nextjs.org/) - React Framework 
3. [Meilisearch](https://www.meilisearch.com/)

# Getting Started

Let's get started with the installation of the necessary tools.

### 1. ****Install and launch Meilisearch****

You have many ways to [install Meilisearch](https://docs.meilisearch.com/learn/getting_started/quick_start.html#download-and-launch). One of the methods is to use [cURL](https://curl.se/) for the installation.

You can paste the following code into your terminal:

```bash
# Install Meilisearch
curl -L https://install.meilisearch.com | sh

# Launch Meilisearch
./meilisearch
```

This command will launch a local Meilisearch server at `http://localhost:7700/`.

### 2. Adding data to Meilisearch

Create and navigate to a new folder called `seed`. You can use the following command to install [Meilisearch's Javascript client](https://github.com/meilisearch/meilisearch-js) using npm.

```jsx
npm install meilisearch
```

We have gathered a list of 1,000 products from various Amazon datasets and compiled them in a `data.json` file. We will add these products to the Meilisearch index.

You can download this `data.json` file from [GitHub](https://github.com/shivaylamba/demos/tree/main/src/ecommerce/seed/data.json).

Each record is associated with a product. Each product has a *brand, category, tag, price, rating*, and other related information. We will make these attributes sortable and filterable in our Meilisearch instance via `[updateFilterableAttributes](https://docs.meilisearch.com/reference/api/filterable_attributes.html#update-filterable-attributes)` and `[updateSortableAttributes](https://docs.meilisearch.com/reference/api/sortable_attributes.html#update-sortable-attributes)` methods.

```jsx
/* Here's an example of a product record in the data.json file */

[
	{
		"id": "711decb2a3fdcbbe44755afc5af25e2f",
    "title": "Kitchenex Stainless Steel Flatware Pie Server and Pie Cutter Set of 2",
    "description": "The Kitchen Stainless Pie Server is both flashy and chic...",
		"category": "Home & Kitchen",
    "brand": "Dr. Pet",
    "price": 16.84,
		"tag": "Kitchen",
    "rating": 4.7,
    "reviews_count": 7
	},
	...999 more items
]
```

Let’s create a script to add the data from the `data.json` file to a `products` index in Meilisearch. Here’s the code of the script:

```jsx
const { MeiliSearch } = require("meilisearch");

const client = new MeiliSearch({
  host: "http://localhost:7700",
  apiKey: "", // No API key has been set
});

const INDEX_NAME = "products";

const index = client.index(INDEX_NAME);

const data = require("./data.json");

(async () => {
  console.log(`Adding Filterable and Sortable Attributes to "${INDEX_NAME}"`);
  await index.updateFilterableAttributes([
    "brand",
    "category",
    "tag",
    "rating",
    "reviews_count",
    "price",
  ]);
  await index.updateSortableAttributes(["reviews_count", "rating", "price"]);

  console.log(`Adding Documents to "${INDEX_NAME}"`);
  await index.updateDocuments(data);
	console.log('Documents Added');
})();
```

If you called your script `seed.js`, you could run the command below to start adding data to your Meilisearch instance:

```bash
node seed.js
```

Wait until the data is ingested into the Meilisearch instance. It is usually done in a few seconds. You may go to `[http://localhost:7700/](http://localhost:7700/)` to check the list of 1000 products in the Meilisearch.


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mys4x6y57w2crrtj0mg5.png)

### 3. Set up the project

Let's build an e-commerce application in [Next.js](https://nextjs.org/). N*ext.js* is an open-source React framework that enables server-side rendering and static website generation.

We can set up a Next.js application using the [create-next-app](https://nextjs.org/docs/api-reference/create-next-app) tool. Navigate to your project base directory in the terminal, and use the following command to install a *Next.js* application:

```bash
npx create-next-app@latest ecommerce
```

It may take a few minutes to complete. This command will create a folder named `ecommerce` in your project base directory with all the boilerplate code. Navigate to  the `ecommerce` folder and install the following npm libraries:

1. `react-instantsearch-dom` -  an open-source library that uses [InstantSearch.js](https://www.algolia.com/doc/guides/building-search-ui/what-is-instantsearch/js) to build search interfaces in front-end applications quickly.
2. `@meilisearch/instant-meilisearch` - It's a client for `InstantSearch.js`. It establishes communication between *Meilisearch* and *InstantSearch*.

You can install these libraries using the following command:

```bash
npm i --save react-instantsearch-dom @meilisearch/instant-meilisearch
```

You may add some pre-defined stylesheet files. You can download all the CSS modules from [here](https://github.com/shivaylamba/demos/tree/main/src/ecommerce/styles) and paste all the files into the `styles` folder in the base directory.

**Important** **Note:**

As of Next.js v12 and above, `react-instantsearch-dom` is not working as expected when [React’s Strict mode](https://reactjs.org/docs/strict-mode.html) is enabled (refer to [link](https://discourse.algolia.com/t/react-instantsearch-not-working/15053/2)). A quick and easy fix for this is to disable strict mode. You can disable it by adding `reactStrictMode: false` to `next.config.js`. You may find this file at the root of your project.

```jsx
/* next.config.js */

module.exports = {
  reactStrictMode: false, // set this to false
  images: {
    domains: [''],
  },
}
```

To apply modifications, restart the Next.js server.

### 4. Building and integrating components

We have completed the initial step of seeding the data to the Meilisearch instance and installing the necessary tools. From here, we will start integrating the components required for creating a search experience in the application.

### 4.1 Adding search box and connecting components to Meilisearch

We can create a navigation bar that contains a search box to perform searches within the application. We will use the `SearchBox` component from the `react-instantsearch-dom` library to do so.

You can create a `navbar.jsx` file in the `components/layout` folder and use the following code in it:

```jsx
import React from 'react';
import { SearchBox } from 'react-instantsearch-dom';
import styles from '../../styles/nav-bar.module.css';

export const NavBar = () => {
  return (
    <div className={styles.container}>
      <SearchBox />
    </div>
  );
};

export default NavBar;
```

We must connect the `SearchBox` component to Meilisearch. We will create a `searchClient` with the necessary credentials and pass it to the `InstantSearch` component as a prop along with the index name.

To do this, you can create a `layout.jsx` file in the `components/layout/` folder and use the following code in it:

```jsx
import React from 'react';
import NavBar from './navbar';
import { InstantSearch } from 'react-instantsearch-dom';
import { instantMeiliSearch } from '@meilisearch/instant-meilisearch';

const searchClient = instantMeiliSearch('http://localhost:7700', '');

const Layout = ({ children }) => {
  return (
    <InstantSearch indexName="products" searchClient={searchClient}>
      <NavBar />
      <main>{children}</main>
    </InstantSearch>
  );
};

export default Layout;
```

`Next.js` uses the [App](https://nextjs.org/docs/advanced-features/custom-app) component to initialize pages. Every time the server is launched, `_app.js` is the primary file rendered. We will add the `Layout` component and a few stylesheet files to the `_app.js` file. Adding the `Layout` component will supply the search results to all the child components.

You may replace the code in the `_app.js` file with the following code:

```jsx
import Layout from "../components/layout/layout";
import "../styles/globals.css";
import "../styles/searchBoxAIS.css";

function MyApp({ Component, pageProps }) {
  return (
    <Layout>
      <Component {...pageProps} />
    </Layout>
  );
}

export default MyApp;
```

### 4.2 Creating various search filters

Let’s add a filter functionality to the application. We will need a few components from the `react-instantsearch-dom` library:

1. The `RefinementList` component can be used to filter the data based on [facets](https://docs.meilisearch.com/learn/advanced/filtering_and_faceted_search.html). The number of items related to the given filter will also be displayed.
    
    ```jsx
    <RefinementList attribute="category" />
    ```
    
2. The `RatingMenu` component creates a rating list. We need to define what’s the maximum rating. It displays a select menu *(starting from 1 star till max)* to select the rating. The component only works with integers.
    
    ```jsx
     <RatingMenu attribute="rating" max={5} />
    ```
    
3. The `ClearRefinements` component will be used to provide a button that will clear all the applied filters within the application. This is handy when we've applied many filters and want to delete them all at once. 
    
    ```jsx
    <ClearRefinements />
    ```
    

We will add all three components to implement the filter functionality. You can create a new file `SearchFilters.jsx` in the `components/home/` folder and use the following code:

```jsx
import {
  ClearRefinements,
  RatingMenu,
  RefinementList,
} from 'react-instantsearch-dom';

const SearchFilters = () => (
  <div>
    <h2>
      <span>Filters</span>
      <ClearRefinements />
    </h2>
    <h4>Categories</h4>
    <RefinementList attribute="category" />
    <h4>Tags</h4>
    <RefinementList attribute="tag" />
    <h4>Brands</h4>
    <RefinementList attribute="brand" />
    <h4>Rating</h4>
    <RatingMenu attribute="rating" max={5} />
  </div>
);

export default SearchFilters;
```

### 4.3 Implementing a product card component

We will create a component named `Hit` to represent a single product. We may design a card view for the component that will include the basic information for the product, like - *title, description, rating, review count, price, brand,* and *image*. All these details can be retrieved from the component’s `product` prop. After that, we can display the results by looping over the component.

We can create a file `hits.jsx` in the `components/home/` folder. You can paste the following lines of code into the file:

```jsx
import styles from "../../styles/searchResult.module.css";

const Hit = ({ product }) => {
  const { rating, images, title, description, brand, price, reviews_count } = product;

  return (
    <div className={styles.card}>
      <div
        className={styles.productResImg}
        style={{ backgroundImage: `url(${images[0]})` }}
      />
      <div className={styles.productResCntnt}>
        <h6 className={styles.productResBrand}>{brand}</h6>
        <div className={styles.productResTitl}>{title}</div>
        <div className={styles.productResDesc}>
          {description.substring(0, 50)}...
        </div>
        <div className={styles.productResPrice}>${price}</div>
        <div className={styles.productResReview}>
          {reviews_count ? (
            <div className={styles.productRateWrap}>
              <span className={styles.productRate}>
                {reviews_count} review
              </span>{" "}
              <span>⭐ {rating}</span>
            </div>
          ) : (
            "No Review"
          )}
        </div>
      </div>
    </div>
  );
};

export default Hit;
```

### 4.4 Designing a page layout to display the list of products

It’s time to design the product list page with a sort feature and pagination to display a limited number of products on a page. For this, we can use the following components from the `react-instantsearch-dom` library:

1. The `Pagination` component, by default, allows you to display a list of 20 products per page.  We can also provide the `showLast` property to display an estimate of the number of pages.
    
    ```jsx
    <Pagination showLast={true} />
    ```
    

1. The `SortBy` component will sort the search results based on the facets. We need to provide a Meilisearch index and a list of `value` and `label`.
    
    Let's say we need to sort the `category` in the index `products` alphabetically. You may use the following code:
    
    ```jsx
    <SortBy
      defaultRefinement="products"
      items={[
        { value: 'category', label: 'Sort category alphabetically' },
      ]}
    />
    ```
    
    The `SortBy` has a few properties, including the `defaultRefinement` attribute for providing the Meilisearch index and the `items` attribute to provide a list of `value` and `label`.
    
    The `value` includes the [document's attribute](https://docs.meilisearch.com/learn/core_concepts/documents.html#structure), and as the name suggests, we will use the `label` to add label text in a dropdown list.
    

1. The `connectStateResults` hook will retrieve the search results from the Meilisearch instance using `InstantSearch`. The `connectStateResults` function has some arguments, including:
    - `searchState` provides the information about the user's input in the provided search box.
    - `searchResults` gives the result obtained after querying the Meilisearch instance.
    - The `searching` argument represents the loading state for the result retrieval from the Meilisearch.

We will use the `searchState` argument to get the list of products and loop the list using the `map` function over the `Hit` component to display the result. We will use the `SortBy` component to sort based on `price` and `reviews_count`.

We can create a file `SearchResult.jsx` in the `component/home/` folder and use the following code:

```jsx
import {
  Pagination,
  SortBy,
  connectStateResults,
} from 'react-instantsearch-dom';
import SearchFilters from './SearchFilters';
import Hit from "./hits.jsx";
import styles from '../../styles/searchResult.module.css';

const Results = connectStateResults(
  ({ searchState, searchResults, searching }) => {
    const hits = searchResults?.hits;

    if (searching) {
      // This will show the loading state till the results are retrieved
			return 'Loading';
    }

    return (
      <>
        <SearchFilters />
        <div className={styles.products}>
					{/* Condition for rendering component based on results */}
          {searchResults?.nbHits !== 0 ? (
            <>
              <div className={styles.resultPara}>
                <span>
                  Showing {searchResults?.hits.length || 0} of{' '}
                  {searchResults?.nbHits || 0}{' '}
                  {searchState.query &&
                    !searching &&
                    `for "${searchState.query}"`}
                </span>
                <div>
                  <SortBy
                    defaultRefinement="products"
                    items={[
                      { value: 'products', label: 'Sort' },
                      {
                        value: 'products:price:desc',
                        label: 'Price: High to Low',
                      },
                      {
                        value: 'products:price:asc',
                        label: 'Price: Low to High',
                      },
                      {
                        value: 'products:reviews_count:desc',
                        label: 'Most Reviews',
                      },
                    ]}
                  />
                </div>
              </div>
              <div className={styles.grid}>
								{/* Using search result list, we will loop over the Hit component */}
                {hits?.length > 0 &&
                  hits.map((product) => (
                    <Hit key={product.id} product={product} />
                  ))}
              </div>
            </>
          ) : (
            <p className={styles.paragraph}>
              No results have been found for {searchState.query}.
            </p>
          )}
					{/* Adding pagination over the results */}
          <Pagination showLast={true} />
        </div>
      </>
    );
  }
);
export default Results;
```

### 4.5 Displaying results on the application

We have created all the required components to display the results. We need to add the `Results` component to the `index.jsx` file to display the result on the screen. You can copy and paste the following code into the `index.jsx` file.

```jsx
import styles from '../styles/home.module.css'
import Results from '../components/home/SearchResult'

export default function Home () {
  return (
    <div className={styles.container}>
      <main className={styles.main}>
        <div className={styles.mainContent}>
          <Results />
        </div>
      </main>
    </div>
  )
}

export const getStaticProps = async () => {
  return {
    props: {}
  }
}
```

You can use the following command to run the application:

```jsx
npm run dev 
```

Go to [http://localhost:3000](http://localhost:3000). The output will resemble the image provided below:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6ng03flr0676gpnlsy1l.gif)

You can find the complete code here, [https://github.com/shivaylamba/demos/tree/main/src/ecommerce](https://github.com/shivaylamba/demos/tree/main/src/ecommerce).

## Conclusion

We created a lightning-fast search experience for an e-commerce application. You can now take the learnings from above and integrate a similar search experience into your applications. You may play with typo-tolerance, geo-search filters, and many other features to better suit your needs.

Here are some references of real-world examples of Meilisearch used in e-commerce experiences:

1. [https://www.minipouce.fr/i/marketplace/explore](https://www.minipouce.fr/i/marketplace/explore)
2. [https://palmes.co/](https://palmes.co/)

We're excited to see what you come up with. Share your experience and Meilisearch integration in your e-commerce application on the [Slack community](https://meilicommunity.slack.com/join/shared_invite/zt-19l5kemhc-IFXAhC4T44arR6uRGSWDVQ#/shared-invite/email).

If you have any queries or suggestions, please let us know [on Slack](https://slack.meilisearch.com/). For more information on Meilisearch, check out our [Github repository](https://github.com/meilisearch/meilisearch/) and official [documentation](https://docs.meilisearch.com/).”