Setup Algolia InstantSearch on Nuxt (with Query/Routing Url and SSR)

Aug 19, 2020
Perform search via query parameter

Install

npm install vue-instantsearch algoliasearch instantsearch.css

Setup

Edit nuxt.config.js

export default {  // ...  /*  ** for Algolia InstantSearch nested query parameter  */  router: {    parseQuery(query) {      const qs = require("qs")      /*      return qs.parse(query, {        arrayLimit: 100,        ignoreQueryPrefix: true      });       */      return qs.parse(query)    },    stringifyQuery(query) {      const qs = require("qs")      const result = qs.stringify(query)      return result ? `?${result}` : ''    }  },  /*  ** Build configuration  */  build: {    transpile: ['vue-instantsearch', 'instantsearch.js/es'],    /*    ** You can extend webpack config here    */    /*    extend (config, ctx) {    }     */  }  // ...}

Usage

Create pages/search.vue. Replace

  • index-name (Under Indices in Algolia Dashboard)
  • Application ID (Under API Keys in Algolia Dashboard)
  • Search-Only API Key

A few changes are made

  • Prevent search/listing if there if no query
  • Show result in row instead of grid
  • Hide pagination if only 1 page of result
  • Support Routing Url

Routing Url

  • By default, routing url use the format of /search?INDEX_NAME%5Bquery%5D=Hello ({INDEX_NAME: {query: "Hello"}})
  • writeState and readState is implemented to support /search?query=Hello instead. (NOTE: this might not work if multiple index is used)
<template>  <ais-instant-search-ssr>    <ais-search-box />    <client-only>      <ais-powered-by />    </client-only>    <ais-hits>      <div slot="item" slot-scope="{ item }">        <!-- show name -->        <h2><ais-highlight attribute="name" :hit="item"/></h2>        <!-- show content -->        <div>{{ item.content }}</div>        <!-- show content with highlight -->        <div><ais-highlight attribute="content" :hit="item"/></div>        <!-- show content with snippet: need to setup Snipetting in Indices -->        <div><ais-snippet attribute="content" :hit="item"/></div>      </div>    </ais-hits>    <ais-state-results>      <template slot-scope="{ state: { query }, results: { hits, nbPages } }">        <div v-if="query && hits.length == 0">No results</div>        <div v-else></div>        <ais-pagination v-if="nbPages > 0"/>      </template>    </ais-state-results>  </ais-instant-search-ssr></template><script>import {  AisInstantSearchSsr,  // AisRefinementList,  AisHits,  AisHighlight,  AisSearchBox,  // AisStats,  AisPagination,  AisSnippet,  AisStateResults,  AisPoweredBy,  createServerRootMixin,} from 'vue-instantsearch'import algoliasearch from 'algoliasearch/lite'import 'instantsearch.css/themes/algolia-min.css'const indexName = 'live'const algoliaClient = algoliasearch(  '3P********', // Application ID  '0a5f4c5c0b18********************'  // Search-Only API Key);// to disable listing on loadconst searchClient = {  search(requests) {    if (requests.every(({ params }) => !params.query)) {     return Promise.resolve({        results: requests.map(() => ({          hits: [],          nbHits: 0,          nbPages: 0,          processingTimeMS: 0,        })),      });    }    return algoliaClient.search(requests);  },};// remove indexNamefunction writeState(routeState) {  if (indexName in routeState)    routeState = routeState[indexName]  return routeState}// restore indexNamefunction readState(routeState) {  routeState = {    [indexName]: routeState  }  return routeState}// read and write router state to support algolia query parameterfunction nuxtRouter(vueRouter) {  return {    read() {      return readState(vueRouter.currentRoute.query);    },    write(routeState) {      routeState = writeState(routeState)      vueRouter.push({        query: routeState,      });    },    createURL(routeState) {      routeState = writeState(routeState)      const url = vueRouter.resolve({        query: routeState,      }).href;      return url    },    onUpdate(cb) {      if (typeof window === 'undefined') return;      this._onPopState = event => {        const routeState = event.state;        // On initial load, the state is read from the URL without        // update. Therefore, the state object isn't present. In this        // case, we fallback and read the URL.        if (!routeState) {          cb(this.read());        } else {          cb(routeState);        }      };      window.addEventListener('popstate', this._onPopState);    },    dispose() {      if (typeof window === 'undefined') return;      window.removeEventListener('popstate', this._onPopState);    },  };}export default {  serverPrefetch() {    return this.instantsearch.findResultsState(this).then(algoliaState => {      this.$ssrContext.nuxt.algoliaState = algoliaState;    });  },  beforeMount() {    const results = this.$nuxt.context.nuxtState.algoliaState || window.__NUXT__.algoliaState;    this.instantsearch.hydrate(results);  },  data() {    const mixin = createServerRootMixin({      searchClient,      indexName,      routing: {        router: nuxtRouter(this.$nuxt.$router),      }    })    return {      ...mixin.data(),    };       },  provide() {    return {      // Provide the InstantSearch instance for SSR      $_ais_ssrInstantSearchInstance: this.instantsearch,    };  },   components: {    AisInstantSearchSsr,    // AisRefinementList,    AisHits,    AisHighlight,    AisSearchBox,    // AisStats,    AisPagination,    AisSnippet,    AisStateResults,    AisPoweredBy,  },  /*  head() {    return {      link: [        {          rel: 'stylesheet',          href: 'https://cdn.jsdelivr.net/npm/[email protected]/themes/algolia-min.css',        },      ],    };  },   */};</script><style>.ais-SearchBox {  margin-bottom: 1em;}.ais-Hits-item {  width: 100%;  border: none;  box-shadow: none;}.ais-Highlight {  font-size: inherit;}.ais-Highlight-highlighted {  font-size: inherit;}</style>

References:

❤️ Is this article helpful?

Buy me a coffee ☕ or support my work via PayPal to keep this space 🖖 and ad-free.

Do send some 💖 to @d_luaz or share this article.

✨ By Desmond Lua

A dream boy who enjoys making apps, travelling and making youtube videos. Follow me on @d_luaz

👶 Apps I built

Travelopy - discover travel places in Malaysia, Singapore, Taiwan, Japan.