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

# Server-side rendering

> Render your React InstantSearch app on your server.

export const FlavorSwitcher = ({current, baseHref = "", options = [], label = "InstantSearch framework"}) => {
  if (options.length === 0) {
    return <div className="not-prose" role="alert" style={{
      margin: "0.25rem 0 1.5rem",
      padding: "0.75rem",
      border: "1px solid #f59e0b",
      borderRadius: "0.625rem",
      color: "inherit",
      fontSize: "0.875rem"
    }}>
        FlavorSwitcher requires at least one option.
      </div>;
  }
  const selected = options.find(option => option.value === current) ?? options[0];
  return <div className="not-prose mint-flavor-switcher">
      <style>{`
        .mint-flavor-switcher {
          --mfs-bg: #ffffff;
          --mfs-bg-hover: #f4f4f5;
          --mfs-bg-current: #eef2ff;
          --mfs-border: #d4d4d8;
          --mfs-fg: #18181b;
          --mfs-muted: #71717a;
          --mfs-accent: #4f46e5;
          position: relative;
          width: min(100%, 19rem);
          margin: 0.25rem 0 1.5rem;
          color: var(--mfs-fg);
          font-size: 0.875rem;
          line-height: 1.25rem;
        }

        .dark .mint-flavor-switcher {
          --mfs-bg: #18181b;
          --mfs-bg-hover: #27272a;
          --mfs-bg-current: #272747;
          --mfs-border: #3f3f46;
          --mfs-fg: #fafafa;
          --mfs-muted: #a1a1aa;
          --mfs-accent: #a5b4fc;
        }

        .mint-flavor-switcher details {
          position: relative;
        }

        .mint-flavor-switcher summary {
          display: flex;
          min-height: 2.75rem;
          box-sizing: border-box;
          align-items: center;
          justify-content: space-between;
          gap: 0.75rem;
          padding: 0.625rem 0.75rem;
          border: 1px solid var(--mfs-border);
          border-radius: 0.625rem;
          background: var(--mfs-bg);
          color: var(--mfs-fg);
          cursor: pointer;
          font-weight: 600;
          list-style: none;
          transition: border-color 150ms ease, box-shadow 150ms ease;
        }

        .mint-flavor-switcher summary::-webkit-details-marker {
          display: none;
        }

        .mint-flavor-switcher summary:hover {
          border-color: var(--mfs-accent);
        }

        .mint-flavor-switcher summary:focus-visible {
          outline: 2px solid var(--mfs-accent);
          outline-offset: 2px;
        }

        .mint-flavor-switcher__label {
          overflow: hidden;
          text-overflow: ellipsis;
          white-space: nowrap;
        }

        .mint-flavor-switcher__chevron {
          flex: none;
          transition: transform 150ms ease;
        }

        .mint-flavor-switcher details[open] .mint-flavor-switcher__chevron {
          transform: rotate(180deg);
        }

        .mint-flavor-switcher__menu {
          position: absolute;
          z-index: 50;
          top: calc(100% + 0.375rem);
          left: 0;
          width: 100%;
          box-sizing: border-box;
          margin: 0;
          padding: 0.375rem;
          border: 1px solid var(--mfs-border);
          border-radius: 0.625rem;
          background: var(--mfs-bg);
          box-shadow: 0 12px 30px rgb(0 0 0 / 16%);
          list-style: none;
        }

        .mint-flavor-switcher__menu li {
          margin: 0;
          padding: 0;
        }

        .mint-flavor-switcher__option {
          display: grid;
          gap: 0.125rem;
          padding: 0.625rem 0.75rem;
          border-radius: 0.4rem;
          color: var(--mfs-fg);
          text-decoration: none;
        }

        .mint-flavor-switcher__option:hover {
          background: var(--mfs-bg-hover);
        }

        .mint-flavor-switcher__option:focus-visible {
          outline: 2px solid var(--mfs-accent);
          outline-offset: -2px;
        }

        .mint-flavor-switcher__option[aria-current="page"] {
          background: var(--mfs-bg-current);
          color: var(--mfs-accent);
        }

        .mint-flavor-switcher__name {
          font-weight: 600;
        }

        .mint-flavor-switcher__description {
          color: var(--mfs-muted);
          font-size: 0.8125rem;
        }

        @media (prefers-reduced-motion: reduce) {
          .mint-flavor-switcher summary,
          .mint-flavor-switcher__chevron {
            transition: none;
          }
        }
      `}</style>

      <details>
        <summary aria-label={`${label}: ${selected.label}`}>
          <span className="mint-flavor-switcher__label">{selected.label}</span>
          <svg className="mint-flavor-switcher__chevron" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
            <path d="m6 9 6 6 6-6" />
          </svg>
        </summary>

        <ul className="mint-flavor-switcher__menu" aria-label={label}>
          {options.map(option => {
    const isCurrent = option.value === selected.value;
    const href = option.href ?? `${baseHref.replace(/\/$/, "")}/${encodeURIComponent(option.value)}`;
    return <li key={option.value}>
                <a className="mint-flavor-switcher__option" href={href} aria-current={isCurrent ? "page" : undefined}>
                  <span className="mint-flavor-switcher__name">
                    {option.label}
                  </span>
                  {option.description ? <span className="mint-flavor-switcher__description">
                      {option.description}
                    </span> : null}
                </a>
              </li>;
  })}
        </ul>
      </details>
    </div>;
};

