当サイトを最適な状態で閲覧していただくにはブラウザのJavaScriptを有効にしてご利用下さい。
JavaScriptを無効のままご覧いただいた場合には一部機能がご利用頂けない場合や正しい情報を取得できない場合がございます。
承知しました
本サイトではWebサイトのエクスペリエンスを向上させるために、Cookieを使用しています。Cookieはブラウザの設定から無効にできます。本サイトで使用するCookieについては、プライバシーポリシーをご確認ください。

Blog

ブログ

開発者向け

Algolia、Rails、Kontent、Next.jsでヘッドレスコンテンツを検索する

By Tom Marshall  

このゲスト記事では、KyanのTom Marshallが、関連する結果を返す魅力的で高速な検索エクスペリエンスを提供するプロセスについて説明します。

以前に検索機能を実装したことがある人なら誰でも知っているように、検索は一見複雑な問題です。グーグルは高い基準を設定しました。ユーザーは当然、入力の間違いや不正確さを許容しながら、検索エクスペリエンスが最初の数件の結果で求めているものを正確に返すと想定しています。

ユーザーの観点からは、検索は「正しく機能する」はずですが、それは口で言うほど簡単ではありません。

ヘッドレスCMSは、コンテンツをプレゼンテーション層から切り離します。それは多くの利点を提供します。それが私たちがヘッドレスを愛する理由ですが、検索機能にはそのプレゼンテーションの知識が必要です。コンテンツがどのように組み合わされてサイトのページを形成するかというこのコンテキストがなければ、ヘッドレスCMSは、箱から出してすぐにユーザーの期待に応えることができる検索ソリューションを提供できません。

ありがたいことに、AlgoliaのようなSearch-as-a-Service製品との統合は、ここでソリューションを提供します。

Kyanでは、魅力的で高速な検索エクスペリエンスを提供し、最も重要なこととして、関連する結果を返します。この記事では、私は、どのようにお見せしましょうAlgoliaNext.jsRuby on Railsを、とKenticoによってKontent

私たちのシステムアーキテクチャ

WebフロントエンドとしてNext.jsプロジェクトがあります。これは、KontentヘッドレスCMSからコンテンツをフェッチし、Rails APIと通信して追加機能(コメントなど)を取得し、Algoliaのインスタント検索を統合して検索UIを提供します。

Kontentは正規のコンテンツストアですが、AlgoliaはKontent DeliveryAPIからリアルタイムで読み取ることはできません。それは遅すぎるでしょう。ローカルコピーが必要です。たとえば、夜間のcronジョブを使用して、コンテンツを定期的に一括でコピーすることもできますが、コンテンツの変更が公開されても検索結果の同期を維持する必要があります。

変更をプルするのではなく、プッシュする必要があります。

Kontentは、コンテンツの変更が公開されるとWebhook通知を発行しますが、生のKontentWebhook通知をAlgoliaに直接接続することはできません。これらのWebhook通知をリッスンし、検索に関連するコンテンツを抽出して、それをAlgoliaにプッシュするには、途中で何かが必要です。

Kyanでは、バックエンドサービスとAPIを構築するためのRailsが大好きなので、Railsアプリになりますが、Next.jsAPIエンドポイントまたはサーバーレス関数でもかまいません。 HTTPを話すものである必要があり、理想的にはKontentDeliveryおよびAlgoliaAPIで利用可能なオープンソースクライアントライブラリがありますが、それは必須ではありません。

インデックスの設計

Algoliaはコンテンツの完全なコピーを必要とせず、検索機能とUIに関連するフィールドのみを必要とします。必須のフィールドを決定するには、次の質問に答える必要があります。

必須のフィールド:

  • 検索語を照合する場合
  • 結果をフィルタリングするには
  • 結果を注文するには
  • 結果カードに表示するには
  • そして最後に、参照に必要なID

例としてブログ記事を使用する:

検索用途必要なフィールド
検索語を照合する場合タイトルと本文
結果をフィルタリングするにはタグと作者
結果を注文するには公開日
結果を提示するため画像、推定読み取り時間、URLスラッグ
参照用のID KontentアイテムID

一部のフィールドは複数のカテゴリに分類される場合がありますが、これは問題ありません。完全なセットが必要です。

検索機能に必要なフィールドがわかったので、Algolia内でインデックスを維持するようにRailsアプリケーションをセットアップできます。

インデックスの維持

