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

# Sync your URLs with React InstantSearch

> How to synchronize your URLs with React InstantSearch.

export const Filter = () => <Tooltip tip="A filter is a condition that limits which records Algolia returns. Filters often use one or more facet-value pairs, such as brand:Apple AND color:red. You can also filter by numeric values, dates, tags, booleans, or geographic constraints." cta="Filtering" href="/doc/guides/managing-results/refine-results/faceting">
    filter
  </Tooltip>;

export const SearchQuery = () => <Tooltip tip="The text users enter into a search box. In the Search API, this corresponds to the query parameter. A search query is often used with filters, facets, and other parameters, but these aren't part of the query text itself.">
    search query
  </Tooltip>;

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>;

<Note>
  This is the **React InstantSearch v7** documentation.
  If you're upgrading from v6, see the [upgrade guide](/doc/guides/building-search-ui/upgrade-guides/react/#migrate-from-react-instantsearch-v6-to-react-instantsearch-v7).
  If you were using React InstantSearch Hooks,
  this v7 documentation applies—just check for [necessary changes](/doc/guides/building-search-ui/upgrade-guides/react/#migrate-from-react-instantsearch-hooks-to-react-instantsearch-v7).
  To continue using v6, you can find the [archived documentation](https://algolia.com/old-docs/deprecated/instantsearch/react/v6/api-reference/instantsearch/).
</Note>

Synchronizing your UI with the browser URL is considered good practice.
It lets your users take one of your results pages, copy the URL, and share it.
It also improves the user experience by enabling the use of the back and next browser buttons to keep track of previous searches.

InstantSearch provides the necessary API entries to let you synchronize the state of your search UI (your refined widgets and current <SearchQuery />) with any kind of storage.

This is possible with the [`routing`](/doc/api-reference/widgets/instantsearch/react#param-routing) option. This guide focuses on storing the UI state in the browser URL.

This guide goes through different ways to handle routing with your search UI:

* Enabling routing with no extra configuration
* Manually rewriting URLs to tailor it to your needs
* Crafting SEO-friendly URLs

If you're using server-side rendering, follow the [server-side rendering](/doc/guides/building-search-ui/going-further/server-side-rendering/react) guide.
If you're using Next.js, see [`createInstantSearchRouterNext`](/doc/api-reference/widgets/instantsearch-next-router/react).

<Note>
  When you are using routing, you can't use [`initialUiState`](/doc/api-reference/widgets/instantsearch/react#param-initial-ui-state) as the two options override each other. Simple and static use cases can be more straightforward using [`initialUiState`](/doc/api-reference/widgets/instantsearch/react#param-initial-ui-state), but anything dynamic or complex should use `routing`.
</Note>

## Basic URLs

React InstantSearch lets you enable URL synchronization by setting the [`routing`](/doc/api-reference/widgets/instantsearch/react#param-routing) to `true`.

```jsx React icon=code theme={"system"}
import { InstantSearch } from "react-instantsearch";
import { liteClient as algoliasearch } from "algoliasearch/lite";

const searchClient = algoliasearch("ALGOLIA_APPLICATION_ID", "ALGOLIA_SEARCH_API_KEY");

function App() {
  return (
    <InstantSearch
      searchClient={searchClient}
      indexName="instant_search"
      routing={true}
    >
      {/* ... */}
    </InstantSearch>
  );
}
```

Assume the following search UI state:

* **Query:** "galaxy"

* **Menu:**

  * `categories`: "Cell Phones"

* **Refinement List:**

  * `brand`: "Apple", "Samsung"

* **Page:** 2

This results in the following URL:

```txt theme={"system"}
https://example.org/?instant_search[query]=galaxy&instant_search[menu][categories]=All Unlocked Cell Phones&instant_search[refinementList][brand][0]=Apple&instant_search[refinementList][brand][0]=Samsung&instant_search[page]=2
```

This URL is accurate, and can be translated back to a search UI state

## Manually rewrite URLs

The default URLs that InstantSearch generates are comprehensive, but if you have many widgets, this can also generate noise. You may want to decide what goes in the URL and what doesn't, or even rename the query parameters to something that makes more sense to you.

Setting [`routing`](/doc/api-reference/widgets/instantsearch/react#param-routing) to `true` is syntactic sugar for the following code:

```jsx React icon=code theme={"system"}
import { InstantSearch } from "react-instantsearch";
import { history } from "instantsearch.js/es/lib/routers";
import { simple } from "instantsearch.js/es/lib/stateMappings";
import { liteClient as algoliasearch } from "algoliasearch/lite";

const searchClient = algoliasearch("ALGOLIA_APPLICATION_ID", "ALGOLIA_SEARCH_API_KEY");

const routing = {
  router: history(),
  stateMapping: simple(),
};

function App() {
  return (
    <InstantSearch
      searchClient={searchClient}
      indexName="instant_search"
      routing={routing}
    >
      {/* ... */}
    </InstantSearch>
  );
}
```

The `stateMapping` option defines how to go from InstantSearch's internal state to a URL, and vice versa.
You can use it to rename query parameters and choose what to include in the URL.

```jsx React icon=code theme={"system"}
import { InstantSearch } from "react-instantsearch";
import { history } from "instantsearch.js/es/lib/routers";
import { liteClient as algoliasearch } from "algoliasearch/lite";

const searchClient = algoliasearch("ALGOLIA_APPLICATION_ID", "ALGOLIA_SEARCH_API_KEY");

const routing = {
  router: history(),
  stateMapping: {
    stateToRoute(uiState) {
      // ...
    },
    routeToState(routeState) {
      // ...
    },
  },
};

function App() {
  return (
    <InstantSearch
      searchClient={searchClient}
      indexName="instant_search"
      routing={routing}
    >
      {/* ... */}
    </InstantSearch>
  );
}
```

InstantSearch manages [`uiState`](/doc/api-reference/widgets/ui-state/react).

The state contains information about the user's search, including the query,
the <Filter /> selection,
the page being viewed,
and the widget hierarchy.
`uiState` only stores modified widget values, not defaults.

To persist this state in the URL,
InstantSearch converts the `uiState` into an object called `routeState`:
this `routeState` then becomes a URL.
Conversely, when InstantSearch reads the URL and applies it to the search,
it converts `routeState` into `uiState`.
This logic lives in two functions:

* `stateToRoute`: converts `uiState` to `routeState`.
* `routeToState`: converts `routeState` to `uiState`.

Assume the following search UI state:

* **Query:** "galaxy"

* **Menu:**

  * `categories`: "Cell Phones"

* **Refinement List:**

  * `brand`: "Apple" and "Samsung"

* **Page:** 2

This translates into the following `uiState`:

```json JSON icon="braces" theme={"system"}
{
  "indexName": {
    "query": "galaxy",
    "menu": {
      "categories": "Cell Phones"
    },
    "refinementList": {
      "brand": ["Apple", "Samsung"]
    },
    "page": 2
  }
}
```

Implement `stateToRoute` to flatten this object into a URL, and `routeToState` to restore the URL into a UI state:

```jsx React icon=code theme={"system"}
import { InstantSearch } from "react-instantsearch";
import { history } from "instantsearch.js/es/lib/routers";
import { liteClient as algoliasearch } from "algoliasearch/lite";

const searchClient = algoliasearch("ALGOLIA_APPLICATION_ID", "ALGOLIA_SEARCH_API_KEY");

const indexName = "instant_search";

const routing = {
  router: history(),
  stateMapping: {
    stateToRoute(uiState) {
      const indexUiState = uiState[indexName];
      return {
        q: indexUiState.query,
        categories: indexUiState.menu?.categories,
        brand: indexUiState.refinementList?.brand,
        page: indexUiState.page,
      };
    },
    routeToState(routeState) {
      return {
        [indexName]: {
          query: routeState.q,
          menu: {
            categories: routeState.categories,
          },
          refinementList: {
            brand: routeState.brand,
          },
          page: routeState.page,
        },
      };
    },
  },
};

function App() {
  return (
    <InstantSearch
      searchClient={searchClient}
      indexName={indexName}
      routing={routing}
    >
      {/* ... */}
    </InstantSearch>
  );
}
```

## SEO-friendly URLs

URLs are more than [query parameters](https://en.wikipedia.org/wiki/Query_string).
Another important part is the [path](https://developer.mozilla.org/en-US/docs/Web/API/URL/pathname).
Manipulating the URL path is a common ecommerce pattern that lets you better reference your result pages.

```txt theme={"system"}
https://example.org/search/Cell+Phones/?query=galaxy&page=2&brands=Apple&brands=Samsung
```

### Example implementation

Here's an example that stores the brand in the path, and the query and page as query parameters.

```jsx React icon=code theme={"system"}
import { InstantSearch } from "react-instantsearch";
import { history } from "instantsearch.js/es/lib/routers";
import { liteClient as algoliasearch } from "algoliasearch/lite";

const searchClient = algoliasearch("ALGOLIA_APPLICATION_ID", "ALGOLIA_SEARCH_API_KEY");

// Returns a slug from the category name.
// Spaces are replaced by "+" to make
// the URL easier to read and other
// characters are encoded.
function getCategorySlug(name) {
  return name.split(" ").map(encodeURIComponent).join("+");
}

// Returns a name from the category slug.
// The "+" are replaced by spaces and other
// characters are decoded.
function getCategoryName(slug) {
  return slug.split("+").map(decodeURIComponent).join(" ");
}

const routing = {
  router: history({
    windowTitle({ category, query }) {
      const queryTitle = query ? `Results for "${query}"` : "Search";

      if (category) {
        return `${category} – ${queryTitle}`;
      }

      return queryTitle;
    },

    createURL({ qsModule, routeState, location }) {
      const urlParts = location.href.match(/^(.*?)\/search/);
      const baseUrl = `${urlParts ? urlParts[1] : ""}/`;

      const categoryPath = routeState.category
        ? `${getCategorySlug(routeState.category)}/`
        : "";
      const queryParameters = {};

      if (routeState.query) {
        queryParameters.query = encodeURIComponent(routeState.query);
      }
      if (routeState.page !== 1) {
        queryParameters.page = routeState.page;
      }
      if (routeState.brands) {
        queryParameters.brands = routeState.brands.map(encodeURIComponent);
      }

      const queryString = qsModule.stringify(queryParameters, {
        addQueryPrefix: true,
        arrayFormat: "repeat",
      });

      return `${baseUrl}search/${categoryPath}${queryString}`;
    },

    parseURL({ qsModule, location }) {
      const pathnameMatches = location.pathname.match(/search\/(.*?)\/?$/);
      const category = getCategoryName(pathnameMatches?.[1] || "");
      const {
        query = "",
        page,
        brands = [],
      } = qsModule.parse(location.search.slice(1));
      // `qs` does not return an array when there's a single value.
      const allBrands = Array.isArray(brands)
        ? brands
        : [brands].filter(Boolean);

      return {
        query: decodeURIComponent(query),
        page,
        brands: allBrands.map(decodeURIComponent),
        category,
      };
    },
  }),

  stateMapping: {
    stateToRoute(uiState) {
      const indexUiState = uiState["instant_search"] || {};

      return {
        query: indexUiState.query,
        page: indexUiState.page,
        brands: indexUiState.refinementList?.brand,
        category: indexUiState.menu?.categories,
      };
    },

    routeToState(routeState) {
      return {
        instant_search: {
          query: routeState.query,
          page: routeState.page,
          menu: {
            categories: routeState.category,
          },
          refinementList: {
            brand: routeState.brands,
          },
        },
      };
    },
  },
};

function App() {
  return (
    <InstantSearch
      searchClient={searchClient}
      indexName={indexName}
      routing={routing}
    >
      {/* ... */}
    </InstantSearch>
  );
}
```

You're now using the [`history` router](/doc/api-reference/widgets/history-router/react) to explicitly set options on the default router mechanism. Notice the usage of both the `router` and `stateMapping` options to map `uiState` to `routeState`, and vice versa.

Using the `routing` option as an object, you can configure:

* `windowTitle`: a method to map the `routeState` object returned from `stateToRoute` to the window title.

* `createURL`: a method called every time you need to create a URL. When:

  * You want to synchronize the `routeState` to the browser URL
  * You want to render `a` tags in the `menu` widget
  * You call `createURL` in one of your connectors' rendering methods.

* `parseURL`: a method called every time users load or reload the page, or click the browser's back or next buttons.

### Make URLs more discoverable

In real-life applications, you might want to make some categories more easily accessible,
with a URL that's easier to read and to remember.

Given your dataset, you can make some categories more discoverable:

* "Cameras and camcorders" → `/Cameras`
* "Car electronics and GPS" → `/Cars`

In this example, on arrival at `https://example.org/search/Cameras`, it pre-selects the "Cameras and camcorders" filter.

You can achieve this with a dictionary.

```js JavaScript icon=code theme={"system"}
// Add the dictionaries to convert the names and the slugs
const encodedCategories = {
  Cameras: "Cameras and camcorders",
  Cars: "Car electronics and GPS",
  Phones: "Phones",
  TV: "TV and home theater",
};

const decodedCategories = Object.keys(encodedCategories).reduce((acc, key) => {
  const newKey = encodedCategories[key];
  const newValue = key;

  return {
    ...acc,
    [newKey]: newValue,
  };
}, {});

// Update the getters to use the encoded/decoded values
function getCategorySlug(name) {
  const encodedName = decodedCategories[name] || name;

  return encodedName.split(" ").map(encodeURIComponent).join("+");
}

function getCategoryName(slug) {
  const decodedSlug = encodedCategories[slug] || slug;

  return decodedSlug.split("+").map(decodeURIComponent).join(" ");
}
```

You can build these dictionaries from your Algolia <Records />.

With such a solution, you have full control over what categories are discoverable from the URL.

### About SEO

For your search results to be part of a public search engine's results, you must be selective.
Trying to index too many search results pages could be considered spam.

To do that, create a [`robots.txt`](http://www.robotstxt.org/) and host it at `https://example.org/robots.txt`.

Here's an example based on the URL scheme you created.

```txt robots.txt theme={"system"}
User-agent: *
Allow: /search/Audio/
Allow: /search/Phones/
Disallow: /search/
Allow: *
```

For demos, see:

* [Basic routing](https://codesandbox.io/s/github/algolia/doc-code-samples/tree/master/react-instantsearch/routing-basic)
* [SEO-friendly routing](https://codesandbox.io/s/github/algolia/doc-code-samples/tree/master/react-instantsearch/routing-seo-friendly)

## Next steps

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

* Checking the [API reference](/doc/api-reference/widgets/instantsearch/react#param-routing) to learn more about the router.
* [Server-side rendering](/doc/guides/building-search-ui/going-further/server-side-rendering/react) your app for increased performance.