<div className="mint-flavor-switcher-slot not-prose">
  <FlavorSwitcher
    current="react"
    baseHref="/doc/guides/building-search-ui/going-further/server-side-rendering"
    options={[
{ value: "js", label: "JavaScript", description: "InstantSearch.js" },
{ value: "react", label: "React", description: "React InstantSearch" },
{ value: "vue", label: "Vue", description: "Vue InstantSearch" },
]}
  />
</div>

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

Server-side rendering (SSR) lets you generate HTML from InstantSearch components on the server.

Integrating SSR with React InstantSearch:

* **Improves general performance:** the browser directly loads with HTML containing search results, and React preserves the existing markup (hydration) instead of re-rendering everything.
* **Improves perceived performance:** users don't see a UI flash when loading the page, but directly the search UI. This can also improve your [Largest Contentful Paint](https://web.dev/lcp/) score.
* **Improves SEO:** the content is accessible to any search engine, even those that don't use JavaScript.

Here's the SSR flow for InstantSearch:

1. On the server, **retrieve the initial search results** of the current search state.
2. Then, on the server, **render these search results to HTML** and send the response to the browser.
3. Then, on the browser, **load the JavaScript code** for InstantSearch.
4. Then, on the browser, **hydrate the server-side rendered InstantSearch app.**

React InstantSearch is compatible with server-side rendering.
The library provides an API that works with any SSR solution.

For more information, see:

* [How to install React InstantSearch](/doc/guides/building-search-ui/installation/react)
* [Get started with React InstantSearch](/doc/guides/building-search-ui/getting-started/react)
* [`getServerState`](/doc/api-reference/widgets/server-state/react)
* [`InstantSearchSSRProvider`](/doc/api-reference/widgets/ssr-provider/react)
* [`InstantSearchNext`](/doc/api-reference/widgets/instantsearch-next/react)

## With Next.js (App router)

