By default, Algolia module only provides the search functionality but you can enable the vue-instantsearch components support to have Vue 3 components ready to serve as search and result components.
In order to enable them, first add instantSearch configuration option to module configuration:
import { defineNuxtConfig } from 'nuxt3'export default defineNuxtConfig({ modules: ['@nuxtjs/algolia'], algolia: { apiKey: process.env.ALGOLIA_SEARCH_API_KEY, applicationId: process.env.ALGOLIA_APPLICATION_ID, instantSearch: { theme: 'algolia' } }})You can choose a theme from satellite, reset, and algolia
Next, let's create indexName variable, call useAlgolia composable in page.vue script section to get the reference to Algolia, and import Vue Instantsearch components:
<script lang="ts" setup>const indexName = 'test_index' const algolia = useAlgoliaRef()import { AisInstantSearch, AisSearchBox, AisHits } from 'vue-instantsearch/vue3/es'</script>Finally, let's use it in our page.vue template section with vue-instantsearch components:
<template> <div> <ais-instant-search :index-name="indexName" :search-client="algolia"> <ais-search-box /> <ais-hits /> </ais-instant-search> </div></template>Server-side rendering requires a few extra steps and can be implemented by using two approaches.
render functionTo use the approach with render function, first we need to extract instantsearch instance from the mixin and provide it to all vue-instantsearch components:
import { createServerRootMixin } from 'vue-instantsearch/vue3/es'import { renderToString } from 'vue/server-renderer'const serverRootMixin = ref( createServerRootMixin({ searchClient: algolia, indexName, }),)const { instantsearch } = serverRootMixin.value.data()provide('$_ais_ssrInstantSearchInstance', instantsearch)Then load the results using useAsyncData and hydrate them on the client:
onBeforeMount(() => { // Use data loaded on the server if (algoliaState.value) { instantsearch.hydrate(algoliaState.value) }})const { data: algoliaState } = await useAsyncData('algolia-state', async () => { if (import.meta.server) { const nuxtApp = useNuxtApp(); nuxtApp.$algolia.transporter.requester = ( await import('@algolia/requester-node-http').then( (lib) => lib.default || lib ) ).createNodeHttpRequester(); } return instantsearch.findResultsState({ // IMPORTANT: a component with access to `this.instantsearch` to be used by the createServerRootMixin code component: { $options: { components: { AisInstantSearchSsr, AisIndex, AisConfigure, AisRefinementList, AisHits, AisHighlight, AisSearchBox, AisStats, AisPagination, }, data() { return { instantsearch }; }, provide: { $_ais_ssrInstantSearchInstance: instantsearch }, render() { return h(AisInstantSearchSsr, null, () => [ // Include any vue-instantsearch components that you use including each refinement attribute h(AisHits), ]); }, }, }, renderToString, });})You can also check out the following Stackblitz link with the usage of above approach in SSR:
https://stackblitz.com/github/plexus77/nuxt-3-algolia-ssr?file=nuxt.config.ts
render functionAs explained by Rigo here there is a way of having SSR Instantsearch without a need for using a render function:
<template> <div> <AisInstantSearchSsr> <AisConfigure :hits-per-page.camel="4" v-if="$route.params.indexName === 'instant_search'" :facet-filters.camel="`brand:${$route.params.brand}`" /> <AisRefinementList :attribute="$route.params.indexName === 'airbnb' ? 'room_type' : 'categories' "> </AisRefinementList> <AisInfiniteHits show-previous> <template #loadPrevious="{ isFirstPage, refinePrevious }"> <button :disabled="isFirstPage" @click="refinePrevious"> Load less </button> </template> <template v-slot="{ items, refineNext, isLastPage }"> <div class="cont"> <div v-for="item in items" :key="item.objectID" class="item"> {{ item.name }} <img :src="item.image ?? item.thumbnail_url" /> {{ item }} </div> </div> <button :disabled="isLastPage" @click="refineNext">Load more</button> </template> </AisInfiniteHits> </AisInstantSearchSsr> </div></template><script>import { renderToString } from "vue/server-renderer";import { AisInstantSearchSsr, AisRefinementList, AisInfiniteHits, AisIndex, AisConfigure, // @ts-ignore} from "vue-instantsearch/vue3/es/index.js";export default defineNuxtComponent({ components: { AisInstantSearchSsr, AisRefinementList, AisInfiniteHits, AisIndex, AisConfigure, }, inject: ["$_ais_ssrInstantSearchInstance"], async serverPrefetch() { const s = await this["$_ais_ssrInstantSearchInstance"].findResultsState({ component: this, renderToString, }); this.$nuxt.ssrContext.payload.data.algoliaState = s; }, props: { indexName: { type: String, required: false, default: null, }, }, mounted() { console.log(this.$_ais_ssrInstantSearchInstance); setTimeout(() => { }, 5000); }, async beforeMount() { if (this.$nuxt.payload.data.algoliaState) { this.$_ais_ssrInstantSearchInstance.hydrate( this.$nuxt.payload.data.algoliaState, ); } else { // somehow, it needs to be disposed and refreshed when i change route with client side navigation this.instantsearch.dispose(); this.$nextTick(() => { this.$nextTick(() => { this.instantsearch.helper.setIndex(this.indexName).search(); }); }); } // avoid double hydration delete this.$nuxt.payload.data.algoliaState; },});</script>Check out following link for more details
https://github.com/Rigo-m/nuxt-ssr-algolia-example/blob/main/components/InstantSearchProvider.vue