Hugoに移行して以来かなり満足度が高いブログ活動ですが、記事機能がないのが惜しいなと思っています。検索の用途としては自分が昔書いたことを発掘するのがまず第一です。案外助けられることが多いんです。
もちろん、Google検索でsite:
オプションを使うなどしても実現できるのはわかっています。ただ、やはり自分でやってみたいという気持ちがあったので今回チャレンジしました。すでにファーストバージョンは完成していて、画面右上から検索用のページへ移動できます。リアルタイムな絞り込み検索を試せます。
Hugoは静的サイトなので、検索のような動的な処理はHugo単体ではできません。
調べているとJSを使う方法や外部のサービスを使う方法など幾つかみつかりました。
ドキュメントにも挙がっていたAlgoliaというサービスが気になったのでさらに調べてみると、forestry.ioの記事で手順が紹介されていました。これを追体験してみることにしました。
検索インデックス用のJSON
HugoのCustom Output Formatsの仕組みを用いて、サイトのビルド時にAlgoliaに登録するためのJSONを生成します。RSSを出力するときと同じような方法で比較的簡単に作ることができました。
config.toml
AlgoliaはJSON形式で検索インデックスを登録できます。なんとなく触った感じ指定のフォーマットがないので、記事タイトル、本文、タグ当たりを含めたらいいと思います。
forestry.ioの設定をベースに、config.toml
に次のような設定を追加しました。
[outputFormats.Algolia]
baseName = "algolia"
isPlainText = true
mediaType = "application/json"
notAlternative = true
[params.algolia]
vars = ["title", "content", "date", "publishdate", "description", "permalink", "thumbnail"]
params = ["tags"]
元記事と異なるのは、params
からcategory
とexpiryDate
を削除しdescription
を追加したことと、vars
のsummary
をcontent
に変えたことです。
最後にoutputsにalgoria
を追加して完成です。
[outputs]
home = [ "HTML", "RSS", "algolia" ]
JSON用のテンプレート
ファイルを生成するときに使うテンプレートが必要です。layouts/_default/list.algolia.json
としてファイルを作り、中身はこのようにしました。
{{/* Generates a valid Algolia search index */}}
{{- $hits := slice -}}
{{- $section := $.Site.GetPage "section" .Section }}
{{- $validVars := $.Param "algolia.vars" | default slice -}}
{{- $validParams := $.Param "algolia.params" | default slice -}}
{{- range $i, $hit := where .Data.Pages "Section" "posts" -}}
{{- $dot := . -}}
{{- if or (and ($hit.IsDescendant $section) (and (not $hit.Draft) (not $hit.Params.private))) $section.IsHome -}}
{{/* Set the hit's objectID */}}
{{- .Scratch.SetInMap $hit.File.Path "objectID" $hit.UniqueID -}}
{{/* Store built-in page variables in iterable object */}}
{{- .Scratch.SetInMap "temp" "date" $hit.Date.UTC.Unix -}}
{{- .Scratch.SetInMap "temp" "publishdate" $hit.PublishDate -}}
{{- .Scratch.SetInMap "temp" "dateString" (substr $hit.PublishDate 0 10) -}}
{{- .Scratch.SetInMap "temp" "content" ($hit.Plain | truncate 2000) -}}
{{- .Scratch.SetInMap "temp" "title" $hit.Title -}}
{{- .Scratch.SetInMap "temp" "permalink" $hit.Permalink -}}
{{- .Scratch.SetInMap "temp" "description" $hit.Description -}}
{{- .Scratch.SetInMap "temp" "thumbnail" $hit.Params.thumbnail -}}
{{/* Include valid page vars */}}
{{- range $key, $param := (.Scratch.Get "temp") -}}
{{- if in $validVars $key -}}
{{- $dot.Scratch.SetInMap $hit.File.Path $key $param -}}
{{- end -}}
{{- end -}}
{{/* Include valid page params */}}
{{- range $key, $param := $hit.Params -}}
{{- if in $validParams $key -}}
{{- $dot.Scratch.SetInMap $hit.File.Path $key $param -}}
{{- end -}}
{{- end -}}
{{- $.Scratch.SetInMap "hits" $hit.File.Path (.Scratch.Get $hit.File.Path) -}}
{{- end -}}
{{- end -}}
{{- jsonify ($.Scratch.GetSortedMapValues "hits") -}}
ずらっと書いてもしょうがないのですが、いくつか元記事とは異なる箇所があります。
本文を全部含めてしまうとAlgoliaの1データあたりのデータ上限を超えてしまうので、contentは.Plain
を2000文字でtruncate
するようにしました。また、サムネイルもを含めるようにしました。
また、tagのページなどが含まれてしまうので、where
をつかって"Section"
が"posts"
のものに絞り込んでループを回しています。
これでビルドするとjsonが作られます。ローカルではhttp://localhost:1313/algolia.json
にアクセスすると確認ができます。
Algoliaの準備
続いてAlgoliaに登録しましょう。
Algoliaは
Algolia is the most reliable platform for building search into your business.
ということで、検索機能を提供してくれるサービスです。ここに先ほどHugoで生成したJSONを元に検索対象のデータを登録していきましょう。
なにはともあれユーザー登録を済ませます。無料で使えるライセンスがあるので僕はそれを選択しました。
無料版の制約は
- 登録可能なデータの上限
- データ操作回数の上限
- algoliaのロゴを表示
といったもので、ブログ用途には十分です。
続いて、NEW APPLICATIONからアプリケーションを登録します。名前を適当にいれればOK。
するとインデックスの登録や検索に使うためのAPI Keyが発行されます。
後ほどこれらを使用します。
Algoliaへインデックスを登録
Hugoを手元でビルドしてアップロードしている人の場合はAlgoliaへの登録も手元から行えばいいのですが、Netlifyのようにビルドお任せサービスを使っている場合にはそうはいきません。幸い、生成されたalgolia.jsonはどこからもアクセスできますので、ビルド後のWebhookでAlgoliaへアップロードする仕組みをトリガーしてあげなければいけません。
やることはごくシンプルで、書き出されたJSONを読み取ってAlgoliaのAPIでデータを登録していくだけなので、自分で書くことは比較的容易です。各種言語に対応したクライアントライブラリも存在します。
ですが、それをどこで実行するのか、それ用のサーバーを別途用意するのか。それとも自分用に動かしているGoのサーバーに機能を追加するのか。今回は元記事を参考に、サーバーレスでタスクを動かせるWebtaskを利用して実施することにしました。
元記事に詳細な手順があるので、ここでは手順を簡単に紹介しておきます。
- WebTaskでサインアップする
$ git clone https://github.com/forestryio-templates/serverless-atomic-algolia
する$ npm install serverless -g && npm install
で必要なものをインストール$ serverless config credentials --provider webtasks
でログインするconfig/secrets.yml.stub
ファイルをconfig/secrets.yml
としてコピーするconfig/secrets.yml
をAlgoliaのダッシュボードの自分のアプリの設定に書き換えるconfig/index.js
の一部を書き換え。Algolia内のIndex名や、サイトのJSONのURLを記載。$ serverless deploy
最後のデプロイでこんな感じの↓URLが発行されます。
https://wt-60952c750afexxxxxxxxxxxxxxxx-0.sandbox.auth0-extend.com/yyyyyyyyyyy
ここにアクセスすると実際にコードが動くわけですが、僕の場合は2つほど依存関係のエラーが起こりました。追加でライブラリを追加し再度デプロイすることでエラーが解消してAlgolia側にデータが登録されることを確認できました。
$ npm install --save source-map-support
$ npm install --save babel-runtime
Algoliaに登録されたデータの様子。340recordsと表示されています。管理ページ内で検索を試みることもできます。
あとはさきほどのWebTaskが定期的に実行されればOKですね。僕の場合はNetlifyのビルド後のWebhookに登録して自動で走るようにしています。
これで検索インデックスの準備はOKです。次回はブログ上に検索ボックスを配置して検索するところを紹介したいと思います。