> ## Documentation Index
> Fetch the complete documentation index at: https://algolia.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Improve performance

> Learn how to optimize your InstantSearch.js app.

export const Records = () => <Tooltip tip="A record is a searchable object in an Algolia index. Each record consists of named attributes." cta="Algolia records" href="/doc/guides/sending-and-managing-data/prepare-your-data#algolia-records">
    records
  </Tooltip>;

export const Index = () => <Tooltip tip="An Algolia index is a searchable dataset that consists of records and configuration settings. These settings define how the records are searched and ranked.">
    index
  </Tooltip>;

Algolia is fast by default.
But network speed and bandwidth can vary.
This page lists 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 slower.

Use a [preconnect link](https://developer.mozilla.org/en-US/docs/Web/HTML/Link_types/preconnect) to carry out the handshake 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.

```html HTML icon=code-xml theme={"system"}
<link crossorigin href="https://YOUR_APPID-dsn.algolia.net" rel="preconnect" />

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

## Add a loading indicator

Consider a user accessing your app in a subway:

1. They type some characters
2. Nothing happens
3. They wait, but still, nothing happens

However, you can enhance the user experience by displaying a loading indicator to indicate something is happening.

To display a loading indicator in the `searchBox`, use the [`showLoadingIndicator`](/doc/api-reference/widgets/search-box/js#param-show-loading-indicator) option. The indicator will display slightly after the last query has been sent to Algolia. Change the duration of the delay with [`stalledSearchDelay`](/doc/api-reference/widgets/instantsearch/js#param-stalled-search-delay) (on the `instantSearch` widget).

<Info>
  All examples in this guide assume you've included InstantSearch.js in your web page from a CDN.
  If, you're using a package manager, adjust how you [import InstantSearch.js and its widgets](/doc/guides/building-search-ui/installation/js).
</Info>

For example:

```js JavaScript icon=code theme={"system"}
const search = instantsearch({
  indexName: "instant_search",
  stalledSearchDelay: 200, // this is the default value for the delay
  searchClient,
});

search.addWidgets([
  instantsearch.widgets.searchBox({
    container: "#searchBox",
    placeholder: "Search for products",
    showLoadingIndicator: true, // this add the loading indicator
  }),
]);
```

### Make your own loading indicator

You can also use the loading indicator with other widgets.
The following example shows how to make a custom component that writes `Loading...` when search stalls.
If network conditions are optimal, users won't see this message.

```js JavaScript icon=code theme={"system"}
const search = instantsearch({
  indexName: "instant_search",
  searchClient,
});

// The container to use
const loadingContainer = document.querySelector("#loading");

search.addWidgets([
  {
    render({ searchMetadata = {} }) {
      const { isSearchStalled } = searchMetadata;

      loadingContainer.innerHTML = isSearchStalled ? "Loading..." : "";
    },
  },
]);
```

## Debouncing

Another way of improving the perception of performance is by preventing lag.
InstantSearch generates one query per keystroke by default.
This can lead to a lag in the worst network conditions because browsers can only make a limited number of parallel requests.
By reducing the number of requests, you can prevent this lag.

Debouncing limits the number of requests and avoids processing unnecessary ones by avoiding sending requests before a timeout.

Implement debouncing at the `searchBox` level with the [`queryHook`](/doc/api-reference/widgets/search-box/js#param-query-hook) option.
For example:

```js JavaScript icon=code theme={"system"}
const search = instantsearch({
  indexName: "instant_search",
  searchClient,
});

let timerId;
let timeout = 0;

search.addWidgets([
  instantsearch.widgets.searchBox({
    container: document.querySelector("#searchBox"),
    placeholder: "Search for products",
    queryHook(query, refine) {
      clearTimeout(timerId);
      timerId = setTimeout(() => refine(query), timeout);
    },
  }),
]);
```

This function uses the option `queryHook` and is called on every keystroke.
In the example, the code debounces the call to `refine`.

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](https://developer.mozilla.org/en-US/docs/Web/API/Network_Information_API) to detect connection changes.

<Info>
  The [Network Information API](https://developer.mozilla.org/en-US/docs/Web/API/Network_Information_API) is unavailable on some browsers.
</Info>

```js JavaScript icon=code theme={"system"}
const search = instantsearch({
  indexName: "instant_search",
  searchClient,
});

let timerId;
let timeout = 0;

search.addWidgets([
  instantsearch.widgets.searchBox({
    container: document.querySelector("#searchBox"),
    placeholder: "Search for products",
    queryHook(query, refine) {
      clearTimeout(timerId);
      timerId = setTimeout(() => refine(query), timeout);
    },
  }),
]);

const connection = navigator.connection;
if (connection) {
  connection.addEventListener("change", function updateTimeout() {
    timeout = ["slow-2g", "2g"].includes(connection?.effectiveType) ? 400 : 0;
  });
}
```

### Select a debounce delay

The optimal debouncing delay should match your audience's typing speed
(typically 30 words per minute (WPM) on mobile devices and 40 WPM on desktop devices).
If the delay is too short, users still see flashes of content.
If the delay is too long, the time between key presses and results becomes too long.

200 ms is the preferred debounce delay.
Delays of over 300 ms will start degrading the user experience.

## Optimize build size

InstantSearch supports [dead code elimination through tree shaking](https://webpack.js.org/guides/tree-shaking/), but you must follow these 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](https://rollupjs.org/) or [webpack 4+](https://webpack.js.org).
* Ensure you pick the ES module build of InstantSearch by targeting the `module` field in `package.json` ([`resolve.mainFields` option in webpack](https://webpack.js.org/configuration/resolve/#resolvemainfields), [`mainFields` option in `@rollup/plugin-node-resolve`](https://github.com/rollup/plugins/tree/master/packages/node-resolve#mainfields)). This is the default configuration in most popular bundlers: you only need to change something if you have a custom configuration.
* Keep [Babel](https://babeljs.io) 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 itself.

If you're using Babel, you can configure [`babel-preset-env`](https://babeljs.io/docs/en/babel-preset-env) not to process ES6 modules:

```js JavaScript icon=code theme={"system"}
// babel.config.js
module.exports = {
  presets: [
    [
      "env",
      {
        modules: false,
      },
    ],
  ],
};
```

If you're using the [TypeScript compiler (`tsc`)](https://www.typescriptlang.org/docs/handbook/2/basic-types.html#tsc-the-typescript-compiler):

```jsonc JSON icon=braces theme={"system"}
// tsconfig.json
{
  "compilerOptions": {
    "module": "esnext",
  }
}
```

Import only what you need. For this, use the multiple entry points from the ES module build:

```js JavaScript icon=code theme={"system"}
// instantsearch() function without reference to the widgets or connectors
import instantsearch from 'instantsearch.js/es';

// Import connectors individually
import { connectSearchBox } from 'instantsearch.js/es/connectors';

// Import widgets individually
import { searchBox } from 'instantsearch.js/es/widgets';

const search = instantsearch({ ... });

search.addWidgets([searchBox({ ... })]);
search.addWidgets([connectSearchBox(() => { ... })({ ... })])
```

### Troubleshooting

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

```js JavaScript icon=code theme={"system"}
import "instantsearch.js/es"; // 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.

## Caching

### Caching by default (and how to turn it off)

By default, Algolia caches the search results of the queries, storing them locally in the cache. This cache only persists during the current page session, and as soon as the page reloads, the cache clears.

If users type a search (or part of it) that's already been entered, the results will be retrieved from the cache instead of requesting them from Algolia, making the app much faster.

While it's a convenient feature, sometimes you may want to clear the cache and make a new request to Algolia.
For instance, when changes are made to some <Records /> in your <Index />, you should update your app's frontend to reflect that change (and avoid displaying stale results retrieved from the cache).

The `refresh` function, available for custom connectors, lets you clear the cache and trigger a new search.

The [`refresh()`](/doc/api-reference/widgets/instantsearch/js#param-refresh) method on the `instantsearch` instance lets you clear the cache and trigger a new search.

### When to discard the cache

Consider discarding the cache when your app's data is updated by:

* Your users (for example, in a dashboard). In this case, refresh the cache based on an app state, such as the last user modification.
* Another process you don't manage (for example, a cron job that updates users inside Algolia). In this case, you should refresh your app's cache periodically.

### Refresh the cache triggered by a user action

The following code triggers a refresh based on a user action (such as adding a new product or clicking a button).

<Columns>
  <Card title="Open CodeSandbox" icon="codesandbox" href="https://codesandbox.io/s/github/algolia/doc-code-samples/tree/master/instantsearch.js/">
    Run and edit the Improve performance example in CodeSandbox.
  </Card>

  <Card title="Explore source code" icon="github" href="https://github.com/algolia/doc-code-samples/tree/master/instantsearch.js/refresh-cache-user-action">
    Browse the source for the Improve performance example on GitHub.
  </Card>
</Columns>

```js JavaScript icon=code theme={"system"}
const search = instantsearch({
  indexName: "demo_ecommerce",
  searchClient,
});

search.addWidgets([
  instantsearch.widgets.searchBox({
    container: "#searchbox",
  }),
]);

search.addWidgets([
  instantsearch.widgets.hits({
    container: "#hits",
  }),
]);

document.querySelector("button").addEventListener("click", () => {
  search.refresh();
});

search.start();
```

### Refresh the cache periodically

You can set an interval to determine how often the app clears the cache.
Use this approach if you can't trigger cache clearance based on user actions.

```js JavaScript icon=code theme={"system"}
const search = instantsearch({
  indexName: "demo_ecommerce",
  searchClient,
});

search.addWidgets([
  instantsearch.widgets.searchBox({
    container: "#searchbox",
  }),

  instantsearch.widgets.hits({
    container: "#hits",
  }),
]);

setInterval(() => {
  search.refresh();
}, 5000);

search.start();
```

If you need to wait for an action from Algolia, use [`waitTask`](/doc/libraries/sdk/v1/methods/wait-task) to avoid refreshing the cache before the task completes.

<Columns>
  <Card title="Open CodeSandbox" icon="codesandbox" href="https://codesandbox.io/s/github/algolia/doc-code-samples/tree/master/instantsearch.js/refresh-cache-periodically">
    Run and edit the Improve performance example in CodeSandbox.
  </Card>

  <Card title="Explore source code" icon="github" href="https://github.com/algolia/doc-code-samples/tree/master/instantsearch.js/refresh-cache-periodically">
    Browse the source for the Improve performance example on GitHub.
  </Card>
</Columns>

### Disable the cache

If you need the most current data and the performance impact of this isn't an issue,
[turn off caching](/doc/libraries/sdk/caching#requests-and-responses).

```js JavaScript icon=code theme={"system"}
import { liteClient as algoliasearch } from "algoliasearch/lite";
import { createNullCache } from "@algolia/cache-common";

const searchClient = algoliasearch(
  "ALGOLIA_APPLICATION_ID",
  "ALGOLIA_SEARCH_API_KEY",
  {
    // Disable caching for completed requests
    responsesCache: createNullCache(),
    // Disable caching for in-flight requests
    requestsCache: createNullCache(),
  },
);
```

## Queries per second (QPS)

Search operations aren't limited by a fixed "search quota".
Instead, they're limited by your plan's [maximum QPS](https://support.algolia.com/hc/en-us/articles/4406975224721) and
[operations limit](https://support.algolia.com/hc/en-us/articles/18138875086865).

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, if you have a search interface with a `searchBox`, a `hierarchicalMenu`, and a `refinementList`,
then every keystroke triggers one operation.
Upon each user change to a `menu` or `refinementList`, a new operation is executed.

If you experience QPS limitations, consider implementing [a debounced `searchBox`](#debouncing).

For more information, see:

* [`searchBox`](/doc/api-reference/widgets/search-box/js)
* [`hierarchicalMenu`](/doc/api-reference/widgets/hierarchical-menu/js)
* [`refinementList`](/doc/api-reference/widgets/refinement-list/js)