コンテンツ編集者がKontentで変更を加えるとき、Webhookを介してAlgoliaに伝播する必要があります。その順序は次のとおりです。

アルゴリアの設定

Algoliaダッシュボードにログインしたら、新しいアプリケーションを作成する必要があります。この段階では、インデックスの作成やレコードのインポートについて心配する必要はありません。 algoliasearch-railsがそれを処理します。 RailsおよびNext.jsアプリケーションのAPIキーを抽出する必要があります。

デモの目的で、Next.js用に事前に生成された「検索専用APIキー」とRailsアプリケーションの「管理APIキー」を使用します。本番環境では、アクセス制御を保護するための特定のAPIキーを作成し、レート制限、HTTPリファラーなどを指定する必要があります。

Webhook通知の処理

まず、Railsアプリケーションを作成し、必要な追加のgemを追加する必要があります。

 $ rails new -T --api -d postgresql blog-kentico-algolia-demo $ cd blog-kentico-algolia-demo $ bin /setup $ bundle add algoliasearch-rails kontent-delivery-sdk-ruby dotenv-rails

config/initializers/algoliasearch.rb追加して、Algoliagemを構成します。

 AlgoliaSearch.configuration = { application_id: ENV.fetch( 'ALGOLIA_APPLICATION_ID' ), api_key: ENV.fetch( 'ALGOLIA_API_KEY' ), }

次は、私たちのにそれらのAlgolia環境変数の値を追加してみましょう.env

 ALGOLIA_APPLICATION_ID= ALGOLIA_API_KEY=

AlgoliagemはRailsのActiveRecordORMのArticleモデルを作成し、生成された移行を実行する必要があります。

 $ rails g model article title:string body:text tags:string author:string published_at:datetime image:string estimated_reading_time_mins:integer url_slug:string kentico_id:string published:boolean