As of Next.js 13, you can use the [App Router](https://nextjs.org/docs/app) to structure your app.
The App Router has a different approach to data fetching than the Pages Router,
which changes the approach to server-side rendering.

To support server-side rendering for the App Router, use the `react-instantsearch-nextjs` package.
It provides an `InstantSearchNext` component that replaces your [`InstantSearch`](/doc/api-reference/widgets/instantsearch/react) component.

### Install `react-instantsearch-nextjs`

First, make sure you have the correct dependencies installed:

* `react-instantsearch` >= `7.1.0`
* `next` >= `13.14.0`

Then, install the `react-instantsearch-nextjs` package:

<CodeGroup>
  ```sh npm theme={"system"}
  npm install react-instantsearch-nextjs
  ```

  ```sh yarn theme={"system"}
  yarn add react-instantsearch-nextjs
  ```
</CodeGroup>

### Usage

Your search component must be in its own file, and it shouldn't be named `page.js` or `page.tsx`.

To render the component in the browser and allow users to interact with it, include the "use client" directive at the top of your code.

```jsx React icon=code theme={"system"}
"use client"; // [!code ++]
import { liteClient as algoliasearch } from "algoliasearch/lite";
import { InstantSearch, SearchBox } from "react-instantsearch";

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

export function Search() {
  return (
    <InstantSearch indexName="YourIndexName" searchClient={searchClient}>
      <SearchBox />
      {/* other widgets */}
    </InstantSearch>
  );
}
```

Import the `InstantSearchNext` component from the `react-instantsearch-nextjs` package,
and replace the [`InstantSearch`](/doc/api-reference/widgets/instantsearch/react) component with it,
without changing the props.

```diff React icon=code theme={"system"}
'use client';
import { liteClient as algoliasearch } from 'algoliasearch/lite';
import {
- InstantSearch,
  SearchBox,
} from 'react-instantsearch';
+import { InstantSearchNext } from 'react-instantsearch-nextjs';

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

export function Search() {
  return (
-   <InstantSearch indexName="YourIndexName" searchClient={searchClient}>
+   <InstantSearchNext indexName="YourIndexName" searchClient={searchClient}>
      <SearchBox />
      {/* other widgets */}
-   </InstantSearch>
+   </InstantSearchNext>
  );
}
```

To serve your search page at `/search`, create an `app/search` directory.
Inside it, create a `page.js` file (or `page.tsx` if you're using TypeScript).

Make sure to [configure your route segment to be dynamic](https://nextjs.org/docs/pages/building-your-application/routing/dynamic-routes)
so that Next.js generates a new page for each request.

```jsx React icon=code theme={"system"}
// app/search/page.js or app/search/page.tsx
import { Search } from "./Search"; // change this with the path to your <Search> component

export const dynamic = "force-dynamic";

export default function Page() {
  return <Search />;
}
```

You can now visit `/search` to see your server-side rendered search page.

<Note>
  If you were previously using [`getServerState`](/doc/api-reference/widgets/server-state/react) in `getServerSideProps` with the Pages Router,
  remove any references to it. It's not needed with the App Router.
</Note>

### Composing hooks

Due to the way the App Router and `InstantSearchNext` are implemented,
you can't compose hooks the same way as you would normally do in React InstantSearch.
All React InstantSearch hooks have a `skipSuspense` option that you can set to `true` to skip the suspense behavior.
This should be done to all hooks but the last one in your custom component.

```jsx React icon=code theme={"system"}
export function MyCustomFilters() {
  const { items: brands } = useRefinementList(
    {
      attribute: 'brand',
    },
    { skipSuspense: true }
  );
  const { items: categories } = useRefinementList({ attribute: 'category' });

  return (
    // Render your filters here
  );
}
```

That way, categories will properly render on the server.

<Note>
  You will see a warning in the console if we detect that more widgets are rendered after suspense.
  This may be a false positive if you are using multi-index or dynamic widgets.
  You can ignore it in that case, and set the `ignoreMultipleHooksWarning` prop to `true` on `InstantSearchNext`.
</Note>

### Static site generation with dynamic route segments

You can generate a static version of your search page at build time using Next's [`generateStaticParams`](https://nextjs.org/docs/app/api-reference/functions/generate-static-params).
Static site generation (pre-rendering) and server-side rendering are essentially the same.
The key difference is timing:
static generation occurs at build time,
while server-side rendering occurs on request.

Adopt the same [usage recommendations](#usage) for both server-side rendering and static site generation.

```jsx React icon=code theme={"system"}
// app/[slug]/page.js or app/[slug]/page.tsx
export const dynamicParams = true;

export async function generateStaticParams() {
  const posts = await fetch(/* ... */).then((res) => res.json());
  return posts.map((post) => ({
    slug: post.slug,
  }));
}

export default async function Page({ params }) {
  const { slug } = await params;
  /* ... */
  return <Search /* ... */ />;
}
```

<Note>
  To statically render all paths at runtime,
  set the [`dynamic` route segment](https://nextjs.org/docs/pages/building-your-application/routing/dynamic-routes) to `'force-static'`.
</Note>

### Enabling routing

To enable routing, add a boolean `routing` prop to `InstantSearchNext`.

```jsx React icon=code theme={"system"}
function Search() {
  return (
    <InstantSearchNext
      indexName="YourIndexName"
      searchClient={searchClient}
      routing // [!code ++]
    >
      {/* widgets */}
    </InstantSearchNext>
  );
}
```

To customize the way InstantSearch maps the state to the route,
pass an object to `routing`, where `router` has the same options as [`history`](/doc/api-reference/widgets/history-router/react),
and `stateMapping` has the same options as the one in [`InstantSearch`](/doc/api-reference/widgets/instantsearch/react).

```jsx React icon=code theme={"system"}
<InstantSearchNext
  routing={{
    router: {
      cleanUrlOnDispose: false,
      windowTitle(routeState) {
        const indexState = routeState.indexName || {};
        return indexState.query
          ? `MyWebsite - Results for: ${indexState.query}`
          : "MyWebsite - Results page";
      },
    },
  }}
>
  {/* widgets */}
</InstantSearchNext>;
```

### Dynamic routes

If you have a dynamic route, such as `/search/[brand]`, you may get CLS (Cumulative Layout Shift) issues when navigating between pages,
especially if you are using Next.js `Link` components.
To avoid this, use the `createInstantSearchNextInstance` function from the `react-instantsearch-router-nextjs` package to create an instance which will be shared across all pages.

```jsx React icon=code theme={"system"}
import {
  createInstantSearchNextInstance,
  InstantSearchNext,
} from "react-instantsearch-nextjs";

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

export function BrandPageSearch({ brand }) {
  return (
    <InstantSearchNext
      indexName="YourIndexName"
      searchClient={searchClient}
      instance={categoryPageInstance}
    >
      <Configure filters={`brand:${brand}`} />
      {/* other widgets */}
    </InstantSearchNext>
  );
}
```

You can see a full example of this in the [Next.js dynamic routes example](https://github.com/algolia/instantsearch/blob/master/examples/react/next-app-router/app/\[category]/Search.tsx).

## With the Next.js Pages Router

Server-side rendering a page with the Pages Router in Next.js is split in two parts:
a function that returns data from the server, and a React component for the page that receives this data.

On the page, wrap the search experience with the [`InstantSearchSSRProvider`](/doc/api-reference/widgets/ssr-provider/react) component.
This provider receives the server state and forwards it to the entire InstantSearch app.

### Server-side rendering

In Next's [`getServerSideProps`](https://nextjs.org/docs/pages/building-your-application/data-fetching/get-server-side-props),
you can use [`getServerState`](/doc/api-reference/widgets/server-state/react) to return the server state as a prop.
To support routing, you can use the `createInstantSearchRouterNext` function from the `react-instantsearch-router-nextjs` package.

<CodeGroup>
  ```jsx JavaScript theme={"system"}
  // index.js
  import { renderToString } from "react-dom/server";
  import { liteClient as algoliasearch } from "algoliasearch/lite";
  import {
    InstantSearch,
    InstantSearchSSRProvider,
    getServerState,
  } from "react-instantsearch";
  import { history } from "instantsearch.js/es/lib/routers/index.js";
  import singletonRouter from "next/router";
  import { createInstantSearchRouterNext } from "react-instantsearch-router-nextjs";

  const searchClient = algoliasearch("undefined", "undefined");

  export default function SearchPage({ serverState, serverUrl }) {
    return (
      <InstantSearchSSRProvider {...serverState}>
        <InstantSearch
          searchClient={searchClient}
          indexName="YourIndexName"
          routing={{
            router: createInstantSearchRouterNext({ singletonRouter, serverUrl }),
          }}
        >
          {/* Widgets */}
        </InstantSearch>
      </InstantSearchSSRProvider>
    );
  }

  export async function getServerSideProps({ req }) {
    const protocol = req.headers.referer?.split["://"](0) || "https";
    const serverUrl = `${protocol}://${req.headers.host}${req.url}`;
    const serverState = await getServerState(
      <SearchPage serverUrl={serverUrl} />,
      { renderToString },
    );

    return {
      props: {
        serverState,
        serverUrl,
      },
    };
  }
  ```

  ```tsx TypeScript theme={"system"}
  // index.tsx
  import { renderToString } from 'react-dom/server';
  import { liteClient as algoliasearch } from 'algoliasearch/lite';
  import {
    InstantSearch,
    InstantSearchServerState,
    InstantSearchSSRProvider,
    getServerState,
  } from 'react-instantsearch';
  import { history } from 'instantsearch.js/es/lib/routers/index.js';
  import singletonRouter from 'next/router';
  import { createInstantSearchRouterNext } from 'react-instantsearch-router-nextjs';

  const searchClient = algoliasearch('undefined', 'undefined');

  type SearchPageProps = {
    serverState?: InstantSearchServerState;
    serverUrl: URL;
  };

  export default function SearchPage({ serverState, serverUrl }: SearchPageProps) {
    return (
      <InstantSearchSSRProvider {...serverState}>
        <InstantSearch
          searchClient={searchClient}
          indexName="YourIndexName"
          routing={{
            router: createInstantSearchRouterNext({ singletonRouter, serverUrl }),
          }}
        >
          {/* Widgets */}
        </InstantSearch>
      </InstantSearchSSRProvider>
    );
  }

  export async function getServerSideProps({ req }) {
    const protocol = req.headers.referer?.split('://')[0] || 'https';
    const serverUrl = `${protocol}://${req.headers.host}${req.url}`;
    const serverState = await getServerState(
      <SearchPage serverUrl={serverUrl} />,
      { renderToString }
    );

    return {
      props: {
        serverState,
        serverUrl,
      },
    };
  }
  ```
</CodeGroup>

Check the complete [SSR example with Next.js](https://codesandbox.io/s/github/algolia/instantsearch/tree/master/examples/react/next?file=/pages/index.tsx).

### Static site generation

You can generate a static version of your search page at build time using Next's [`getStaticProps`](https://nextjs.org/docs/basic-features/data-fetching/get-static-props). Static site generation (or pre-rendering) is essentially the same thing as [server-side rendering](/doc/guides/building-search-ui/going-further/server-side-rendering/react#server-side-rendering), except the latter happens at request time, while the former happens at build time.

You can use the same [`InstantSearchSSRProvider`](/doc/api-reference/widgets/ssr-provider/react) and [`getServerState`](/doc/api-reference/widgets/server-state/react) APIs for both [server-side rendering](#server-side-rendering) and static site generation.

<CodeGroup>
  ```js JavaScript theme={"system"}
  // index.js
  import { renderToString } from "react-dom/server";
  import { liteClient as algoliasearch } from "algoliasearch/lite";
  import {
    InstantSearch,
    InstantSearchSSRProvider,
    getServerState,
  } from "react-instantsearch";

  const searchClient = algoliasearch("undefined", "undefined");

  export default function SearchPage({ serverState }) {
    return (
      <InstantSearchSSRProvider {...serverState}>
        <InstantSearch searchClient={searchClient} indexName="YourIndexName">
          {/* Widgets */}
        </InstantSearch>
      </InstantSearchSSRProvider>
    );
  }

  export async function getStaticProps() {
    const serverState = await getServerState(<SearchPage />, { renderToString });
    return {
      props: {
        serverState,
      },
    };
  }
  ```

  ```tsx TypeScript theme={"system"}
  // index.tsx
  import { renderToString } from 'react-dom/server';
  import { liteClient as algoliasearch } from 'algoliasearch/lite';
  import {
    InstantSearch,
    InstantSearchServerState,
    InstantSearchSSRProvider,
    getServerState,
  } from 'react-instantsearch';

  const searchClient = algoliasearch('undefined', 'undefined');

  type SearchPageProps = {
    serverState?: InstantSearchServerState;
  };

  export default function SearchPage({ serverState }: SearchPageProps) {
    return (
      <InstantSearchSSRProvider {...serverState}>
        <InstantSearch searchClient={searchClient} indexName="YourIndexName">
          {/* Widgets */}
        </InstantSearch>
      </InstantSearchSSRProvider>
    );
  }

  export async function getStaticProps() {
    const serverState = await getServerState(<SearchPage />, {
      renderToString
    });
    return {
      props: {
        serverState,
      },
    };
  }
  ```
</CodeGroup>

### Dynamic routes

If you want to generate pages dynamically—for example,
one for each brand—you can use Next's [`getStaticPaths`](https://nextjs.org/docs/pages/building-your-application/data-fetching/get-static-paths) API.

The following example uses [dynamic routes](https://nextjs.org/docs/routing/dynamic-routes) along with `getStaticPaths` to create one page per brand.

<CodeGroup>
  ```js JavaScript theme={"system"}
  // pages/brands/[brand].js
  import { renderToString } from "react-dom/server";
  import { liteClient as algoliasearch } from "algoliasearch/lite";
  import {
    InstantSearch,
    InstantSearchSSRProvider,
    SearchBox,
    Hits,
    Configure,
    getServerState,
  } from "react-instantsearch";

  const searchClient = algoliasearch("undefined", "undefined");

  export default function BrandPage({ brand, serverState }) {
    return (
      <InstantSearchSSRProvider {...serverState}>
        <InstantSearch searchClient={searchClient} indexName="YourIndexName">
          <Configure facetFilters={`brand:${brand}`} />
          <SearchBox />
          <Hits />
        </InstantSearch>
      </InstantSearchSSRProvider>
    );
  }

  export async function getStaticPaths() {
    return {
      // You can retrieve your brands from an API, a database, a file, etc.
      paths: [{ params: { brand: "Apple" } }, { params: { brand: "Samsung" } }],
      fallback: "blocking", // or `true` or `false`
    };
  }

  export async function getStaticProps({ params }) {
    if (!params) {
      return { notFound: true };
    }

    const serverState = await getServerState(<BrandPage brand={params.brand} />, {
      renderToString,
    });

    return {
      props: {
        brand: params.brand,
        serverState,
      },
    };
  }
  ```

  ```tsx TypeScript theme={"system"}
  // pages/brands/[brand].tsx
  import { renderToString } from 'react-dom/server';
  import { liteClient as algoliasearch } from 'algoliasearch/lite';
  import {
    InstantSearch,
    InstantSearchServerState,
    InstantSearchSSRProvider,
    SearchBox
    Hits,
    Configure,
    getServerState,
  } from 'react-instantsearch';
  import { GetStaticPathsResult, GetStaticPropsContext } from 'next';

  const searchClient = algoliasearch(
    'undefined',
    'undefined'
  );

  type BrandPageProps = {
    brand?: string;
    serverState?: InstantSearchServerState;
  };

  export default function BrandPage({ brand, serverState }: BrandPageProps) {
    return (
      <InstantSearchSSRProvider {...serverState}>
        <InstantSearch searchClient={searchClient} indexName="YourIndexName">
          <Configure facetFilters={`brand:${brand}`} />
          <SearchBox />
          <Hits />
        </InstantSearch>
      </InstantSearchSSRProvider>
    );
  }

  type Params = {
    brand: string;
  };

  export function getStaticPaths(): GetStaticPathsResult<Params> {
    return {
      // You can retrieve your brands from an API, a database, a file, etc.
      paths: [{ params: { brand: 'Apple' } }, { params: { brand: 'Samsung' } }],
      fallback: 'blocking', // or `true` or `false`
    };
  }

  export async function getStaticProps({
    params,
  }: GetStaticPropsContext<Params>) {
    if (!params) {
      return { notFound: true };
    }

    const serverState = await getServerState(
      <BrandPage brand={params.brand} />,
      { renderToString }
    );

    return {
      props: {
        brand: params.brand,
        serverState,
      },
    };
  }
  ```
</CodeGroup>

If you have a reasonable amount of paths to generate and this number doesn't change much, you can generate them all at build time.
In this case, you can set `fallback: false`, which will serve a 404 page to users who try to visit a path that doesn't exist (for example, a brand that isn't in your dataset).

If there are many categories and generating them all significantly slows down your build,
you can pre-render only a subset of them (for example, the most popular ones) and generate the rest on the fly.

With `fallback: true`, whenever a user visits a path that doesn't exist, your `getStaticProps` code runs on the server and the page is generated once for all subsequent users.
Users see a loading screen that you can implement with [`router.isFallback`](https://nextjs.org/docs/pages/api-reference/functions/get-static-paths#fallback-pages) until the page is ready.

With `fallback: 'blocking'`, the scenario is the same as with `fallback: true` but there's no loading screen. The server only returns the HTML once the page is generated.

## With Remix

[Remix](https://remix.run/) is a full-stack web framework that encourages usage of runtime servers, notably for server-side rendering.

Server-side rendering a page in Remix is split in two parts:

* A [loader](https://remix.run/docs/en/v1/api/conventions#loader) that returns data from the server,
* A React component for the page that receives this data.

In the page, you need to wrap the search experience with the [`InstantSearchSSRProvider`](/doc/api-reference/widgets/ssr-provider/react) component.
This provider receives the server state and forwards it to the entire InstantSearch app.

In Remix' loader, you can use [`getServerState`](/doc/api-reference/widgets/server-state/react) to return the server state.
To support routing, forward the server's request URL to the [`history`](/doc/api-reference/widgets/history-router/js) router.

<CodeGroup>
  ```js JavaScript theme={"system"}
  // index.js
  import { renderToString } from "react-dom/server";
  import { liteClient as algoliasearch } from "algoliasearch/lite";
  import {
    InstantSearch,
    InstantSearchSSRProvider,
    getServerState,
  } from "react-instantsearch";
  import { history } from "instantsearch.js/cjs/lib/routers/index.js";

  import { json } from "@remix-run/node";
  import { useLoaderData } from "@remix-run/react";

  const searchClient = algoliasearch("undefined", "undefined");

  export async function loader({ request }) {
    const serverUrl = request.url;
    const serverState = await getServerState(<Search serverUrl={serverUrl} />, {
      renderToString,
    });

    return json({
      serverState,
      serverUrl,
    });
  }

  function Search({ serverState, serverUrl }) {
    return (
      <InstantSearchSSRProvider {...serverState}>
        <InstantSearch
          searchClient={searchClient}
          indexName="YourIndexName"
          routing={{
            router: history({
              getLocation() {
                if (typeof window === "undefined") {
                  return new URL(serverUrl);
                }

                return window.location;
              },
            }),
          }}
        >
          {/* Widgets */}
        </InstantSearch>
      </InstantSearchSSRProvider>
    );
  }

  export default function HomePage() {
    const { serverState, serverUrl } = useLoaderData();

    return <Search serverState={serverState} serverUrl={serverUrl} />;
  }
  ```

  ```tsx TypeScript theme={"system"}
  // index.tsx
  import { renderToString } from 'react-dom/server';
  import { liteClient as algoliasearch } from 'algoliasearch/lite';
  import type { InstantSearchServerState } from 'react-instantsearch';
  import {
    InstantSearch,
    InstantSearchSSRProvider,
    getServerState,
  } from 'react-instantsearch';
  import { history } from 'instantsearch.js/cjs/lib/routers/index.js';

  import { json } from '@remix-run/node';
  import { useLoaderData } from '@remix-run/react';
  import type { LoaderFunction } from '@remix-run/node';

  const searchClient = algoliasearch('undefined', 'undefined');

  export const loader: LoaderFunction = async ({ request }) => {
    const serverUrl = request.url;
    const serverState = await getServerState(
      <Search serverUrl={serverUrl} />,
      { renderToString }
    );

    return json({
      serverState,
      serverUrl,
    });
  };

  type SearchProps = {
    serverState?: InstantSearchServerState;
    serverUrl?: string;
  };

  function Search({ serverState, serverUrl }: SearchProps) {
    return (
      <InstantSearchSSRProvider {...serverState}>
        <InstantSearch
          searchClient={searchClient}
          indexName="YourIndexName"
          routing={{
            router: history({
              getLocation() {
                if (typeof window === 'undefined') {
                  return new URL(serverUrl!) as unknown as Location;
                }

                return window.location;
              },
            }),
          }}
        >
          {/* Widgets */}
        </InstantSearch>
      </InstantSearchSSRProvider>
    );
  }

  export default function HomePage() {
    const { serverState, serverUrl } = useLoaderData() as SearchProps;

    return <Search serverState={serverState} serverUrl={serverUrl} />;
  }
  ```
</CodeGroup>

Check out the complete examples:

* [SSR with Remix](https://codesandbox.io/s/github/algolia/doc-code-samples/tree/master/react-instantsearch/remix?file=/app/routes/index.tsx).
* [SSR with Express](https://codesandbox.io/s/github/algolia/instantsearch/tree/master/examples/react/ssr?file=/src/App.js).

## With a custom server

This guide shows how to server-side render your app with [Express](https://github.com/expressjs/express).
However, you can follow the same approach with any Node.js server.

The example in this guide has three files:

* `App.js`: the React component shared between the server and the browser
* `server.js`: the server entry to a Node.js HTTP server
* `browser.js`: the browser entry (which gets compiled to `assets/bundle.js`)

<Steps>
  <Step title="Create the React component">
    `App.js` is the main entry point to your React app. It exports an `App` component that you can render both on the server and in the browser.

    The [`InstantSearchSSRProvider`](/doc/api-reference/widgets/ssr-provider/react) component receives the server state and forwards it to [`InstantSearch`](/doc/api-reference/widgets/instantsearch/react).

    <CodeGroup>
      ```js JavaScript theme={"system"}
      // App.js
      import { liteClient as algoliasearch } from "algoliasearch/lite";
      import React from "react";
      import { InstantSearch, InstantSearchSSRProvider } from "react-instantsearch";

      const searchClient = algoliasearch("undefined", "undefined");

      function App({ serverState }) {
        return (
          <InstantSearchSSRProvider {...serverState}>
            <InstantSearch indexName="YourIndexName" searchClient={searchClient}>
              {/* Widgets */}
            </InstantSearch>
          </InstantSearchSSRProvider>
        );
      }

      export default App;
      ```

      ```tsx TypeScript theme={"system"}
      // App.tsx
      import { liteClient as algoliasearch } from 'algoliasearch/lite';
      import React from 'react';
      import {
        InstantSearch,
        InstantSearchServerState,
        InstantSearchSSRProvider,
      } from 'react-instantsearch';

      const searchClient = algoliasearch('undefined', 'undefined');

      type AppProps = {
        serverState?: InstantSearchServerState;
      };

      function App({ serverState }: AppProps) {
        return (
          <InstantSearchSSRProvider {...serverState}>
            <InstantSearch indexName="YourIndexName" searchClient={searchClient}>
              {/* Widgets */}
            </InstantSearch>
          </InstantSearchSSRProvider>
        );
      }

      export default App;
      ```
    </CodeGroup>
  </Step>

  <Step title="Server-side render the page">
    When you receive the request on the server, you need to retrieve the server state so you can pass it down to `App`. This is what [`getServerState`](/doc/api-reference/widgets/server-state/react) does: it receives your InstantSearch app and computes a search state from it.

    In the `server.js` file:

    ```js JavaScript icon=code theme={"system"}
    import express from "express";
    import React from "react";
    import { renderToString } from "react-dom/server";
    import { getServerState } from "react-instantsearch";
    import App from "./App";

    const app = express();

    app.get("/", async (req, res) => {
      const serverState = await getServerState(<App />, { renderToString });
      const html = renderToString(<App serverState={serverState} />);

      res.send(
        `
      <!DOCTYPE html>
      <html>
        <head>
          <script>window.__SERVER_STATE__ = ${JSON.stringify(serverState)};</script>
        </head>
        <body>
          <div id="root">${html}</div>
        </body>
        <script src="/assets/bundle.js"></script>
      </html>
        `,
      );
    });

    app.listen(8080);
    ```

    Here, the server:

    1. **Retrieves the server state** with [`getServerState`](/doc/api-reference/widgets/server-state/react).
    2. **Renders the `App` as HTML** with this server state.
    3. **Sends the HTML** to the browser.

    Since you're sending plain HTML to the browser,
    you need a way to forward the server state object so you can reuse it in your InstantSearch app.
    To do so, you can serialize it and store it on the `window` object (here on the `__SERVER_STATE__` global), for later reuse in `browser.js`.
  </Step>

  <Step title="Hydrate the app in the browser">
    Once the browser has received HTML from the server, the final step is to connect this markup to the interactive app.
    This step is called [hydration](https://reactjs.org/docs/react-dom.html#hydrate).

    In the `browser.js` file:

    ```js JavaScript icon=code theme={"system"}
    import React from "react";
    import { hydrate } from "react-dom";
    import App from "./App";

    hydrate(
      <App serverState={window.__SERVER_STATE__} />,
      document.querySelector("#root"),
    );

    delete window.__SERVER_STATE__;
    ```

    Deleting `__SERVER_STATE__` from the global object allows the server state to be garbage collected.
  </Step>

  <Step title="Support routing">
    Server-side rendered search experiences should be able to generate HTML based on the current URL.
    You can use the [`history`](/doc/api-reference/widgets/history-router/js) router to synchronize [`InstantSearch`](/doc/api-reference/widgets/instantsearch/react) with the browser URL.

    <CodeGroup>
      ```diff JavaScript theme={"system"}
      // App.js
      import { liteClient as algoliasearch } from 'algoliasearch/lite';
      +import { history } from 'instantsearch.js/es/lib/routers';
      +// or cjs if you're running in a CommonJS environment
      +// import { history } from 'instantsearch.js/cjs/lib/routers';
      import React from 'react';
      import {
        InstantSearch,
        InstantSearchSSRProvider,
      } from 'react-instantsearch';

      const searchClient = algoliasearch('undefined', 'undefined');

      -function App({ serverState }) {
      +function App({ serverState, serverUrl }) {
        return (
          <InstantSearchSSRProvider {...serverState}>
            <InstantSearch
              indexName="YourIndexName"
              searchClient={searchClient}

      -       routing={{
      -         router: history({
      -           getLocation: () =>
      -             typeof window === 'undefined' ? serverUrl : window.location,
      -         }),
      -       }}
            >
              {/* Widgets */}
            </InstantSearch>
          </InstantSearchSSRProvider>
        );

      }

      export default App;
      ```

      ```diff TypeScript theme={"system"}
      // App.tsx
      import { liteClient as algoliasearch } from 'algoliasearch/lite';
      import React from 'react';
      +import { history } from 'instantsearch.js/es/lib/routers';
      +// or cjs if you're running in a CommonJS environment
      +// import { history } from 'instantsearch.js/cjs/lib/routers';
      import {
        InstantSearch,
        InstantSearchServerState,
        InstantSearchSSRProvider,
      } from 'react-instantsearch';

      const searchClient = algoliasearch('undefined', 'undefined');

      type AppProps = {
        serverState?: InstantSearchServerState;
      + serverUrl: URL;
      };

      -function App({ serverState }: AppProps) {
      +function App({ serverState, serverUrl }: AppProps) {
        return (
          <InstantSearchSSRProvider {...serverState}>
            <InstantSearch
              indexName="YourIndexName"
              searchClient={searchClient}
      +       routing={{
      +         router: history({
      +           getLocation: () =>
      +             typeof window === 'undefined' ? serverUrl : window.location,
      +         }),
      +       }}
            >
              {/* Widgets */}
            </InstantSearch>
          </InstantSearchSSRProvider>
        );
      }

      export default App;
      ```
    </CodeGroup>

    You can rely on `window.location` when rendering in the browser,
    and use the `location` provided by the server when rendering on the server.

    In the `server.js` file,  recreate the URL and pass it to the `App`:

    ```diff JavaScript icon=code theme={"system"}
    import express from 'express';
    import React from 'react';
    import { renderToString } from 'react-dom/server';
    import { getServerState } from 'react-instantsearch';
    import App from './App';

    const app = express();

    app.get('/', async (req, res) => {
    + const serverUrl = new URL(
    +   `${req.protocol}://${req.get('host')}${req.originalUrl}`
    + );
    - const serverState = await getServerState(<App />, { renderToString });
    + const serverState = await getServerState(<App serverUrl={serverUrl} />, {
    +   renderToString,
    + });
    - const html = renderToString(<App serverState={serverState} />);
    + const html = renderToString(<App serverState={serverState} serverUrl={serverUrl} />);

      res.send(
        `
      <!DOCTYPE html>
      <html>
        <head>
          <script>window.__SERVER_STATE__ = ${JSON.stringify(serverState)};</script>
        </head>
        <body>
          <div id="root">${html}</div>
        </body>
        <script src="/assets/bundle.js"></script>
      </html>
        `
      );
    });

    app.listen(8080);
    ```
  </Step>
</Steps>
