Guides / Building Search UI / Going further / Improve performance

Improve Performance for React InstantSearch Hooks

Front-end performance is a crucial aspect of delivering exceptional search to end users. Search is the quickest access to content on a website, meaning users expect it to be instant.

While Algolia offers unparalleled search speed, you can further increase perceived performance by implementing a set of best practices and adapting to the user’s network conditions.

Preparing the connection to Algolia

When sending the first network request to a domain, a security handshake, which consists of several round trips between the client and the Algolia server, happens. If the handshake first happens when the user presses their first keystroke, this first request is significantly slower.

You can use a preconnect <link> to carry out the handshake right after the page loads, before any user interaction. 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" />

Mitigate the impact of a slow network

Algolia is a hosted search API, so a slow network affects the experience. Still, there are ways to make the user’s performance perception despite adverse network conditions.

Customizing the loading indicator

By default, <SearchBox> displays a loading indicator when the search is stalled. This provides a visual cue to hint the user that something is happening even though the network is taking a while to respond.

You can change the loading icon using the loadingIconComponent prop.

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

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

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

Disabling as-you-type

Algolia is designed for as-you-type, which is recommended for an optimal 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 the user stops typing. There’s no built-in solution to debounce in React InstantSearch Hooks, but you can implement it at the <SearchBox> level using the queryHook prop.

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
import React from 'react';
import algoliasearch from 'algoliasearch/lite';
import { Hits, InstantSearch, SearchBox } from 'react-instantsearch-hooks-web';

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

let timerId = undefined;

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 users are on a fast Internet connection, you might want to go back to as-you-type to offer the best possible experience.

You can leverage the Network Information API to detect connection changes.

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 algoliasearch from 'algoliasearch/lite';
import { Hits, InstantSearch, SearchBox } from 'react-instantsearch-hooks-web';

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) ? 400 : 0;
}

The Network Information API is still an experimental technology and isn’t available on all browsers.

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 back-end server makes an initial request to Algolia, renders them as HTML and sends it to the browser.

SSR is particularly useful 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

React InstantSearch Hooks supports dead code elimination via tree shaking. Make sure your app is properly set up to take advantage of it:

  • Bundle your code using a module bundler that supports tree shaking via the sideEffects property in package.json, such as Rollup or webpack 4+.
  • Make sure to pick the ES module build of React InstantSearch Hooks 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, so you shouldn’t need to change anything unless you have a custom configuration.
  • Keep Babel or other transpilers from transpiling ES6 modules to CommonJS modules. Tree shaking is much less optimal with CommonJS, it’s preferable 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), you can configure it to generate ES6 modules.

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

To ensure tree shaking works, try to import widgets in your project without using them.

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

Then, build your application and 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 but they’re subject to the maximum QPS and the operations limit of your plan.

When using the <SearchBox> widget with InstantSearch, each keystroke triggers one operation. Then, depending on the widgets you add to your search UI, you may generate more operations at once. For example, when using <DynamicWidgets> with the facets prop set to [] to avoid requesting too many facets, an extra network request happens on mount.

If you’re having issues with the QPS, you can consider implementing a debounced <SearchBox>.

Next steps

You now have a good starting point to create an even more performant experience with React InstantSearch Hooks. Next up, you could improve this app by:

Did you find this page helpful?