> ## 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 Vue InstantSearch app on your server.

<Info>
  You're reading the documentation for Vue InstantSearch v4.
  Read the migration guide to learn [how to upgrade from v3 to v4](/doc/guides/building-search-ui/upgrade-guides/vue#upgrade-from-v3-to-v4).
  You can still find the [v3 documentation for this page](https://algolia.com/old-docs/deprecated/instantsearch/vue/v3/guides/server-side-rendering/).
</Info>

Server-side rendering (SSR) describes a technique for rendering websites.
When you request an SSR page, the HTML is rendered on the server and then sent to your client.

The main steps to implement server-side rendering with Algolia are:

On the server:

1. Request search results from Algolia
2. Render the Vue app with the results of the request
3. Store the search results in the page
4. Return the HTML page as a string

On the client:

1. Read the search results from the page
2. Render (or hydrate) the Vue app with the search results

You can build server-side rendered apps with Vue.js in different ways—for example,
using the [Vue CLI](https://cli.vuejs.org/) or [Nuxt.js](https://nuxt.com).

For code examples, see these GitHub repositories:

* [InstantSearch with Vue 3, SSR, and Vue CLI](https://github.com/algolia/doc-code-samples/tree/master/vue-instantsearch/vue3-ssr-vue-cli)
* [InstantSearch with Vue 3, SSR, and Vite](https://github.com/algolia/doc-code-samples/tree/master/vue-instantsearch/vue3-ssr-vite)

## With Vue CLI

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

  <Card title="Explore source code" icon="github" href="https://github.com/algolia/doc-code-samples/tree/master/vue-instantsearch/server-side-rendering">
    Browse the source for the Server-side rendering example on GitHub.
  </Card>
</Columns>

<Steps>
  <Step title="Create Vue app with SSR plugin">
    Use the [Vue CLI](https://cli.vuejs.org) to create the app and add the SSR plugin:

    ```sh Command line icon=square-terminal theme={"system"}
    vue create algolia-ssr-example
    cd algolia-ssr-example
    vue add router
    vue add @akryum/ssr
    ```
  </Step>

  <Step title="Start the development server">
    Run `npm run ssr:serve`.
  </Step>

  <Step title="Install Vue InstantSearch">
    See [How to install Vue InstantSearch](/doc/guides/building-search-ui/installation/vue).
  </Step>

  <Step title="Add Vue InstantSearch to your app">
    Add the following to the `src/main.js` file:

    <CodeGroup>
      ```js Vue 3 theme={"system"}
      import {
        AisInstantSearchSsr,
        createServerRootMixin,
      } from "vue-instantsearch/vue3/es";

      Vue.use(VueInstantSearch);
      ```

      ```js Vue 2 theme={"system"}
      import VueInstantSearch from "vue-instantsearch";

      Vue.use(VueInstantSearch);
      ```
    </CodeGroup>
  </Step>

  <Step title="Build a search interface">
    Create a new page `src/views/Search.vue` and build a search interface:

    ```vue Vue icon=code theme={"system"}
    <template>
      <ais-instant-search :search-client="searchClient" index-name="instant_search">
        <ais-search-box />
        <ais-stats />
        <ais-refinement-list attribute="brand" />
        <ais-hits>
          <template v-slot:item="{ item }">
            <p>
              <ais-highlight attribute="name" :hit="item" />
            </p>
            <p>
              <ais-highlight attribute="brand" :hit="item" />
            </p>
          </template>
        </ais-hits>
        <ais-pagination />
      </ais-instant-search>
    </template>

    <script>
    import { liteClient as algoliasearch } from "algoliasearch/lite";
    const searchClient = algoliasearch(
      "latency",
      "6be0576ff61c053d5f9a3225e2a90f76",
    );

    export default {
      data() {
        return {
          searchClient,
        };
      },
    };
    </script>
    ```
  </Step>

  <Step title="Add route to search interface page">
    Add route to this page in `src/router.js`:

    ```js JavaScript icon=code theme={"system"}
    import Vue from "vue";
    import Router from "vue-router";
    import Home from "./views/Home.vue";
    import Search from "./views/Search.vue";

    Vue.use(Router);

    export function createRouter() {
      return new Router({
        mode: "history",
        base: process.env.BASE_URL,
        routes: [
          // ...
          {
            path: "/search",
            name: "search",
            component: Search,
          },
        ],
      });
    }
    ```
  </Step>

  <Step title="Update the header">
    Update in `src/App.vue`:

    ```vue Vue icon=code theme={"system"}
    <template>
    <div id="app">
    <div id="nav">
        <router-link to="/">Home</router-link> |
        <router-link to="/about">About</router-link> |
        <router-link to="/search">Search</router-link>
    </div>
    <router-view />
    </div>
    </template>
    ```
  </Step>

  <Step title="Apply styling">
    Add `instantsearch.css` to `public/index.html`:

    ```html HTML icon=code-xml theme={"system"}
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/instantsearch.css@8.18.0/themes/satellite-min.css" integrity="sha256-W3Il6yFstioLHQJoDDU6iFn68e3hZpPPygHS2cjpHeo=" crossorigin="anonymous">
    ```
  </Step>

  <Step title="Add modules to configuration">
    Vue InstantSearch uses ES modules, but the app runs on the server, with Node.js.
    That's why you need to add these modules to the `vue.config.js` configuration file:

    ```js JavaScript icon=code theme={"system"}
    module.exports = {
      pluginOptions: {
        ssr: {
          nodeExternalsWhitelist: [
            /\.css$/,
            /\?vue&type=style/,
            /vue-instantsearch/,
            /instantsearch.js/,
          ],
        },
      },
    };
    ```

    At this point, Vue.js renders the app on the server.
    But when you go to `/search` in your browser, you won't see the search results on the page.
    That's because, by default, Vue InstantSearch starts searching and showing results after the page is rendered for the first time.
  </Step>

  <Step title="Create a backend instance">
    To perform searches on the backend as well, you need to create a backend instance in `src/main.js`:

    <CodeGroup>
      ```js Vue 3 theme={"system"}
      import VueInstantSearch, {
        createServerRootMixin,
      } from "vue-instantsearch/vue3/es";
      import { liteClient as algoliasearch } from "algoliasearch/lite";

      const searchClient = algoliasearch(
        "latency",
        "6be0576ff61c053d5f9a3225e2a90f76",
      );

      export async function createApp({
        renderToString,
        beforeApp = () => {},
        afterApp = () => {},
      } = {}) {
        const router = createRouter();

        //Pprovide access to all components
        Vue.use(VueInstantSearch);

        await beforeApp({
          router,
        });

        const app = new Vue({
          // Provide access to the instance
          mixins: [
            createServerRootMixin({
              searchClient,
              indexName: "instant_search",
            }),
          ],
          serverPrefetch() {
            return this.instantsearch.findResultsState({
              component: this,
              renderToString,
            });
          },
          beforeMount() {
            if (typeof window === "object" && window.__ALGOLIA_STATE__) {
              this.instantsearch.hydrate(window.__ALGOLIA_STATE__);
              delete window.__ALGOLIA_STATE__;
            }
          },
          router,
          render: (h) => h(App),
        });

        const result = {
          app,
          router,
        };

        await afterApp(result);

        return result;
      }
      ```

      ```js Vue 2 theme={"system"}
      import VueInstantSearch, { createServerRootMixin } from "vue-instantsearch";
      import { liteClient as algoliasearch } from "algoliasearch/lite";

      const searchClient = algoliasearch(
        "latency",
        "6be0576ff61c053d5f9a3225e2a90f76",
      );

      export async function createApp({
        renderToString,
        beforeApp = () => {},
        afterApp = () => {},
      } = {}) {
        const router = createRouter();

        // Provide access to all components
        Vue.use(VueInstantSearch);

        await beforeApp({
          router,
        });

        const app = new Vue({
          // Provide access to the instance
          mixins: [
            createServerRootMixin({
              searchClient,
              indexName: "instant_search",
            }),
          ],
          serverPrefetch() {
            return this.instantsearch.findResultsState({
              component: this,
              renderToString,
            });
          },
          beforeMount() {
            if (typeof window === "object" && window.__ALGOLIA_STATE__) {
              this.instantsearch.hydrate(window.__ALGOLIA_STATE__);
              delete window.__ALGOLIA_STATE__;
            }
          },
          router,
          render: (h) => h(App),
        });

        const result = {
          app,
          router,
        };

        await afterApp(result);

        return result;
      }
      ```
    </CodeGroup>

    The Vue app can now inject the backend instance of Vue InstantSearch.
  </Step>

  <Step title="Replace InstantSearch with SSR InstantSearch">
    In the main file, replace `ais-instant-search` with `ais-instant-search-ssr`.
    You can also remove its props since they're now passed to the `createServerRootMixin` function.

    ```vue Vue icon=code theme={"system"}
    <template>
      <ais-instant-search-ssr>
        <!-- ... -->
      </ais-instant-search-ssr>
    </template>

    ```
  </Step>

  <Step title="Add the InstantSearch state">
    To save the results on the backend, add the InstantSearch state to the `context` using the `getState` function:

    ```js JavaScript icon=code theme={"system"}
    import _renderToString from "vue-server-renderer/basic";
    import { createApp } from "./main";

    function renderToString(app) {
      return new Promise((resolve, reject) => {
        _renderToString(app, (err, res) => {
          if (err) reject(err);
          resolve(res);
        });
      });
    }

    export default (context) => {
      return new Promise(async (resolve, reject) => {
        // Read the provided instance
        const { app, router, instantsearch } = await createApp({ renderToString });

        router.push(context.url);

        router.onReady(() => {
          // Save the results once rendered fully.
          context.rendered = () => {
            context.algoliaState = app.instantsearch.getState();
          };

          const matchedComponents = router.getMatchedComponents();

          // Find the root component that handles the rendering
          Promise.all(
            matchedComponents.map((Component) => {
              if (Component.asyncData) {
                return Component.asyncData({
                  route: router.currentRoute,
                });
              }
            }),
          ).then(() => resolve(app));
        }, reject);
      });
    };
    ```
  </Step>

  <Step title="Rehydrate the app">
    Rehydrate the app with the initial request once you start searching.
    For this, you need to save the data on the page.
    Vue CLI provides a way to read the value on the `context` and save it in `public/index.html`:

    ```html HTML theme={"system"}
    <!DOCTYPE html>
    <html>
        <body>
        <!--vue-ssr-outlet-->
        {{{ renderState() }}}
        {{{ renderState({ contextKey: 'algoliaState', windowKey: '__ALGOLIA_STATE__' }) }}}
        {{{ renderScripts() }}}
        </body>
    </html>
    ```
  </Step>
</Steps>

## With Nuxt 2

The following section describes how to set up server-side rendering with Vue InstantSearch and Nuxt 2.
[Check the community](https://www.algolia.com/developers/code-exchange/?query=nuxt\&page=1) for solutions for Nuxt 3.

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

  <Card title="Explore source code" icon="github" href="https://github.com/algolia/doc-code-samples/tree/master/vue-instantsearch/nuxt">
    Browse the source for the Server-side rendering example on GitHub.
  </Card>
</Columns>

<Steps>
  <Step title="Create Nuxt app with Vue InstantSearch">
    Create a [Nuxt](https://nuxtjs.org) app and add `vue-instantsearch`:

    ```sh Command line icon=square-terminal theme={"system"}
    npx create-nuxt-app algolia-nuxt-example
    cd algolia-nuxt-example
    npm install vue-instantsearch algoliasearch
    ```
  </Step>

  <Step title="Add modules to configuratio">
    Vue InstantSearch uses ES modules, but the app runs on the server, with Node.js.
    That's why you need to add these modules to the `nuxt.config.js` configuration file:

    ```js JavaScript icon=code theme={"system"}
    module.exports = {
      build: {
        transpile: ["vue-instantsearch", "instantsearch.js/es"],
      },
    };
    ```
  </Step>

  <Step title="Build a search interface">
    Create a new page `pages/search.vue` and build a Vue InstantSearch interface:

    ```vue Vue icon=code theme={"system"}
    <template>
      <ais-instant-search :search-client="searchClient" index-name="instant_search">
        <ais-search-box />
        <ais-stats />
        <ais-refinement-list attribute="brand" />
        <ais-hits>
          <template v-slot:item="{ item }">
            <p>
              <ais-highlight attribute="name" :hit="item" />
            </p>
            <p>
              <ais-highlight attribute="brand" :hit="item" />
            </p>
          </template>
        </ais-hits>
        <ais-pagination />
      </ais-instant-search>
    </template>
    ```
  </Step>

  <Step title="Apply styling">
    Add the component declarations and the style sheet:

    <CodeGroup>
      ```js Vue 3 theme={"system"}
      import { liteClient as algoliasearch } from "algoliasearch/lite";
      import {
      	AisHighlight,
      	AisHits,
      	AisInstantSearch,
      	AisPagination,
      	AisRefinementList,
      	AisSearchBox,
      	AisStats,
      	createServerRootMixin,
      } from "vue-instantsearch/vue3/es";

      const searchClient = algoliasearch(
      	"latency",
      	"6be0576ff61c053d5f9a3225e2a90f76",
      );

      export default {
      	components: {
      		AisInstantSearch,
      		AisRefinementList,
      		AisHits,
      		AisHighlight,
      		AisSearchBox,
      		AisStats,
      		AisPagination,
      	},
      	data() {
      		return {
      			searchClient,
      		};
      	},
      	head() {
      		return {
      			link: [
      				{
      					rel: "stylesheet",
      					href: "https://cdn.jsdelivr.net/npm/instantsearch.css@8.18.0/themes/satellite-min.css",
      				},
      			],
      		};
      	},
      };
      ```

      ```js Vue 2 theme={"system"}
      import { liteClient as algoliasearch } from "algoliasearch/lite";
      import {
      	AisHighlight,
      	AisHits,
      	AisInstantSearch,
      	AisPagination,
      	AisRefinementList,
      	AisSearchBox,
      	AisStats,
      	createServerRootMixin,
      } from "vue-instantsearch";

      const searchClient = algoliasearch(
      	"latency",
      	"6be0576ff61c053d5f9a3225e2a90f76",
      );

      export default {
      	components: {
      		AisInstantSearch,
      		AisRefinementList,
      		AisHits,
      		AisHighlight,
      		AisSearchBox,
      		AisStats,
      		AisPagination,
      	},
      	data() {
      		return {
      			searchClient,
      		};
      	},
      	head() {
      		return {
      			link: [
      				{
      					rel: "stylesheet",
      					href: "https://cdn.jsdelivr.net/npm/instantsearch.css@8.18.0/themes/satellite-min.css",
      				},
      			],
      		};
      	},
      };
      ```
    </CodeGroup>
  </Step>

  <Step title="Configure Vue InstantSearch for SSR">
    1. Add `createServerRootMixin` to create a reusable search instance.
    2. Add `findResultsState` in `serverPrefetch` to perform a search query in the backend.
    3. Call the `hydrate` method in `beforeMount`.
    4. Replace `ais-instant-search` with `ais-instant-search-ssr`
  </Step>

  <Step title="Provide the instance to the component">
    Add the `createRootMixin`:

    <CodeGroup>
      ```vue Vue 3 theme={"system"}
      <template>
        <ais-instant-search-ssr>
          <ais-search-box />
          <ais-stats />
          <ais-refinement-list attribute="brand" />
          <ais-hits>
            <template v-slot:item="{ item }">
              <p>
                <ais-highlight attribute="name" :hit="item" />
              </p>
              <p>
                <ais-highlight attribute="brand" :hit="item" />
              </p>
            </template>
          </ais-hits>
          <ais-pagination />
        </ais-instant-search-ssr>
      </template>

      <script>
      import {
        AisInstantSearchSsr,
        AisRefinementList,
        AisHits,
        AisHighlight,
        AisSearchBox,
        AisStats,
        AisPagination,
        createServerRootMixin,
      } from "vue-instantsearch/vue3/es";
      import { liteClient as algoliasearch } from "algoliasearch/lite";
      import _renderToString from "vue-server-renderer/basic";

      function renderToString(app) {
        return new Promise((resolve, reject) => {
          _renderToString(app, (err, res) => {
            if (err) reject(err);
            resolve(res);
          });
        });
      }

      const searchClient = algoliasearch(
        "latency",
        "6be0576ff61c053d5f9a3225e2a90f76",
      );

      export default {
        mixins: [
          createServerRootMixin({
            searchClient,
            indexName: "instant_search",
          }),
        ],
        serverPrefetch() {
          return this.instantsearch
            .findResultsState({
              component: this,
              renderToString,
            })
            .then((algoliaState) => {
              this.$ssrContext.nuxt.algoliaState = algoliaState;
            });
        },
        beforeMount() {
          const results =
            (this.$nuxt.context && this.$nuxt.context.nuxtState.algoliaState) ||
            window.__NUXT__.algoliaState;

          this.instantsearch.hydrate(results);

          // Remove the SSR state so it can't be applied again by mistake
          delete this.$nuxt.context.nuxtState.algoliaState;
          delete window.__NUXT__.algoliaState;
        },
        components: {
          AisInstantSearchSsr,
          AisRefinementList,
          AisHits,
          AisHighlight,
          AisSearchBox,
          AisStats,
          AisPagination,
        },
        head() {
          return {
            link: [
              {
                rel: "stylesheet",
                href: "https://cdn.jsdelivr.net/npm/instantsearch.css@8.18.0/themes/satellite-min.css",
              },
            ],
          };
        },
      };
      </script>
      ```

      ```vue Vue 2 theme={"system"}
      <template>
        <ais-instant-search-ssr>
          <ais-search-box />
          <ais-stats />
          <ais-refinement-list attribute="brand" />
          <ais-hits>
            <template v-slot:item="{ item }">
              <p>
                <ais-highlight attribute="name" :hit="item" />
              </p>
              <p>
                <ais-highlight attribute="brand" :hit="item" />
              </p>
            </template>
          </ais-hits>
          <ais-pagination />
        </ais-instant-search-ssr>
      </template>

      <script>
      import {
        AisInstantSearchSsr,
        AisRefinementList,
        AisHits,
        AisHighlight,
        AisSearchBox,
        AisStats,
        AisPagination,
        createServerRootMixin,
      } from "vue-instantsearch";
      import { liteClient as algoliasearch } from "algoliasearch/lite";
      import _renderToString from "vue-server-renderer/basic";

      function renderToString(app) {
        return new Promise((resolve, reject) => {
          _renderToString(app, (err, res) => {
            if (err) reject(err);
            resolve(res);
          });
        });
      }

      const searchClient = algoliasearch(
        "latency",
        "6be0576ff61c053d5f9a3225e2a90f76",
      );

      export default {
        mixins: [
          createServerRootMixin({
            searchClient,
            indexName: "instant_search",
          }),
        ],
        serverPrefetch() {
          return this.instantsearch
            .findResultsState({
              component: this,
              renderToString,
            })
            .then((algoliaState) => {
              this.$ssrContext.nuxt.algoliaState = algoliaState;
            });
        },
        beforeMount() {
          const results =
            (this.$nuxt.context && this.$nuxt.context.nuxtState.algoliaState) ||
            window.__NUXT__.algoliaState;

          this.instantsearch.hydrate(results);

          // Remove the SSR state so it can't be applied again by mistake
          delete this.$nuxt.context.nuxtState.algoliaState;
          delete window.__NUXT__.algoliaState;
        },
        components: {
          AisInstantSearchSsr,
          AisRefinementList,
          AisHits,
          AisHighlight,
          AisSearchBox,
          AisStats,
          AisPagination,
        },
        head() {
          return {
            link: [
              {
                rel: "stylesheet",
                href: "https://cdn.jsdelivr.net/npm/instantsearch.css@8.18.0/themes/satellite-min.css",
              },
            ],
          };
        },
      };
      </script>
      ```
    </CodeGroup>
  </Step>
</Steps>
