Many websites have some sort of search feature because it helps users navigate through their content easily. Implementing it the right way can be tricky and might not give a good user experience. In this tutorial, we will be integrating Algolia, a popular and powerful search service for the best experience on our Nuxt site.
Giving users the ability to quickly search through and navigate our content easily comes with great benefits. This not only improves the user experience, but also increases user retention and boosts conversion as users can now explore beyond what brought them to our site in the first place.
In this tutorial, we’ll be looking at how to integrate this search functionality into our Nuxt app using Algolia. Algolia is a third-party service that we can integrate into our app and provides us with a set of tools that allow us to create a full search experience in our sites and applications.
We’ll be using Nuxt Content, “Git Based Headless CMS” which allows us to create and manage content using Markdown, XML, JSON files, and so on. We’ll build a Nuxt site with Nuxt Content with a search feature using Algolia InstantSearch, for styling, we’ll use TailwindCSS. This tutorial is aimed at Vue.js devs that are familiar with Nuxt.
Prerequisites
To follow along with this tutorial, you’ll need to have the following installed:
Nuxt.js is a framework built on Vue, it has many capabilities and features including Server-Side Rendering (SSR).
To install it, open our terminal and run:
npx create-nuxt-app
Where
is the name of our project folder, I’ll be using
algolia-nuxt
for this project.
Running the command will ask you some questions (name, Nuxt options, UI framework, TypeScript, etc. ). To find out more about all the options, see the
Create Nuxt app
.
When asked for Nuxt.js modules, make sure to select
Content - Git-based headless CMS
to install the
nuxt/content
module along with our Nuxt app.
TailwindCSS
is a utility first CSS framework that provides us with custom classes we can use to style our app.
We’ll also be using
TailwindCSS Typography
, which is “a plugin that provides a set of
prose
classes you can use to add beautiful typographic defaults to any vanilla HTML you don’t control (like HTML rendered from Markdown, or pulled from a CMS).”
First, we install
@nuxtjs/tailwindcss
which is a Nuxt module for TailwindCSS integration, as well as TailwindCSS and its peer-dependencies using npm:
You can import the CSS file into our components or make it accessible globally by defining the CSS files/modules/libraries you want to set globally
(included in every page).
/* nuxt.config.js*/ // Global CSS: https://go.nuxtjs.dev/config-css css: [ // CSS file in the project '@/assets/css/tailwind.css', ],
Here, we have added the path to our
tailwind.css
file to the list of global CSS files in our
nuxt.config.js
.
The
@/
tells Nuxt that it’s an absolute path to look for the file from the root directory.
Install TailwindCSS Typography
# Using npm npm install @tailwindcss/typography
Then add the plugin to our
tailwind.config.js
file:
Configure TailwindCSS To Remove Unused Styles In Production
In our
tailwind.config.js
file, configure the purge option with the paths to all of our pages and components so TailwindCSS can tree-shake unused styles in production builds:
Now, let’s create our articles and a blog page to list out our articles. But first, let’s create a site header and navigation component for our site.
Creating A Site Header And Navigation
Navigate to our
components/
folder, and create a new file
siteHeader.vue
and enter the following code:
Here, in our
we have a
component wrapped in
which routes to the home page and another
that routes to
/blog
(We’ll create the blog page that we will create later on)
.
This works without us importing the components and configuring routing ourselves because, by default, Nuxt handles importing components and routing for us.
Also, let’s modify the default
component. In
components/Logo.vue
, replace the content with the following code:
Algolia-nuxt
We can now add our
siteHeader.vue
component to our site. In
layouts/default.vue
, add
just above the
component.
...
The
component renders the current Nuxt page depending on the route.
Creating Our First Article
In
content/
, which is a folder created automatically for the
nuxt/content
module, create a new folder
articles/
and then a new file in the folder
first-blog-post.md
. Here is the file for our first article in
markdown
format. Enter the following code:
--- title: My first blog post description: This is my first blog post on algolia nuxt tags: [first, lorem ipsum, Iusto] --- ## Lorem ipsum Lorem ipsum dolor sit amet consectetur, adipisicing elit. Assumenda dolor quisquam consequatur distinctio perferendis. ## Iusto nobis nisi repellat magni facilis necessitatibus, enim temporibus. - Quisquam - assumenda - sapiente explicabo - totam nostrum inventore
The area enclosed with
---
is the
YAML
Front Matter which will be used as a custom injected variable that we will access in our template.
Next, we’re going to create a
dynamic page
which will be used to:
Fetch the article content using
asyncData
which runs before the page has been rendered. We have access to our content and custom injected variables through the context by using the variable
$content
. As we are using a dynamic page, we can know what article file to fetch using the
params.slug
variable provided by
Vue Router
to get the name of each article.
Render the article in the template using
.
Ok, navigate to
pages/
and create a
blog/
folder. Create a
_slug.vue
(our dynamic page) file and insert the following:
{{ article.title }}
{{ article.description }}
{{tag}}
If you go to your browser and navigate to
https://localhost:3000/blog/first-blog-post
you should see our rendered content:
Now that our dynamic page is working and our article is rendering, let’s create some duplicates for the purpose of this tutorial.
--- title: My first blog post description: This is my first blog post on algolia nuxt tags: [first, Placeat amet, Iusto] --- ## Lorem ipsum Lorem ipsum dolor sit amet consectetur, adipisicing elit. Assumenda dolor quisquam consequatur distinctio perferendis. ## Iusto nobis nisi repellat magni facilis necessitatibus, enim temporibus. - Quisquam - assumenda - sapiente explicabo - totam nostrum inventore
Create Blog Page To List Our Articles
Let’s now create a blog page to list our articles. This is also where our search bar will live. Create a new file
pages/blog/index.vue
.
All posts
{{ article.title }}
{{ article.description }}
Here, in our
asyncData
function, when fetching
$content('articles')
we chain
.only(['title', 'slug', 'updatedAt', 'description'])
to fetch only those attributes from the articles,
.sortBy('createdAt', 'asc')
to sort it and lastly
fetch()
to fetch the data and assign it to
const articles
which we then return.
So, in our
, we can the list of articles and create links to them using their
slug
property.
vue-instantsearch
Algolia InstantSearch UI component/widget library for Vue.
instantsearch.css
Custom styling for instantSearch widgets.
algoliasearch
A HTTP client to interact with Algolia.
nuxt-content-algolia
Package for indexing our content and sending it to Algolia.
remove-markdown
This strips all markdown characters from the
bodyPlainText
of the articles.
dotenv
This helps to read environment variables from
.env
files.
We’ll be using these packages throughout the rest of this tutorial, but first, let’s set up an Algolia account.
Set Up Algolia Account
Sign up for an Algolia account at
https://www.algolia.com/
. You can do this for free, however, this will give you a trial period of 14days. Since we’re not performing heavy tasks with Algolia, their free tier will do just fine for our project after the trial expires.
You’ll be taken through some onboarding steps. After that, an
UNAMED APP
will be created for you. On the sidebar, on the left, navigate to
the API Keys
you’ll be provided with:
Application ID
This is your unique application identifier. It’s used to identify you when using Algolia’s API.
Search Only API Key
This is the public API key to use in your frontend code. This key is only usable for search queries and sending data to the Insights API.
Admin API Key
This key is used to create, update and DELETE your indices. You can also use it to manage your API keys.
Now that we have our API Keys, let’s save them in an
.env
file for our project. Navigate to the project root folder and create a new file
.env
and enter your API keys:
Set Up
nuxt-content-algolia
To Send Content Index To Algolia
We’ve successfully created an index property on our account. Now we have to generate an index from our Nuxt articles which is what Algolia will use to provide results for search queries. This is what the
nuxt-content-algolia
module that we previously installed is for.
We need to configure it in our
nuxt.config.js
.
First, we will add it to our
buildModules
:
// nuxt.config.js ... // Modules for dev and build (recommended): https://go.nuxtjs.dev/config-modules buildModules: ['@nuxtjs/tailwindcss', 'nuxt-content-algolia'], ...
Then, we create a new
nuxtContentAlgolia
object and add a few configurations to it:
// nuxt.config.js export default { ... nuxtContentAlgolia: { // Application ID appId: process.env.ALGOLIA_APP_ID, // Admin API Key // !IMPORTANT secret key should always be an environment variable // this is not your search only key but the key that grants access to modify the index apiKey: process.env.ALGOLIA_ADMIN_API_KEY, paths: [ { name: 'articles', index: process.env.ALGOLIA_INDEX || 'articles', fields: ['title', 'description', 'tags', 'bodyPlainText'] } ] }, ... }
The
nuxtContentAlgolia
takes in the following properties:
appId
Application ID*.
apiKey
Admin API Key.
paths
An array of index objects. This is where we define where we want to generate indexes from. Each object takes the following properties:
name
The name of the folder within the
content/
folder. In other words, we’ll be using files within
content/articles/
since we defined the name as
'articles'
.
index
This is the name of the index we created on our Algolia dashboard.
fields
An array of fields to be indexed. This is what Algolia will base its search queries on.
Generate
bodyPlainText
From Articles
Note that in the
fields
array, we have
bodyPlainText
as one of its values. Nuxt Content does not provide such a field for us. Instead, what Nuxt Content provides is
body
which is a complex object that will be rendered in the DOM.
In order to get our
bodyPlainText
which is simply all text, stripped of markdown and HTML characters, we have to make use of yet another package,
remove-markdown
.
To use the
remove-markdown
function we need to make use of Nuxt
hooks
. We’ll use the
'content:file:beforeInsert'
hook which allows you to add data to a document before it is inserted, to strip off the markdown and add the generated plain text to
bodyPlainText
.
In the
'content:file:beforeInsert'
hook, we get the
remove-markdown
package. Then we check if the file to be inserted is a markdown file. If it is a markdown file, we generate the plain text by calling
removeMd
which takes
document.text
— the text of our content, as an argument, which we assign to a new
document.bodyPlainText
property. The property will now be available for use through Nuxt Content.
BodyPlainText
generated and visible in Nuxt. (
Large preview
)
Great! Now that that’s done, we can generate the index and send it over to Algolia.
Confirm Algolia Index
Alright. We’ve set up
nuxt-content-algolia
and we’ve generated
bodyPlainText
for our articles. We can now generate this index and send the data over to Algolia by building our project using
nuxt generate
.
npm run generate
This will start building our project for production and run the
nuxtContentAlgolia
config. When we look at our terminal after the build we should see that our content has been indexed and sent to Algolia.
Open
Indices
, then go to
Search API logs
, where you will see a log of operations performed with your
Search API
. You can now open and check the API call sent from your Nuxt project. This should have the content of your article as specified in the
fields
section of
nuxtContentAlgolia
config.
Nice! 🍻
Building The Search UI
So far we’ve been able to generate and send index data to Algolia, which means that we are able to query this data to get search results.
To do that within our app, we have to build our search UI.
Vue-InstantSearch
provides lots of UI components using Algolia that can be integrated to provide a rich search experience for users. Let’s set it up.
Create And Configure
vue-instantSearch
Plugin
In order to use the Algolia
InstantSearch
widgets in our Nuxt app, we will have to create a plugin in our
plugins
folder.
Go to
plugins/
and create a new file
vue-instantsearch.js
.
// plugins/vue-instantsearch.js import Vue from 'vue' import InstantSearch from 'vue-instantsearch' Vue.use(InstantSearch)
Here, we’re simply importing
InstantSearch
and using it on the
Vue
frontend.
Now, we have to add the
vue-instantSearch
plugin to our plugins and build options in
nuxt.config.js
in order to transpile it to Vue.js.
So, go over to
nuxt.config.js
and add the following:
// nuxt.config.js export default { ... // Plugins to run before rendering page: https://go.nuxtjs.dev/config-plugins plugins: ['@/plugins/vue-instantsearch.js'], // Build Configuration: https://nuxtjs.org/docs/2.x/configuration-glossary/configuration-build#transpile build: { transpile: ['vue-instantsearch', 'instantsearch.js/es'] } ... }
InstantSearch
code uses ES modules, yet it needs to be executed in
Node.js
. That’s why we need to let Nuxt know that those files should be transpiled during the build. Now that we’ve configured our
vue-instantSearch
plugin, let’s create a search component.
Create A Search Component
Create a new file
components/Search.vue
.
Since we’ve installed
vue-instantSearch
as a plugin, we can use it within our Vue components.
...
First, in the
section, we’re importing
algoliaSearch
and
instantsearch.css
.
Next, we provide the credentials for our Algolia search which are:
Application ID
,
Search API key
.
As parameters to
algoliaSearch
then assign it to
searchClient
which we will use in our
to configure our Algolia search widgets.
ais-instant-search
Widget
ais-instant-search
is the root Vue
InstantSearch
component. All other widgets need to be wrapped with the root component to function. The required attributes for this component are:
index-name
Name of the index to query, in this case, it would be
articles
.
search-client
algoliaSearch
object containing Application ID and Search API Key.
...
ais-configure
Widget
The
ais-configure
widget helps configure the search functionality by sending defined parameters to Algolia.
Any props you add to this widget will be forwarded to Algolia. For more information on the different parameters you can set, have a look at the
search parameters API reference
.
The parameters we’ll set for now will be:
attributesToSnippet
The name of the attribute or
field
to snippet in, we’ll soon see more on this.
hits-per-page.camel
Number of results in one page.
snippetEllipsisText="…"
Set
...
before and after snipped text.
...
ais-autocomplete
Widget
This widget is basically a wrapper that allows us to create a search result that autocompletes the query. Within this widget, we can connect to other widgets to provide a richer UI and access multiple indices.
{{ index.indexName }}
...
So, within our
ais-autocomplete
widget, we’re doing a few things:
Overriding the DOM output of the widget using the
default
slot. We’re doing this using the scopes:
currentRefinement: string
: the current value of the query.
indices: object[]
: the list of indices.
refine: (string) => void
: the function to change the query.
...
...
Create a search
to hold, change the query and value of the
currentRefinement
.
...
...
Render the search results for each index. Each index has the following properties:
indexName: string
: the name of the index.
indexId: string
: the id of the index.
hits: object[]
: the resolved hits from the index matching the query.
...
{{ index.indexName }}
...
Then render the results —
hits
.
...
...
Here’s what we’re using:
Widget to highlight the portion of the result which directly matches the query of the field passed to the
attribute
prop.
Widget to display the relevant section of the snippeted attribute and highlight it. We defined the
attribute
in
attributesToSnippet
in
.
Let’s run our dev server and see what our New search looks like.
InstantSearch comes with some default styles that we included in our project using the
instantsearch.css
package. However, we might need to change or add some styles to our components to suit the site we’re building.
The CSS classes with many widgets can be overwritten using the
class-names
prop. For example, we can change the highlighted style of
.
...
...
And in our CSS:
...
...
We see that the class we defined has been applied to the highlight.
Configuring InstantSearch For Server-Side Rendering (SSR)
We now have our search component up and running but it only renders on the client-side and this means we have to wait for the search component to load even after the page loads. We can further improve the performance of our site by rendering it on the server-side.
According to
Algolia
, the steps for implementing server-side rendering are:
On the server:
Make a request to Algolia to get search results.
Render the Vue app with the results of the request.
Store the search results on the page.
Return the HTML page as a string.
On the client:
Read the search results from the page.
Render (or hydrate) the Vue app with search results.
Using Mixins,
serverPreFetch
,
beforeMount
Following Algolia’s documentation on implementing SSR with Nuxt, we have to make the following changes:
...
We’re simply doing the following:
createServerRootMixin
to create a reusable search instance;
findResultsState
in
serverPrefetch
to perform a search query on the back end;
hydrate
method in
beforeMount
.
Then in our
,
...
...
...
Here, we to replace
ais-instant-search
with
ais-instant-search-ssr
.
Conclusion
We’ve successfully built a Nuxt site with some content handled by Nuxt Content and integrated a simple Algolia search into our site. We’ve also managed to optimize it for SSR. I have a link to the source code of the project in this tutorial and a demo site deployed on Netlify, the links are down below.
We have tons of options available to customize and provide a rich search experience now that the basics are out of the way.
The Algolia widgets showcase
is a great way to explore those options and widgets. You’ll also find more information on the
widgets
used in this tutorial.