Algolia Search API integration with Hugo website

Hello,
I would like to see an example of integration of Algolia’s Search API in Hugo website.

According to the https://gohugo.io/tools/search/#commercial-search-services it’s possible.

Thank you!

There may be several ways to do this, but I would recommend you look into AlpineJS.

I quickly updated an old test repo to the latest AlpineJS version here:

Nothing fancy, but you should get the building blocks you need.

2 Likes

Hi @bep. Thank you very much for your answer!!! I see config.toml file contains algolia ID and algolia API key. Is it possible to hide a secret key using Hugo or I have to use server side code for this purpose?

Thank you!

This is a client side key, so it’s not a secret. I may have named it so it sounds like a secret, but it’s supposed to be shared. You need to create an API key (not a secret) in the Algolia console that has very limited access (search only).

Hi @bep. Thank you very much for your answer!

I took Zdoc | Hugo Themes theme for our large project with Hugo and I would like to use there Algolia Search. I have a problem implementing Algolia Search in this Zdoc theme. Can you help me please?

Thank you very much!

Raya

I switched us over to Algolia on an e-commerce site I work on. It was fairly easy to implement using the algoliasearch npm package. We just wrote the search index as a template and then exported it on every production build using a custom Netlify plugin. I’d be happy to share more details if you’re interested, but Bep’s solution definitely looks simpler.

If you’re interested you can see our implementation in action here.

One thing to consider is whether or not your want to generate your own search index or not. A custom search index gives you a lot more control over what shows up in search and how it’s categorized. Although letting Algolia do it automatically is of course easier, although I expect that your millage may vary with the usefulness of the results.

All that said, if you want something closer to a pre-built solution, I’m not sure Zdoc is the way to go as it seems to have it’s own search built in that doesn’t use Algolia. There are some Hugo themes that support Algolia integration right out of the box though. I found one called Docsy that seems to support Algolia as an option, for example.

Hi @LandGod. Thank you for your answer! I really want to use a simple solution by replacing the existing search in ZDoc theme by Algolia search created by @bep. Currently I have issues when I do it and I really need help!

Hi @rayalevinson. Unfortunately I don’t think that there will be an easy way to do this. It is certainly possible, but would require writing and understanding a fair amount of custom code as well as just messing around with things to see how it worked. It’s not really something where we can just tell you how to do it in a forum post. There is no step-by-step process.

Hi @LandGod. Thank you for your answer. I found how to change the original ZDoc code in order to work with @ben code. It works! Now when I want to work with my data in Algolia I need to know what url should I use. In the example I see that https://latency-dsn.algolia.net/1/indexes/*/queries’ is used. Where can I find what url should I use in my case?

I see only API keys on my Algolia account.

Thank you very much!

Just looking at Bep’s code, it looks like you need to use https://YOUR_APP_ID_HERE-dsn.algolia.net/1/indexes/*/queries where you put your Algolia app id instead of YOUR_APP_ID_HERE. I would recommend checking out the Algolia docs for details on how this actually works though.

If this can help, we built an Algolia/Meili Hugo module which sports the solution to easily build the index, and frontend using instantsearch.js (mostly set through your configuration file, no js needed)

Let me know if this helps: GitHub - theNewDynamic/hugo-module-tnd-search: Everything search for Hugo

1 Like

You are right @LandGod. This is the right path. It works! Thank you @bep and @LandGod for your help!!!

When I search this topic via Google I always land on this page, it seems zero progress.

Here is my finding: Algolia provides DocSearch + Crawler limited to 10,000 index records, free, for documentation sites. If your web page has 10 subtitles (which is typical) then Crawler will create 10 records in Index. Start from this DocSearch, play a little bit, and find index structure.

What I found is this:

  1. This template will create JSON file which you can upload to newly created index at Algolia,

layouts/index.json

{{ $pages := .Site.RegularPages -}}
[
{{- $first := true -}}
{{- range $p := $pages -}}
{{- if not $first -}},{{ end -}}
{
  "objectID": {{ (printf "%s--lvl0" $p.Permalink) | jsonify }},
  "url": {{ $p.Permalink | jsonify }},
  "anchor": "",
  "type": "lvl0",
  "hierarchy": {
    "lvl0": {{ $p.Title | jsonify }},
    "lvl1": null,
    "lvl2": null,
    "lvl3": null,
    "lvl4": null,
    "lvl5": null
  },
  "content": ""
}
{{- $first = false -}}
{{- $headingMatches := findRE "(?m)^#{1,6}\\s+.*$" $p.RawContent -}}
{{- range $idx, $heading := $headingMatches -}}
{{- $countHash := len (replaceRE "[^#]+" "" $heading) -}}
{{- $headingText := strings.TrimSpace (replaceRE "^#{1,6}\\s+" "" $heading) -}}
{{- if not $first -}},{{ end -}}
{
  "objectID": {{ (printf "%s--heading%d" $p.Permalink $idx) | jsonify }},
  "url": {{ (print $p.Permalink "#" ($headingText | urlize)) | jsonify }},
  "anchor": {{ ($headingText | urlize) | jsonify }},
  "type": {{ (print "lvl" (sub $countHash 1)) | jsonify }},
  "hierarchy": {
    "lvl0": {{ $p.Title | jsonify }},
    "lvl1": {{ if eq $countHash 2 }}{{ $headingText | jsonify }}{{ else }}null{{ end }},
    "lvl2": {{ if eq $countHash 3 }}{{ $headingText | jsonify }}{{ else }}null{{ end }},
    "lvl3": {{ if eq $countHash 4 }}{{ $headingText | jsonify }}{{ else }}null{{ end }},
    "lvl4": {{ if eq $countHash 5 }}{{ $headingText | jsonify }}{{ else }}null{{ end }},
    "lvl5": {{ if eq $countHash 6 }}{{ $headingText | jsonify }}{{ else }}null{{ end }}
  },
  "content": ""
}
{{- $first = false -}}
{{- end -}}
{{- if gt (len $p.Plain) 0 -}}
{{- if not $first -}},{{ end -}}
{
  "objectID": {{ (printf "%s--content" $p.Permalink) | jsonify }},
  "url": {{ $p.Permalink | jsonify }},
  "anchor": "",
  "type": "content",
  "hierarchy": {
    "lvl0": {{ $p.Title | jsonify }},
    "lvl1": null,
    "lvl2": null,
    "lvl3": null,
    "lvl4": null,
    "lvl5": null
  },
  "content": {{ $p.Plain | jsonify }}
}
{{- $first = false -}}
{{- end -}}
{{- end -}}
]

