Hugoで作ったWebサイトに検索機能をつける

この記事は公開されてから1年以上経過しており、最新の内容に追従できていない可能性があります。

どうやって検索をつけるか

Hugoは静的なページを生成するので、ダイナミックに変化する検索のような機能をつけることは工夫なしにはできない。 逆に、ダイナミックに変化しないもの、コンテンツからわかるものはできます。例えばカテゴリ一覧とかタグ一覧とか。

検索をつけるにはいくつかの方法が考えられる。例えば…

  • Elasticsearchのような検索エンジンと、そのAPIを用意する。
  • 自前の検索を諦める。
  • JSに書き出してブラウザ上で検索する。

検索エンジンとAPIを用意する

ElasticsearchとかSolrとかそういう。

作っているWebサイトがどの程度の規模になるかにもよって、大抵の場合はオーバーキルというか、面倒。

検索エンジンの運用、メンテも必要になって、例えば、コンテンツをどうやって更新するか、APIの運用したり。なので、プロダクションでがっつり運用!の候補にはあんまりならないんじゃないだろうか…?

自前の検索を諦める

例えばGoogleカスタム検索とか、Algoliaとかそういった検索APIをホストするものを使ったりするのが候補になりそう。

特に Algolia は大きなフレームワークなんかのドキュメントでもよく見かけるような気がするけれど、これはオープンソースサポートするよ!というところからかな。

JSに書き出してブラウザ上で検索

  • Hugoで全記事の情報をJavascriptに書き出す
  • Javascript上でシュッと検索する

という、それはそう、みたいな実装でできる。

欠点としては、あいまいな検索が弱く、全記事だすとデータ量がちょっと大きくなる。が、ブラウザキャッシュも効かせられるので、そんなに気にしなくてもいいかもしれない。大きなサイトならデータを絞ったり、Javascriptを分割したりすると良いかもしれない。 例えば、頻繁に記事がかかれるのであれば、年ごとのデータファイルを用意して、順番にフェッチして検索していく、とか。

このブログでは500kbくらいとそんなに大きくなかったので、まあいいか、とそのままにしている。

こんな感じで記事データを書き出していて、

{{ define "escape" }}
    {{- .
        | replaceRE "[。、]" " "
        | replaceRE "[[:space:]]+" " "
        | replaceRE "[[:space:]]+,|,[[:space:]]" ","
        | replaceRE ",,+" ","
    }}
{{ end }}

{{ define "showBody" }}
    {{- $tags := "" }}
    {{- range .Params.Tags }}
        {{- $tags = printf "%s %s" $tags .}}
    {{- end }}
    {{- template "escape" (printf "%s %s %s" .Title $tags .Plain) }}
{{ end }}

{{ define "showTags" }}
    [
        {{- range .Params.Tags }}
            {
                url: {{- relURL (printf "tags/%s/" (. | urlize)) }},
                name: {{- template "escape" . }},
            },
        {{- end }}
    ]
{{ end }}

{{ define "scripts" }}
const data = [
    {{- range $index, $page := .Site.Pages }}
        {{- if eq $page.Type "archives" }}
            {{- if ne $page.Draft }}
                {{- if findRE "archives/\\d+/\\d+/.+" $page.Permalink }}
                    {
                        url: {{- $page.Permalink }},
                        title: {{- $page.Title }},
                        date: {{- $page.Date.Format "2006/01/02" }},
                        tags: {{- template "showTags" . }},
                        body: {{- template "showBody" . }},
                    },
                {{- end }}
            {{- end }}
        {{- end }}
    {{- end }}
];
{{ end }}

こんな感じで、愚直な実装をしている。

const result = [];
data.forEach((d) => {
    if (d.body.match(word) === null) {
        return;
    }

    result.push({
        data: d,
        point: d.body.split(word).length,
    })
});

result.sort((a, b) => { return b.point - a.point; });

ここでpointを計算するロジックを調整すると、いい感じになりそうな雰囲気がある。

参考にした記事

サイト案内

運営してるひと: @sters9

最近は Go, Ruby, Rails, Kubernetes, GCP, Datadog あたりをしていますがもっといろいろやりたい!

サイト案内

開発環境の紹介

プライバシーポリシー

tools.gomiba.co

サイト内検索

アーカイブ

2024/09 (3) 2024/07 (1) 2024/06 (3) 2024/05 (1) 2024/04 (7) 2024/03 (4) 2024/01 (3)

2023/12 (1) 2023/11 (3) 2023/10 (1) 2023/09 (1) 2023/08 (2) 2023/05 (4) 2023/04 (4) 2023/03 (4) 2023/02 (2) 2023/01 (1)

2022/12 (2) 2022/11 (4) 2022/10 (3) 2022/09 (2) 2022/08 (4) 2022/07 (5) 2022/06 (4) 2022/05 (9) 2022/04 (8) 2022/03 (10) 2022/02 (21) 2022/01 (8)

2021/12 (11) 2021/11 (1) 2021/10 (4) 2021/09 (2) 2021/08 (1) 2021/07 (2) 2021/06 (5) 2021/05 (10) 2021/04 (1) 2021/03 (8) 2021/02 (12) 2021/01 (8)

2020/05 (2) 2020/04 (2) 2020/02 (2) 2020/01 (1)

2019/12 (3) 2019/11 (2) 2019/10 (5) 2019/09 (3) 2019/07 (6) 2019/06 (4) 2019/04 (3) 2019/01 (2)

2018/12 (6) 2018/10 (4) 2018/09 (6) 2018/08 (7) 2018/07 (16) 2018/06 (7) 2018/05 (7) 2018/04 (5) 2018/03 (3) 2018/02 (10) 2018/01 (6)

2017/12 (8) 2017/11 (6) 2017/10 (10) 2017/09 (12) 2017/08 (12) 2017/07 (3) 2017/06 (1) 2017/01 (4)

2016/12 (5) 2016/10 (3) 2016/09 (1) 2016/07 (2) 2016/06 (1) 2016/04 (1) 2016/02 (1) 2016/01 (2)

2015/12 (1) 2015/10 (1) 2015/09 (3) 2015/06 (1) 2015/01 (1)

2014/08 (2) 2014/07 (3) 2014/05 (1) 2014/01 (7)

2013/12 (2) 2013/11 (4) 2013/10 (1) 2013/09 (1) 2013/08 (3) 2013/07 (4) 2013/06 (5) 2013/05 (2) 2013/04 (7) 2013/03 (1)