Docs
Tutorials
Build a Next.js App

Get Started

In this walkthrough we are going to get started to build a search experience with Next.js.

You dont need to use Next.js to use Searchkit, but it is the easiest way to get started.

In this walkthrough, we will:

  • Setup an api route to fetch results from Elasticsearch
  • Use React Instantsearch to display the results 🎉

Download an Example Project

You can check out a Next.js project with Searchkit here:

curl https://codeload.github.com/searchkit/searchkit/tar.gz/main | \
tar -xz --strip=2 searchkit-main/examples/with-ui-nextjs-react

or view the example codebase on github here (opens in a new tab)

Code Sandbox Example

You can also check out the code sandbox example here:

Create a Next.js app

First, we need to create a Next.js app. We can do this by running the following command:

npx create-next-app@latest

and follow the instructions.

Navigate to the newly created directory.

Install Dependencies

Next we need to install the dependencies for this project:

  npm install @searchkit/instantsearch-client @searchkit/api react-instantsearch-dom

Setup the Node API

create a new file in the pages/api directory called search.js and add the following code:

import Client from "@searchkit/api";
 
const apiConfig = {
  connection: {
    host: "<replace-with-your-elasticsearch-host>",
    apiKey: "<replace-with-your-elasticsearch-api-key>",
  },
  search_settings: {
    highlight_attributes: ["title", "actors"],
    search_attributes: ["title", "actors"],
    result_attributes: ["title", "actors"],
    facet_attributes: ["type", "rated"],
  },
};
 
const apiClient = Client(apiConfig);
 
export default async function handler(req, res) {
  const results = await apiClient.handleRequest(req.body);
  res.send(results);
}

Replace the host and apiKey with your Elasticsearch host and API key. The apiKey is optional, but recommended for production environments. You can find more information about the API key here (opens in a new tab).

This will setup a new nextjs api route (opens in a new tab) under the /api/search path. This route will handle the search requests and use instantsearch Elasticsearch Adapter to handle the requests. The response is then returned back to the client.

For more information on API configuration, see the API Configuration docs.

Setup the Frontend

Now that we have the API setup, we can start building the frontend. We will use react-instantsearch-dom (opens in a new tab) to build the search experience.

First, we need to create a new file in the pages directory called search.js and add the following code:

import { InstantSearch, SearchBox, Hits } from "react-instantsearch-dom";
import createClient from "@searchkit/instantsearch-client";
 
const searchClient = createClient({
  url: "/api/search",
});
 
export default function Search() {
  return (
    <InstantSearch
      searchClient={searchClient}
      indexName="<elasticsearch index or alias name>"
    >
      <SearchBox />
      <Hits />
    </InstantSearch>
  );
}

Instantsearch will use the searchClient to make requests to the API we created earlier. The indexName is the name of the index we want to search.

Run the app

Now that we have everything setup, we can run the app and see the search experience in action.

npm run dev

IMAGE1

Searchable Attributes

Now that we have the search experience setup, we can add the search functionality.

Adjusting the search fields

we can adjust the search fields by updating the search_attributes in the apiConfig object in the pages/api/search.js file.

  search_attributes: ["title^3", "actors", "plot"],

Above we have boosted title by 3 times. This means that the title will have a higher weight than the other fields. This will make sure that the title has a higher importance in the search results.

Overriding the default Elasticsearch query

We can optionally override the default search query by implementing the getQuery function in the handleRequest method called in pages/api/search.js file.

this function will receive the query and the function will return the Elasticsearch query that will be used to search the index.

const results = await apiClient.handleRequest(body, {
  getQuery: (query, search_attributes) => {
    return [
      {
        combined_fields: {
          query,
          fields: search_attributes,
        },
      },
    ];
  },
});

Customise Results Hit

We can add a custom hit component to display the results. We can create a new file called Hit.js in the components directory and add the following code:

Below we are using the Highlight component from react-instantsearch-dom to highlight the search term in the title and actors fields.

index.js
import { Highlight } from "react-instantsearch-dom";
 
const hitView = (props) => {
  return (
    <div>
      <h2>
        <Highlight hit={props.hit} attribute="title" />
      </h2>
      <br />
 
      <Highlight hit={props.hit} attribute="actors" />
    </div>
  );
};

We need to pass the attribute prop to the highlight_attributes config to tell which fields to bring highlight options for.

pages/api/search.js
  highlight_attributes: ["title", "actors"],

then we can import the Hit component in the pages/search.js file and pass it to the Hits component.

import Hit from "../components/Hit";
 
export default function Search() {
  return (
    <InstantSearch searchClient={searchClient} indexName="movies">
      <SearchBox />
      <Hits hitComponent={Hit} />
    </InstantSearch>
  );
}

IMAGE3

Facets

Adding a Refinement List Facet

Start by updating the apiConfig object in the pages/api/search.js file to add the type facet.

pages/api/search.js
  facet_attributes: ["type"],

This assumes there is a type field in the index that is a keyword type field.

If the field is a text type field, you can define and use the type.keyword subfield instead.

  facet_attributes: [{ attribute: "type", field: "type.keyword" }],

Then we can add the RefinementList component to the pages/search.js file.

import {
  InstantSearch,
  SearchBox,
  Hits,
  RefinementList,
} from "react-instantsearch-dom";
 
export default function Search() {
  return (
    <InstantSearch searchClient={searchClient} indexName="movies">
      <SearchBox />
      <RefinementList attribute="type" />
      <Hits hitComponent={Hit} />
    </InstantSearch>
  );
}

IMAGE4

Make it searchable

By default, the RefinementList component will show all the values for the facet. We can make it searchable by adding the searchable prop.

<RefinementList attribute="type" searchable />

Adding a Numeric Range based Facet

Start by updating the apiConfig object in the pages/api/search.js file to add the imdbrating facet. This requires the imdbrating field to be a numeric type field like a float in the Elasticsearch index.

facet_attributes: [
  { attribute: "imdbrating", type: "numeric" },
  { attribute: "type", field: "type.keyword" }
],

Then we can add the NumericMenu component to the pages/search.js file.

import {
  InstantSearch,
  SearchBox,
  Hits,
  NumericMenu,
} from "react-instantsearch-dom";
 
<NumericMenu
  attribute="imdbrating"
  items={[
    { label: "5 - 7", start: 5, end: 7 },
    { label: "7 - 9", start: 7, end: 9 },
    { label: ">= 9", start: 9 },
  ]}
/>;

Summary

We have quickly built a really nice search experience from scratch using Elasticsearch and Algolia Instantsearch. We have also learned how to customize the search experience by adjusting the search fields and overriding the default Elasticsearch query.


Apache 2.0 2023 © Joseph McElroy.
Need help? Join discord