Do not forget to add JSON to config:

outputs:
  home:
    - HTML
    - JSON

Run hugo to create index.json. Upload it to Algolia. In Algolia console, configure searchable attributes.

Use standard DocSearch JS library as is it will work perfectly with this index. You only need to pass API Key, App Id, and Index Name to it. It works!

The only issue is, index size, it will contain about 10x more records than number of indexed pages (since it tries to provide per-section scroll). It will index subtitles and corresponding paragraphs (but in provided example it is not fully implemented yet); so that if in DocSearch popup you click Heading Level 3 it will open corresponding page and scroll to this exact location.

I was able to test it with my 200k+ pages website, but index size was about 3.5 million records, extremely fast search, but I stopped it because Algolia estimated it about $1650/month. They have another plan with up to 1 million records free.

My next step is to build widget similar to DocSearch and to have SOLR based schema on personal hosting. I also can try to optimize layouts/index.json so it will generate 10x less records (although I can lose some functionality).

P.S. You can see exact DocSearch GET queries in Chrome Dev Tools; compare with this index.json file to understand more details. DocSearch uses Auto-Suggest, each click on keyboard forces new GET request to be sent to Algolia, it works very fast.

P.P.S.
Thanks to OpenAI “o1” model for creating layouts/index.json :innocent:

[UPDATE] I forgot to add: my JSON file was so large (3.5 mlns objects) that I created small Java application to upload it in batches, here it is:

package algolia;

import com.algolia.api.SearchClient;
import com.algolia.model.search.Action;
import com.algolia.model.search.BatchRequest;
import com.algolia.model.search.BatchWriteParams;
import com.fasterxml.jackson.databind.ObjectMapper;

import java.io.FileInputStream;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

public class UploadRecordsExample {
    public static void main(String[] args) throws Exception {
// 1. Create a SearchClient using your Algolia Application ID and Admin API key.
//    Replace these strings with your own.
        SearchClient client = new SearchClient(
                "APPLICATION_ID",
                "ADMIN_API_KEY"
        );
        ObjectMapper objectMapper = new ObjectMapper();
        InputStream input = new FileInputStream("/Users/fe/Projects/MasteryEducation/MyWebsite.com/public/index.json");
        MyAlgoliaRecord[] records = objectMapper.readValue(input, MyAlgoliaRecord[].class);
        ArrayList<MyAlgoliaRecord> list = new ArrayList<>();
        int counter = 0;
        for (MyAlgoliaRecord r : records) {
            counter++;
            list.add(r);
            if (counter % 1000 == 0) {
                submitBatch(client, list);
                list.clear();
            }
        }
        // Optional: close the client if needed (e.g., in a long-running application)
        client.close();
    }

    private static void submitBatch(SearchClient client, List<MyAlgoliaRecord> r) {
        client.batch(
                "index_name", //name of your index at Algolia
                new BatchWriteParams()
                        .setRequests(
                                createBatchRequests(r)
                        )
        );
    }

    private static List<BatchRequest> createBatchRequests(List<MyAlgoliaRecord> r) {
        return r.stream()
                .map(record -> new BatchRequest()
                        .setAction(Action.ADD_OBJECT)
                        .setBody(record))
                .collect(Collectors.toList());
    }
}


package algolia;

import lombok.Data;

@Data
public class MyAlgoliaRecord {
    private String objectID;
    private String url;
    private String anchor;
    private String type;
    private String content;
    private Hierarchy hierarchy;
}


package algolia;

import lombok.Data;

@Data
public class Hierarchy {
    private String lvl0;
    private String lvl1;
    private String lvl2;
    private String lvl3;
    private String lvl4;
    private String lvl5;
}

Thanks,

Hi everyone, I just found this thread today and see there is a pretty recent update. Nice work @Fuad_Efendi! I manage DocSearch at Algolia and just wanted to clarify one thing - DocSearch is 100% free for both search and records up through 1M records. Generally, this is more than enough to cover typical, even “large” documentation sites. Exceptionally large documentation sites rarely have more than a few hundred thousand records. If you have a use case that requires more than that, I recommend trying to get ahold of us in our Discord forums. You can find the link on our main page. Thanks!

2 Likes