🔎 How I integrated Algolia search with NextJS and markdown files?

jpomykala

Jakub Pomykała

Posted on April 18, 2022

🔎 How I integrated Algolia search with NextJS and markdown files?

Since 2019 I’m building a SaaS for translation management as a solo developer. Since the very beginning, I needed a place where I could post code samples, and explain people how to integrate different frameworks or how to use SimpleLocalize CLI to upload and download translation files. You can see Algolia search in action on SimpleLocalize documentation.

algolia nextjs markdown example

At beginning, I started using GitBook software because it was shining and trendy at the time. It was a good move, because when you start something new you don’t want to start from scratch on every level. The main goal is to deliver good quality code and value to the customers, all other things you can (or should) buy to save time.

Short story long, I wasn’t quite happy with the GitBook after couple of months, because it didn’t fit to my needs, I didn’t like the style, and I was spending too much time on adjusting and fixing links and so on. I decided to go with my custom documentation, and surprisingly everything went smoothly! The only concert was lack o good search ability which is available in all documentation solutions, or open-source Docusaurus.

Installation

Algolia search components and Algolia client installation

npm install react-instantsearch-dom algoliasearch --save

Enter fullscreen mode Exit fullscreen mode

gray-matter a Markdown parser and globby for finding files

npm install gray-matter globby --saveDev

Enter fullscreen mode Exit fullscreen mode

Configuration

import algoliasearch from 'algoliasearch/lite';
import {connectStateResults, Hits, InstantSearch, SearchBox} from 'react-instantsearch-dom';

const searchClient = algoliasearch(
  'APP_ID',
  'SEARCH_API_KEY'
);

Enter fullscreen mode Exit fullscreen mode

Results component

By default, Algolia <Hits/> component shows all results when search query is empty. I adjusted the code to show results only if there are any results and data are loaded.

const Results = connectStateResults(({searchState, searchResults, searching}) => {
  const hasQuery = searchState && searchState.query;
  const hasResults = (searchResults?.hits ?? []).length > 0;
  const isSearching = searching;
  if (hasQuery && hasResults) {
    return <Hits hitComponent={Hit}/>;
  }
  if (hasQuery && !hasResults && !isSearching) {
    return <div>No results 😔</div>
  }
  return null;
}
);

Enter fullscreen mode Exit fullscreen mode

Hit component

Hit component is nothing else as just a one search result. I stripped my styling to make it easier to copy-paste. 😄

import React from "react";

function Hit(props: any) {
  const content = props?.hit?.content ?? "";
  const words = content.split(" ").length;
  return (<a href={props.hit.slug}>
    <div>
      <h3>{props?.hit?.frontmatter?.title ?? "no title"}</h3>
    </div>
    <p>{props?.hit?.frontmatter?.excerpt ?? ""}</p>
  </a>)
}

export default Hit;

Enter fullscreen mode Exit fullscreen mode

Search component

Use InstantSearch component and search client which you configured in the previous step, put your index name, and use Algolia SearchBox component and our custom Results component with changed empty state behaviour which we created previously.

<InstantSearch indexName="simplelocalize-docs" searchClient={searchClient}>
    <SearchBox/>
    <Results/>
</InstantSearch>

Enter fullscreen mode Exit fullscreen mode

I didn’t want to make it more complex that it needs to be, I overwrite some CSS properties in my stylesheets to hide a search button and reset button. I also added Bootstrap styling to the search box using SCSS @extend property. Simple and it does the job.

.ais-SearchBox-input {
  @extend .form-control;
}

.ais-SearchBox-submit {
  display: none;
}

.ais-SearchBox-reset {
  display: none;
}

Enter fullscreen mode Exit fullscreen mode

Get markdown files to index

Now, it’s time to get all files which should be indexed and visible in the search results. I created a new index-docs.js file which I will be executed after every successful build on CI/CD server. All my documentation pages are in /docs/ directory. So I needed to write a script for converting it into an array of objects to populate Algolia search index.

import {globby} from 'globby';
import fs from "fs";
import matter from "gray-matter";
import algoliasearch from 'algoliasearch';

const pages = await globby([
  'docs/',
]);

const objects = pages.map(page => {
  const fileContents = fs.readFileSync(page, 'utf8')
  const {data, content} = matter(fileContents)
  const path = page.replace('.md', '');
  let slug = path === 'docs/index' ? 'docs' : path;
  slug = "/" + slug + "/"
  return {
    slug,
    content,
    frontmatter: {
      ...data
    }
  }
})

//algolia update index code

Enter fullscreen mode Exit fullscreen mode
  • I’m getting all markdown files from /docs/ directory with globby,
  • reading content of the files with fs,
  • parsing files with gray-matter for markdown,
  • and doing some magic tricks to convert file path to slugs which are used.

In my case, slugs are the just file paths, where the file path looks like this /docs/{category}/{title}.md, for example:/docs/integrations/next-translate.md, so slug will look like this: /docs/integrations/next-translate/

In the end I’m getting an array of objects which looks like this:

[
    {
      "slug": "/docs/integrations/next-translate/",
      "content": "My article content for indexing",
      "frontmatter": {
        "category": "Integrations",
        "date": "2022-01-20",
        "some-other-properties": "properties"
      }
    }
]

Enter fullscreen mode Exit fullscreen mode

Update Algolia index

Algolia provides a really simple and easy to use JavaScript client, so there no magic here. We are acquiring our index, and saving objects which we created in previous step.

const client = algoliasearch(
  'APP_ID',
  'ADMIN_API_KEY'
);
const index = client.initIndex("simplelocalize-docs")
index.saveObjects(objects, {
  autoGenerateObjectIDIfNotExist: true
});

Enter fullscreen mode Exit fullscreen mode

Include index update in package.json

{
  "name": "simplelocalize-docs",
  "version": "1.0.0",
  "private": true,
  "scripts": {
    "dev": "next dev",
    "build": "next build && next export -o build/ && i18n:download && index:docs",
    "index:docs": "npx ts-node --skip-project index-docs.mjs",
    "i18n:upload": "simplelocalize upload",
    "i18n:download": "simplelocalize download"
  }
}

Enter fullscreen mode Exit fullscreen mode

You can execute the script manually by running npm run index:docs.

algolia search index overview

That’s it? That’s it! See the search in action on SimpleLocalize documentation page.

💖 💪 🙅 🚩
jpomykala
Jakub Pomykała

Posted on April 18, 2022

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related