Jamstack - GatsbyJS & WordPressで関連記事を表示させる

新しい本が出るということで、サイトの方も色々とテコ入れしているのですが、Jamstack構成のサイトで悩ましいことになるのが検索機能と関連記事の表示ではないでしょうか。

エビスコムのサイトでは、著者NOTEのページに検索機能を組み込んでいます。外部のサービスを利用しようかとも考えましたが、そのうち関連記事も組み込みたいということで、各記事の形態素解析を行い、キーワードの抽出を行う形で検索機能を組み込みました。

著者NOTEのページに組み込んだ検索機能

ところが、Gatsby v4でParallel Query Runningが搭載された結果、Gatsbyの処理の中にこれ以上重い処理を組み込むのはまずいという流れに。

そこで、形態素解析の一連の処理を、Gatsbyのライフサイクルから追い出してしまうとともに、関連記事の表示も組み込んでみました。

処理そのものは非常にシンプルです。

  1. WordPressから記事を取得
  2. 形態素解析
  3. 検索用のキーワード作成
  4. 形態素解析の結果をBM25Vectorizerに渡してベクトル化
  5. similarity(コサイン類似度)による比較で類似記事の抽出

文章の評価に関してtf-idfしか知らなかったもので、JavaScriptで使えるtf-idfのライブラリーを探したものの、tf-idfを計算してくれるだけでベクトル化などは自前で用意しなければならなかったもので苦労しました。
しかし、Okapi BM25にたどりついて、そちらを探したところ、winkjsBM25によるベクトル化、コサイン類似度まで完結できるので、非常にシンプルな構成になりました(tokens().out()な部分は、kuromojinからの出力に置き換えればなんとかなります)。

形態素解析: kuromojin
https://github.com/azu/kuromojin

文章の評価: BM25Vectorizer/similarity
https://winkjs.org/

記事の鮮度なども加味してバランスを取りつつ、こんな感じのデータが取得できています(想像していたものとは違うものの、なかなか納得できる結果でした)。

gatsby-v4-react-helmet [
{ slug: 'gatsby-v4-wordpress-trouble', similarity: 0.175817 },
{ slug: 'gatsbyjs-gutenberg-css', similarity: 0.097883 },
{ slug: 'microcms-imgix-gatsby', similarity: 0.089381 },
{ slug: 'site-and-gatsby', similarity: 0.085722384 },
{ slug: 'file-system-route-api', similarity: 0.08334787199999999 },
]

こうして出来上がったデータですが、Gatsbyで扱うことが目的です。JSONファイルとして出力して、gatsby-transformer-jsonで読み込むのがシンプルですが、JSONファイルの扱いでトラブルになりそうなので、今回はMongoDBにデータベースを用意することにしました。

MongoDBに用意したデータベース

Gatsbyからは、gatsby-source-mongodbで簡単に扱うことができます。

あとは、predevprebuildでデータベースを更新して、ビルドの際には必要なデータをMongoDBから取得するだけです。

ひとまず、これで目的は達成です。しかし、WordPressとMongoDBからのデータをslugをもとに探しながら処理しなければならないのは、ちょっと面倒です。

そこで、gatsby-node.jsに以下のように追加して、スキーマーをカスタマイズします。

exports.createSchemaCustomization = ({ actions, schema }) => {
const { createTypes } = actions
createTypes(`
type WpNote implements Node {
keyword: mongodbEbisucomNotekeyword @link(from: "slug" by: "slug")
}
`)
}

これで、WordPress側のWpNoteというノードにkeywordというフィールドを追加し、そこにMongoDBから読み込んでいるノードを繋いでくれます。繋ぐ際には、両方のノードのslugが一致しているものを繋ぐように指定しています(この場合、 by: "slug"だけでも機能します)。

公式のCustomizing the GraphQL Schema: foreign-key-fieldsのあたりが参考になります。

フィールド単位で持ってくるのであれば、リゾルバーを利用するのが正解だと思いますが、今後の拡張も考えて、ざっくりと繋いでしまいます。

GraphiQLで確認してみると、こんな感じに繋がれているのが確認できます。

MongoDBから読み込んでいるノード
WordPressから読み込んでいるWpNoteノードにkeywordフィールドが追加され、MongoDBのノードが繋がれています。

あとは、今までのクエリをちょっと書き換えれば、用意したデータも簡単に扱うことができます。

用意したデータを使って作成した関連記事の表示

こんな感じでゆる~く何とかなるのも、Gatsbyの魅力だと思います。