>注:PostgreSQLを使用しているため、移行を実行する前にtags属性を配列( t.string :tags, array: true, default: [] になるように調整します。また、 tags参照することができTagモデルが、我々は唯一の現在戦とフィルタにタグ名を必要としています。

 $ rails db:migrate

Algolia gemはActiveRecordの拡張機能として機能するため、 ArticleレコードはPostgreSQLデータベースにローカルで自動的に保持されます。このデータベースの永続性は厳密には必要ないため、サーバーレス関数またはNext.jsAPIエンドポイントを使用している場合はスキップできます。ただし、このデータベースの永続性は、 algoliasearch-rails gemがデフォルトで動作する方法であるため、Railsでの抵抗が最も少ないパスです。

algoliasearch-railsメソッドを使用して、 Articleモデル属性をどのように使用するかを定義できます。

class Article < ApplicationRecord
 include AlgoliaSearch

 algoliasearch if: :indexable? do
   # the list of attributes to include in the Algolia record
   attributes :title,
              :body,
              :tags,
              :author,
              :published_at,
              :image,
              :estimated_reading_time_mins,
              :estimated_reading_time_human_readable,
              :url_slug,
              :kentico_id

   # defines the attributes to match search terms against.
   # list them by order of importance.
   searchableAttributes %w[title tags author body]

   # attributes to filter results by
   attributesForFaceting %w[author tags estimated_reading_time_human_readable]

   # defines the ranking criteria used to compare two matching records in case
   # their text-relevance is equal. It should reflect your record popularity.
   customRanking ['desc(published_at_unix_timestamp)']
 end

 # only include articles that are published within Kontent
 def indexable?
   published?
 end

 # group the estimated reading times into a human readable set for friendlier
 # filtering, rather than having a filter option for each integer value
 def estimated_reading_time_human_readable
   case estimated_reading_time_mins
   when 0...2
Less than 2 minutes   when 2...6
     ‘2 to 6 minutes   when 6...10
     ‘6 to 10 minutes   when (10..)
     '10+ minutes'
   end
 end

 # convert the date attribute to an integer for sorting
 def published_at_unix_timestamp
   published_at.to_time(:utc).to_i
 end
end

KontentからのWebhook通知は、選択したURLにPOSTされます。着信Webhookを処理するには、 config/routes.rbルートを追加する必要があります。

 Rails.application.routes.draw do namespace :webhooks, defaults: { format : :json } do post :kontent end end

次に、そのルートのリクエストを処理するためのコントローラーアクションが必要です。

KontentのArticleアイテムへの変更からのWebhookリクエスト本文の例を次に示します。

 { 'data' : {   'items' : [ {       'id' : '18862937-3bc2-481e-9ca8-c177b813570a' ,       'codename' : 'the_9_worst_songs_about_clothing_websites' ,       'language' : 'default' ,       'type' : 'article' ,       'collection' : 'default'
 } ],   'taxonomies' : [] }, 'message' : {   'id' : 'f01a8710-3f62-4c3c-b048-61951ece3a4b' ,   'project_id' : '5e56b927-e956-012f-97f2-fce44a1b6e28' ,   'type' : 'content_item_variant' ,   'operation' : 'publish' ,   'api_name' : 'delivery_production' ,   'created_timestamp' : '2021-07-15T16:39:23.8202009Z' ,   'webhook_url' : 'https://example.com/api/v1/webhooks/kentico' #TODO
 } }

リクエストの本文には、検索インデックスに必要なArticleアイテムのすべてのフィールドが含まれているわけではないことに気付くでしょう。代わりに、Webhookは変更されたコンテンツアイテムを列挙するだけです。これが、通知を受け取った後にKontentから完全なコンテンツをフェッチする必要がある理由です。

物事を管理しやすくするために、4つの異なるファイルにまたがるRailsロジックを分解します。

>注:本番アプリケーションでは、メインスレッドの過負荷を回避するためにここでバックグラウンドジョブを使用しますが、これはこのデモの範囲を超えています。

Kontentの設定

まず、Kontentにログインして、新しい空のプロジェクトを作成します。

次に、Webhookを作成する必要があります。

Kontent Webhook実装をローカルで開発およびテストするには、パブリックインターネットからの着信Webhookがローカル開発マシンに到達する必要があります。

ngrokは、ローカルマシンにトンネリングするWebhookのパブリックURLを提供できます。

 $ ngrok http 3000

Kontentプロジェクト設定でWebhookを作成し、URLをngrokからのトンネルURLとして設定します(例: https://d347-86-150-50-107.ngrok.io/webhooks/kentico ://d347-86-150-50-107.ngrok.io/webhooks/kentico)。

>注:Rails 6以降では、ngrokホストを開発構成に追加するか、 config.hosts.clearconfig/environments/development.rb /development.rbに追加してホスト制限を完全に無効にする必要があります。

KontentプロジェクトのIDとウェブフック秘密を追加.env Algolia環境変数と一緒に:

 KENTICO_PROJECT_ID= KENTICO_WEBHOOK_SECRET=

次に、ローカルRailsサーバーを起動します。

 $ rails s

この段階で、コンテンツタイプを定義し、いくつかのテストコンテンツアイテムを作成する必要があります。 kontent-cliを使用して、デモプロジェクトリポジトリのバックアップから復元して、これを手動で行わないようにすることができます。

 $ npx @kentico/kontent-cli backup --action restore --apiKey= --projectId= --name= 'kontent-blog-demo-backup'

>注:APIキーを生成するには、Kontentプロジェクト設定で管理APIを有効にする必要があり、 --nameパラメーターは自動的に.zipサフィックスを適用します。

すべてが正常に機能している場合は、バックアップの復元によって新しく作成されたコンテンツアイテムのWebhookリクエストがトリガーされるためngrokおよびrails

完了すると、Kontentプロジェクトにデモコンテンツアイテムがあり、Railsデータベースに対応するArticleレコードがあります。

 $ rails c > Article.find_by(kentico_id: '18862937-3bc2-481e-9ca8-c177b813570a' ).title => 'The 9 worst songs about clothing websites'

また、Web UIを介したAlgoliaインデックス(ハードリフレッシュが必要な場合があります):

デバッグ

問題が発生した場合、Kontentは各Webhookのデバッグログを提供してエラーを検査し、Webhookリクエスト本文をコピーします。これにより、PostmanやcURLなどのHTTPクライアントを使用してローカルでテストできます。

最後に、Next.js UI

Algoliaは、ライブ検索エクスペリエンスを構築するためのカスタマイズ可能なビルド済みコンポーネントのセットを提供するInstantSearchコンポーネントライブラリを提供します。フロントエンドはNext.jsアプリケーションであるため、InstantSearch forReactを使用します。

AlgoliaのInstantSearchドキュメントは、InstantSearchコンポーネントを組み合わせて、製品で機能する検索インターフェイスに構成する方法を説明する優れた仕事をしているため、ここでは取り上げません。代わりに、 Algoliaのビルド済みのNext.jsサーバー側レンダリングデモプロジェクトをプルし、デモ用にカスタマイズします。

 $ yarn create next -app --example https://github.com/algolia/react-instantsearch/tree/master/examples/ next client

yarn create next-appが終了したら、2つのファイルをカスタマイズして、AlgoliaのデモプロジェクトをAlgoliaアプリケーションに接続する必要があります。

pages/index.jsのAlgolia検索クライアント構成を更新して、アプリケーションID、検索APIキーを環境構成からプルする必要があります。

 const searchClient = algoliasearch(  '' ,  ''
);

そして、同じファイルで、 DEFAULT_PROPS indexName

 const DEFAULT_PROPS = { searchClient, indexName: 'Article' , };

>注: algoliasearch-rails gemを使用すると、インデックス名はデフォルトでモデル名になりますが、疑わしい場合は、インデックス名がAlgoliaWebダッシュボードに表示されます。

components/app.js HitComponentマークアップを更新し、デモのAlgoliaプロジェクトの属性ではなく、インデックスの属性を使用する必要があります。

 const HitComponent = ({ hit }) => ( 
'hit' >
'hit-picture' >
'hit-content' >
'hit-author' > By  'author' hit={hit} />
Est. Read Time: {hit.estimated_reading_time_mins} mins
);

次に、同じファイルで、ファセット属性を使用してRefinementListメニューを更新します。

 
'menu' >

Authors

'author' />

Tags

'tags' />

Est. Read Time

'estimated_reading_time_human_readable' />

最後に、Next.jsとRailsのポート3000の両方のデフォルトは、その両方を同時に実行するために、あなたがする必要がありますNext.jsのためのポート上書きdev中でスクリプトをpackage.json

 'scripts' : {   'dev' : 'next -p 3001' , ...

これで、エンドツーエンドの検索ソリューションができました。 Next.jsアプリは、次のコマンドでローカルに起動できます。

 $ yarn dev

そして、http:// localhost:3001のブラウザで表示します。

成功!

Kontentで公開されたコンテンツの更新はすべて、Railsを介してAlgoliaに自動的に伝播され、ユーザーは検索語を入力してファセット属性から選択するときに、結果をリアルタイムでフィルタリングできます。

自分で試してみたい場合は、GitHubでデモプロジェクトを見つけることができます。

追加クレジット

隠された複雑さ-モジュラーコンテンツ

現在、Railsアプリは、ユーザーがKontent内の記事コンテンツアイテムへの変更を公開すると、Algolia検索インデックスを更新します。ただし、ユーザーがブログ記事をタグと作成者で検索およびフィルタリングできるようにします。これらのコンテンツアイテムが変更された場合はどうなりますか?現在、何もありません。

タグまたは作成者が変更されたときに検索インデックスを更新するには、Webhook処理コードを更新して、タグと作成者のWebhook通知を破棄する代わりに、そのタグまたは作成者にリンクされているすべての記事アイテムを更新する必要があります。

KontentとAlgoliaを使用したモジュラーコンテンツのインデックス作成について詳しくは、Kontentブログをご覧ください。

バッチ同期

正規のソースからのデータのコピーを維持しているときはいつでも、完全なデータセットを更新するためのメカニズムを用意することが賢明です。このメカニズムは、初期データセットをロードするために使用できますが、将来的には、正規データの構造の変更後にすべてのレコードを更新する必要がある場合にも使用できます。

Railsアプリでは、このためのrakeタスクを追加します。このタスクは、すべてのArticleコンテンツアイテムをフェッチしてループし、Algoliaを更新します。

ただし、Kontent DeliveryAPIは応答ごとに最大2000アイテムに制限されていることに注意する必要があります。これにはリンクされたアイテムが含まれるため、リンクされたアイテムが含まれるデータを要求する場合、2000のチャンクでページングしてもエラーを防ぐことはできません。

簡単な解決策は、特定のコンテンツに基づいてこの値を調整する必要がありますが、2000を超えるアイテム(たとえば10)で応答がないことを確信できるほど小さいページサイズでループすることです。

より洗練されたアプローチは、増分バックオフを実装することです。これにより、最大アイテムエラーが発生した場合、リクエストは成功するまで小さいページサイズで再試行されます。このアプローチは成功を保証し、パフォーマンスも向上するはずですが、実装はより複雑です。

私たちは、人を動力源とするテクノロジーエージェンシーであるKyanです。

Headless CMSの導入をお考えでしょうか?

クラウドとマルチデバイスに最適化されたKentico Kontentをお試しください