🔍

Hugo 製のブログに Pagefind で検索機能をつけて Cloudflare Pages でデプロイする

2024/01/21に公開

概要

PagefindHugo 製のブログに検索機能をつけてみた記事です。
(この記事は個人ブログに投稿した記事に加筆したものです。)

開発環境

  • pagefind_extended v1.0.4
  • hugo v0.120.4

実装

検索 UI 取り付け

まずは Getting Started with Pagefind に従って検索 UI をブログに取り付けます。
<head> 内に下記を追加しました。(CSS を上書きするので、ブログ本体の CSS 読み込みより前に追加する。)

layouts/_default/baseof.html
<link href="/pagefind/pagefind-ui.css" rel="stylesheet">
<script src="/pagefind/pagefind-ui.js"></script>
<script>
    window.addEventListener('DOMContentLoaded', (event) => {
        new PagefindUI({
            element: "#search",
	    showImages: false,
	    translations: {
                clear_search: "消去",
                zero_results: "[SEARCH_TERM]の検索結果はありません",
                alt_search: "[SEARCH_TERM]の検索結果はありませんでした。[DIFFERENT_TERM]の検索結果を表示しています",
                search_suggestion: "[SEARCH_TERM]の検索結果はありませんでした。次のいずれかの検索を試してください",
            }
        });
    });
</script>

検索 UI はデフォルトの物を使い、Pagefind UI configuration options を見て一部のオプションを調整しています。

翻訳についてはデフォルトの文言定義が pagefind_ui/translations/ja.json にあるのですが、結果が0件のときの日本語が不自然に思えたので変更しています。
(不自然だと感じる自分の日本語感に自信が持てなくて修正の PR を出すに至っていません……。)

あと検索 UI のダークモード対応をするため、Using the Default UI を見て CSS 変数を上書きしておきました。

そして検索ボックスを出したい位置に

layouts/_default/baseof.html
<div id="search"></div>

を追加しておきます。

インデックス作成

次に pagefind の実行ですが、私が Hugo 製のブログを開発している環境には Node.js を入れていないのでバイナリをダウンロードしました。

https://github.com/CloudCannon/pagefind/releases

Installing and running Pagefind によると

Pagefind publishes two releases, pagefind and pagefind_extended. The extended release is a larger binary, but includes specialized support for indexing Chinese and Japanese pages.

とのことですので、pagefind_extended のほうを使います。
ダウンロード後、展開してブログのルートディレクトリに置きます。

pagefind はビルド結果に対してインデックスを作成するので、一旦 hugo コマンドを打って /public にビルド結果を生成します。

そしてインデックス作成ですが、私は個別の記事だけを検索対象としたいので CLI のオプションで対象を絞ります。
私のブログでは /logs/posts の下に個別の記事が存在するので --glob="{logs,posts}/*/*.html" というオプションを付けます。
{logs,posts}/**/*.html だと一覧ページである /logs/index.html/logs/page/2/index.html も対象に含まれてしまうので * の数は重要です。

また、この後 hugo server で起動したときに pagefind のファイルを読み込めるよう、結果を /static に吐き出します。

結果的に実行するのは下記のコマンドになります。

./pagefind_extended --site public --glob="{logs,posts}/*/*.html" --output-path="static/pagefind"

これでインデックス作成が完了しました。

hugo server で起動して検索すると下記のような感じになります。

検索対象の調整

あとは Configuring what content is indexed を参考にしてお好みで検索対象の調整をします。

Hugo 特有の注意点として、テンプレートファイル内で特定の条件で data-pagefind-body を付けたい場合、下記のように safeHTMLAttr を通さないと "zgotmplz" という文字列に強制置換されてしまいます。

<body {{ if .IsPage }}{{ "data-pagefind-body" | safeHTMLAttr }}{{ end }}>
<!-- 中略 -->
</body>

参考: safe.HTMLAttr

デプロイ

開発が終わったら Cloudflare Pages にデプロイします。
なおリポジトリの連携等の初期設定は終わっているものとします。

まず .gitignore に static/pagefindpagefind_extended を追記し、ローカル開発時に使用しただけのファイルはコミットしないようにします。

.gitignore
public/
.hugo_build.lock
+ static/pagefind
+ pagefind_extended.exe

デプロイ時に pagefind を実行するため pagefind.sh というシェルスクリプトを作成しました。

pagefind.sh
#!/bin/bash

wget https://github.com/CloudCannon/pagefind/releases/download/v1.0.4/pagefind_extended-v1.0.4-x86_64-unknown-linux-musl.tar.gz
tar -zxvf pagefind_extended-v1.0.4-x86_64-unknown-linux-musl.tar.gz
./pagefind_extended --site public --glob="{logs,posts}/*/*.html"

npx で pagefind をインストールするのではなくバイナリをダウンロードしているのは私の好みです。
Cloudflare Pages のデプロイ環境のデフォルトの Node.js のバージョンは非常に古いのでそのまま使いたくないし、だからといって pagefind を入れるために最新の Node.js をダウンロードするのも二度手間っぽいという理由です。
(それと、時々 Node.js のダウンロードに異常に時間がかかってデプロイが進まないことがある気がします……。)

あとは Cloudflare Pages のビルドコマンドを

hugo && chmod +x ./pagefind.sh && ./pagefind.sh

に変更します。

その後、pagefind.sh もろともソースコードを push すればデプロイ完了です。

余談:検索結果について

たとえば「画像生成」と検索してみると

実際には「画像」というキーワードで検索されていることがわかります。

あと「した」と検索すると「したら」「したがって」が含まれるものが検索されるのですが

この検索結果には「~した。」という文章が含まれている記事は登場しません。

このように、決して入力したキーワードに合致する部分がすべて抽出されるわけではないため、もしプロダクション環境で使うような場合は求めている精度に到達しているか検証する必要があるかもしれません。

Discussion