Hugo で schema.org (JSON-LD) を自動生成するスクリプト

Google の検索結果をリッチにするためのマークアップについて、FAQpage 形式のリッチリザルトを JavaScript で動的に生成する方法として以下の記事を執筆しました。

この記事では、以下の記事の技術情報を前提に Hugo で実装する場合の手法について補足説明します。

解決策のコード

// HTML ドキュメントの読み込みを待つトリガー
window.addEventListener("DOMContentLoaded", () => {
  // JSON-LD 形式の Schema を生成する処理の開始
  createFaqSchema();
});

function createFaqSchema() {
  // 質問情報のあるセクションを抽出
  let questions = document.querySelectorAll(".question-section");
  if (!questions.length) {
    // 質問に関するコンテンツがなかった場合は処理を終了
    return false;
  }

  // 質問と回答のセットを格納するための変数。つまりセットはいくつあってもよい。
  const entities = [];
  questions.forEach((section) => {
    // 質問そのものの抽出
    let question = section.querySelector(".question").innerText;
    // 回答そのものの抽出
    let answer = section.querySelector(".answer").innerHTML.trim();
    entities.push(createQuestionEntity(question, answer));
  });

  const schemaBody = {
    "@context": "https://schema.org",
    "@type": "FAQPage",
    mainEntity: entities,
  };

  createSchemaElement(JSON.stringify(schemaBody));
}

// <head> タグ内に <script> タグを生成する
function createSchemaElement(structuredDataText) {
  const script = document.createElement("script");
  script.setAttribute("type", "application/ld+json");
  script.textContent = structuredDataText;
  document.head.appendChild(script);
}

function createQuestionEntity(question, answer) {
  if (!question) {
    return false;
  }
  if (!answer) {
    return false;
  }

  return {
    "@type": "Question",
    name: `${question}`,
    acceptedAnswer: {
      "@type": "Answer",
      text: `${answer}`,
    },
  };
}

layouts/shortcodes/faq.html として保存していること。

{{- $title := .Get "title" -}}

<div class="question-section">
  <span class="question">{{ $title }}</span>
  <div class="answer">
    {{ .Inner | markdownify }}
  </div>
</div>

呼び出し方

{{<faq title="インターネットとはなんですか?">}}
インターネットは、世界中のコンピュータなどの情報機器を接続するネットワークです。
{{</faq>}}

<!-- 注!カッコは全角になっているので利用の際は半角にすること -->

Hugo におけるFAQ形式の構造化表現の悩み

Hugo は静的サイトジェネレーターですので、記事にあたるマークダウンファイルの中身はただのテキストにすぎません。そのため、記事の本文からFAQ部分を抽出して慈善にJSON-LDを生成することは難しそうでした。

Data template を使えば他にもやりようはありそうですが、記事そのものとFAQのファイルが分離されてしまうのは管理上面倒くさいです。

他にも、Front Matter に書いてしまう方法も議論されています(下記リンク)。これなら同一ファイルでコンテンツとFAQを集約できるものの、Front Matter にコンテンツのテキスト(場合によっては長文)を記載するのも、いまいち直感的ではありませんでした。

確かに、この方法でも動かすことはできました。

このコンテンツ管理で特にに違和感がない場合は、確実に静的ページとして慈善ビルドが行えるこの方法の方がよいかと思います。ただしその場合、Front Matter に記載したFAQコンテンツを記事本文に表示するための partial テンプレートが別途必要になるかと思います。

今回の選択においては、「マークダウンファイルに記事を書いている形そのままでHTMLを生成したい」という要件に主軸をおいたため、JavaScript での動的生成の方式に落ち着きました。