Guides / Building Search UI / Going further

Improve performance for React InstantSearch

This is the React InstantSearch v7 documentation. React InstantSearch v7 is the latest version of React InstantSearch and the stable version of React InstantSearch Hooks.

If you were using React InstantSearch v6, you can upgrade to v7.

If you were using React InstantSearch Hooks, you can still use the React InstantSearch v7 documentation, but you should check the upgrade guide for necessary changes.

If you want to keep using React InstantSearch v6, you can find the archived documentation.

Algolia is fast by default. But network speed and bandwidth can vary. This page lists a few best practices you can implement to adapt to your users’ network conditions.

Prepare the connection to Algolia

When sending the first network request to a domain, a security handshake must happen, consisting of several round trips between the client and the Algolia server. If the handshake first happened when users typed their first keystroke, the speed of that first request would be significantly slower.

Use a preconnect link to carry out the handshake immediately after loading the page but before any user interaction. To do this, add a link tag with your Algolia domain in the head of your page.

1
2
3
4
<link crossorigin href="https://YOUR_APPLICATION_ID-dsn.algolia.net" rel="preconnect" />

<!-- For example -->
<link crossorigin href="https://B1G2GM9NG0-dsn.algolia.net" rel="preconnect" />

Customize the loading indicator

By default, the <SearchBox> displays a loading indicator when the search is stalled. When the network is slow, this visual cue tells users that something is happening

You can change the loading icon with the loadingIconComponent prop.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import React from 'react';
import { liteClient as algoliasearch } from 'algoliasearch/lite';
import { Hits, InstantSearch, SearchBox } from 'react-instantsearch';

const searchClient = algoliasearch('YourApplicationID', 'YourSearchOnlyAPIKey');

function App() {
  return (
    <InstantSearch indexName="instant_search" searchClient={searchClient}>
      <SearchBox loadingIconComponent={() => 'Loading…'} />
      <Hits />
    </InstantSearch>
  );
}

Turn off search-as-you-type

Algolia is designed to deliver a search-as-you-type experience. Yet, it can also lead to lag in slow network conditions because browsers can only run a limited number of parallel requests to the same domain. Reducing requests can help prevent further lag.

Debouncing helps you limit requests and avoid processing non-necessary ones by only sending requests once users have stopped typing.

Implement debouncing at the <SearchBox> level with the queryHook prop. For example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import React from 'react';
import { liteClient as algoliasearch } from 'algoliasearch/lite';
import { Hits, InstantSearch, SearchBox } from 'react-instantsearch';

const searchClient = algoliasearch('YourApplicationID', 'YourSearchOnlyAPIKey');

let timerId = undefined;
let timeout = 200;

function App() {
  return (
    <InstantSearch indexName="instant_search" searchClient={searchClient}>
      <SearchBox
        queryHook={queryHook}
      />
      <Hits />
    </InstantSearch>
  );
}

function queryHook(query, search) {
  if (timerId) {
    clearTimeout(timerId);
  }

  timerId = setTimeout(() => search(query), timeout);
}

When network speeds improve, you should restore search-as-you-type to offer users the best possible experience. You can use the Network Information API to detect connection changes.

The Network Information API is unavailable on some browsers.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
import React, { useEffect } from 'react';
import { liteClient as algoliasearch } from 'algoliasearch/lite';
import { Hits, InstantSearch, SearchBox } from 'react-instantsearch';

const searchClient = algoliasearch('YourApplicationID', 'YourSearchOnlyAPIKey');

const connection = navigator.connection;
let timerId = undefined;
let timeout = 0;

updateTimeout();

function App() {
  useEffect(() => {
    connection.addEventListener('change', updateTimeout);

    return () => connection.removeEventListener('change', updateTimeout);
  });

  return (
    <InstantSearch indexName="instant_search" searchClient={searchClient}>
      <SearchBox queryHook={queryHook} />
      <Hits />
    </InstantSearch>
  );
}

function queryHook(query, search) {
  if (timerId) {
    clearTimeout(timerId);
  }

  timerId = setTimeout(() => search(query), timeout);
}

function updateTimeout() {
  timeout = ['slow-2g', '2g'].includes(connection?.effectiveType) ? 200 : 0;
}

Server-side rendering

Server-side rendering (SSR) lets you generate HTML from InstantSearch components on the server. Before loading the page for the first time, a backend server makes an initial request to Algolia, renders them as HTML, and sends it to the browser.

SSR is beneficial in slow network conditions because the browser directly loads an HTML document containing search results. There’s no need to wait for all the JavaScript assets to load before seeing the search results.

Optimize build size

InstantSearch supports dead code elimination through tree shaking, but you must follow a few rules for it to work:

  • Bundle your code using a module bundler that supports tree shaking with the sideEffects property in package.json, such as Rollup or webpack 4+.
  • Make sure you pick the ES module build of InstantSearch by targeting the module field in package.json (resolve.mainFields option in webpack, mainFields option in @rollup/plugin-node-resolve). This is the default configuration in most popular bundlers: you only need to change something if you have a custom configuration.
  • Keep Babel or other transpilers from transpiling ES6 modules to CommonJS modules. Tree shaking is much less optimal on CommonJS modules, so it’s better to let your bundler handle modules by itself.

If you’re using Babel, you can configure babel-preset-env not to process ES6 modules:

1
2
3
4
5
6
7
8
9
10
module.exports = {
  presets: [
    [
      'env',
      {
        modules: false,
      },
    ],
  ],
}

If you’re using the TypeScript compiler (tsc):

1
2
3
4
5
{
  "compilerOptions": {
    "module": "esnext"
  }
}

Troubleshooting

To check if tree shaking works, try to import InstantSearch into your project without using it.

1
import { InstantSearch } from 'react-instantsearch'; // Unused import

Build your app, then look for the unused code in your final bundle (for example, “InstantSearch”). If tree shaking works, you shouldn’t find anything.

Queries per second (QPS)

Search operations aren’t limited by a fixed “search quota”. Instead, they’re limited by your plan’s maximum QPS and operations limit.

Every keystroke in InstantSearch using the SearchBox counts as one operation. Then, depending on the widgets you add to your search interface, you may have more operations being counted on each keystroke. For example, you can expect additional network requests with DynamicWidgets.

If you experience QPS limitations, consider implementing a debounced <SearchBox>.

Did you find this page helpful?