about uscontact

How to add Algolia Search to NuxtJS

Author: zemna
Date: April 18, 2021
Share this:

You can provides powerful search feature in your websiste using Algolia.

Install required packages from npm

We need install total 5 package to finish this task.

npm install vue-instantsearch algoliasearch nuxt-content-algolia remove-markdown v-click-outside --save

Create custom plugin file(vue-instantsearch.js) in /plugins folder

// /plugins/vue-instantsearch.js
import Vue from 'vue'
import InstantSearch from 'vue-instantsearch'
Vue.use(InstantSearch)

Register custom plugin to nuxt.config.js

// /nuxt.config.js
export default {
  // ...
  plugins: [
    '~/plugins/vue-instantsearch'
  ],
  // ...
}

Set transpile build configuration in nuxt.config.js

// /nuxt.config.js
export default {
  build: {
    transpile: ['vue-instantsearch', 'instantsearch.js/es']
  },
}

Configure nuxt-content-algolia to send index to Algolia

// /nuxt.config.js
export default {
  // ...
  buildModules: [
    'nuxt-content-algolia'
  ],
  nuxtContentAlgolia: {
    appId: process.env.ALGOLIA_APP_ID,
    apiKey: process.env.ALGOLIA_API_KEY,
    paths: [
      {
        name: 'articles',
        index: 'articles',
        fields: ['title', 'description', 'bodyPlainText']
      }
    ]
  },
}

Create .env file and set required variables

Algolia API Key is is not your search only key but the key that grants access to modify the index.

You can generate a new API key in Algolia admin page.

// /.env
ALGOLIA_APP_ID=your-algolia-app-id
ALGOLIA_API_KEY=your-algolia-api-key

Create custom hook to remove html from content body

Html tags and other attributes is not needed when search. So, we create a plain body text using remove-markdown package.

// /nuxt.config/js
export default {
  // ...
  hooks: {
    'content:file:beforeInsert': (content) => {
      const removeMd = require('remove-markdown');
      if (content.extension == 'md') {
        content.bodyPlainText = removeMd(content.text);
      }
    }
  }
}

Create Search.vue component in /components folder

<template>
  <ais-instant-search
    :search-client="searchClient"
    index-name="articles"
  >
    <ais-configure :attributesToSnippet="['bodyPlainText']" :hits-per-page.camel="5" />
    <ais-autocomplete v-click-outside="onClickOutside">
      <div slot-scope="{ currentRefinement, indices, refine }" class="md:relative">
        <div class="relative">
          <font-awesome-icon :icon="['fas', 'search']" class="absolute h-4 text-gray-400 mt-3 ml-3" />
          <input
            type="search"
            ref="searchInput"
            class="w-full py-2 px-4 pl-10 bg-gray-100 rounded"
            :value="currentRefinement"
            @input="refine($event.currentTarget.value)"
            placeholder="Search - Ctrl+K to focus"
            autocomplete="off"
            @focus="showResults = true"
            @keydown.up.prevent="highlightPrevious(indices[0].hits.length)"
            @keydown.down.prevent="highlightNext(indices[0].hits.length)"
            @keydown.enter="goToArticle(indices)"
          >
        </div>
        <div v-if="currentRefinement.length && showResults" class="absolute right-0 z-10 transform mt-3 px-2 w-screen max-w-md sm:px-0">
          <div class="rounded-lg shadow-lg ring-1 ring-black ring-opacity-5 overflow-hidden">
            <div class="relative grid gap-6 bg-white text-gray-700 px-4 py-4 sm:gap-8 sm:p-4">
              <div v-if="currentRefinement" v-for="section in indices" :key="section.objectID" class="divide-y divide-gray-300">
                <div v-if="section.hits.length">
                  <h2 class="uppercase text-gray-700 py-1 px-2">{{ section.indexName }}</h2>
                </div>
                <NuxtLink to="#" v-for="(hit, index) in section.hits" :key="hit.objectID"
                  class="block text-sm col-span-2 py-2 transition ease-in-out duration-150"
                  :class="{ 'bg-gray-100': isCurrentIndex(index) }">
                  <div class="px-2" @mouseover="highlightedIndex = index">
                    <ais-highlight attribute="title" :hit="hit" class="block text-gray-600 font-semibold tracking-wide" />
                    <ais-snippet attribute="bodyPlainText" :hit="hit" class="block text-gray-400 font-base" />
                  </div>
                </NuxtLink>
              </div>
              <ais-powered-by theme="light" class="px-2" />
            </div>
          </div>
        </div>
      </div>
    </ais-autocomplete>
  </ais-instant-search>
</template>
<script>
import algoliasearch from 'algoliasearch/lite'
import vClickOutside from 'v-click-outside'
export default {
  directives: {
    clickOutside: vClickOutside.directive
  },
  data() {
    return {
      searchClient: algoliasearch ('6C3W4JP2I6', 'ea72adbc6e9f7b4da0b111f7319cd3a3'),
      showResults: false,
      highlightedIndex: -1 
    }
  },
  mounted() {
    this.$nextTick(function () {
      window.addEventListener('keydown', event => {
        if((event.metaKey || event.ctrlKey) && event.key === 'k') {
          this.$refs.searchInput.focus()
          event.preventDefault()
        }
      })
    })
  },
  watch: {
    '$route' () {
      this.showResults = false
      this.$refs.searchInput.blur()
    }
  },
  methods: {
    onClickOutside() {
      this.showResults = false
    },
    highlightPrevious(resultsCount) {
      if (this.highlightedIndex > 0) {
        this.highlightedIndex -= 1
      } else {
        this.highlightedIndex = resultsCount - 1
      }
    },
    highlightNext(resultsCount) {
      if (this.highlightedIndex < resultsCount - 1) {
        this.highlightedIndex += 1
      } else {
        this.highlightedIndex = 0
      }
    },
    isCurrentIndex(index) {
      return index === this.highlightedIndex
    },
    goToArticle(indices) {
      this.$nuxt.$router.push('/articles/' + indices[0].hits[this.highlightedIndex].objectID)
    }
  }
}
</script>

Insert <Search /> vue component in your layout of page

<template>
  <header>
    <Search />
  </header>
</template> 
Share this:

Leave a Reply

Your email address will not be published.

ZEMNA.NET

Professional software development agency estalished in Indonesia. We offers website setup, web application development, fulfillment service, and etc.
Follow us
Follow us to get latest news and tutorials.
2022 - Copyright, All Rights Reserved, Made by ZEMNA.NET with ❤️
magnifiercrossmenu