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