I published the results of this tutorial to npm as a package. If you're looking to just grab and go, you can install the module via npm. If you want to learn more about Nuxt modules and the Algolia javascript SDK, read on! Note this package only handles syncing the data to Algolia, it does nothing about rendering a search UI for your Nuxt website. You can read more about implementing the UI in Algolia's official docs for Vue Instant Search or stay tuned for my next post.
Using Nuxt as a static site generator is a great developer experience and makes for a fast site. Storing the content in static files alone however doesn't make search simple. Algolia not only makes search simple (can start out with simple text search) but scales tremendously with the ability to provide all kind of filtering and weights to your search data. You can read more about the features of Algolia here.
In order to sync our content to Algolia, we're going to be building a Nuxt module. Modules make it simple to reuse logic across different projects and run custom code at specified points during the Nuxt lifecycle. We're going to hook into the generation process and run custom code at the end of it to read our content and insert it into Algolia via the Algolia Javascript SDK.
We will interact with the module by configuring it in nuxt.config.js
// nuxt.config.js
export default{
// this registers the module to be used
// it's only needed during the build so it can be registered under buildModules (not modules)
buildModules: [
'~/modules/nuxt-content-algolia',
// ...
],
// configuration options here
nuxtContentAlgolia: {
appId: process.env.ALGOLIA_APP_ID,
// !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_API_KEY,
// relative to content directory
// each path get's its own index
paths: [
{
name: 'blog',
// optional (will use name if index not specified)
index: process.env.ALGOLIA_INDEX || 'blog',
fields: ['title', 'description', 'bodyPlainText', 'tags'],
},
],
},
}
Our actual module can live in a file at modules/nuxt-content-algolia
(notice this is where we told nuxt config to look for the "build module"). First thing we should do in our file is to allow our module to accept the configuration options from the nuxt.config.js and merge those options with the some defaults.
// modules/nuxt-content-algolia
export default function algoliaModule(moduleOptions = {}) {
this.options.nuxtAlgolia = this.options.nuxtContentAlgolia || {}
const config = {
hook: 'generate:done',
...this.options.nuxtAlgolia,
...moduleOptions,
}
}
The Algolia SDK requires us to provide an app id and a secret api key to interact with it. If those aren't provided we might as well not even attempt to sync things to algolia, so let's check for those up front. If they don't exist we'll report an error to the console and return from the module to let the build continue on its merry way.
const consola = require('consola')
export default function algoliaModule(moduleOptions = {}) {
// config...
if (!config.appId || !config.apiKey) {
consola.error(
new Error('appId and apiKey are required for nuxt-algolia module')
)
return
}
}
Nuxt provides several hooks to execute code at different times during the generate process. See all of the generate hooks on Nuxt's website. I've made the hook configurable from the options with generate:done
as the default. This works great for SSG but wanted to leave it customizable as I'm not as familiar with actually running Nuxt on a server.
export default function algoliaModule(moduleOptions = {}) {
// config...
// requirements...
// config.hook references the default "generate:done"
this.nuxt.hook(config.hook, async (nuxt) => {
// ... where the magic happens
})
}
this.nuxt.hook(config.hook, async (nuxt) => {
// require the nuxt content function
const { $content } = require(`${nuxt.options.srcDir}/node_modules/@nuxt/content`)
// loop through paths provided at config
for (let i = 0; i < config.paths.length; i++) {
const path = config.paths[i]
const indexName = path.index || path.name
// get the content from the file system
let docs = await $content(path.name).fetch()
// transform each "document" to only include the fields specified in the config
docs = docs.map((doc) => {
const newDoc = {}
path.fields.forEach((field) => (newDoc[field] = doc[field]))
newDoc.objectID = doc.slug
return newDoc
})
// initialize the algolia search package with our app info and index name
const client = algoliasearch(config.appId, config.apiKey)
const index = client.initIndex(indexName)
// clear the index in case any documents were removed
index.clearObjects()
// Finally save the new documents and output a success message
const { objectIDs } = await index.saveObjects(docs)
consola.success(
`Indexed ${objectIDs.length} records in Algolia for: ${indexName}`
)
}
})
Nuxt modules are super powerful tools that allow you to hook into different points of the Nuxt lifecycle and make standard ways of dealing with the same problem across projects. Nuxt modules provide the perfect avenue for taking content stored with Nuxt content in your file system and mirroring that content in a powerful search index like Algolia.
If you're interested in taking the next step with Nuxt + Algolia and actually implementing the search UI, sign up for the newsletter so you don't miss the next post!