このブログでは、記事ページに構造化データを出力しています。
Drupalのモジュールに任せる方法もありますが、今回はテーマ側からJSON-LDを生成する構成にしました。
理由は単純です。
記事単体の情報だけではなく、「この記事を書いた人は誰なのか」「その人はどの会社に所属しているのか」まで、サイト全体でつなげたかったからです。
構造化データは、Webページの内容や属性を検索エンジンなどの機械が理解しやすい形式で表現するための仕組みです。Googleも、記事ページに Article、NewsArticle、BlogPosting の構造化データを追加することで、記事のタイトル、著者、日付などを理解しやすくできると説明しています。
この記事では、実際にDrupalでどのように実装したのか、そして「同じ人物を複数ページで別々に定義しない」という考え方について説明します。
単に「構造化データを入れました」という記事ではありません。
サイト全体を、機械から見てもつながった情報として扱えるようにする。
そのために何を考えたのかをまとめます。
構造化データで何を表現しているのか
今回、記事ページで出力している主な構造化データは2種類です。
| 種類 | 表現している情報 |
|---|---|
| BlogPosting | 記事そのものの情報。タイトル、著者、公開日、更新日、カテゴリなど |
| BreadcrumbList | ページがサイト内のどこに位置しているか |
どちらもJSON-LD形式で <head> に出力しています。
GoogleはJSON-LDを、構造化データの実装方法として推奨しています。HTMLの表示部分と機械向けのデータを分けて記述できるため、複雑な情報を扱いやすいのが特徴です。
今回のポイントは、これらを記事単体の情報として閉じず、サイト内にすでに存在する人物や組織の情報と関連付けることです。
なぜDrupalのモジュールではなくテーマ側で実装したのか
Drupalには、構造化データを出力するためのモジュールがあります。
標準的な構成であれば、モジュールを利用する方が早い場合もあります。
では、なぜ今回は使わなかったのか。
サイト独自の情報設計を、そのままコードにしたかったからです。
今回やりたかったのは、記事に著者名を書くだけではありません。
会社概要ページですでに定義している人物情報と、ブログ記事の著者情報を同じ人物として関連付けることでした。
もちろんモジュールを拡張して実現する方法もあります。
ただ、今回のように要件が明確で、出力する情報も自分で管理できるのであれば、コードから直接生成した方が構成を把握しやすくなります。
重要なのは「モジュールを使わないこと」ではありません。
標準機能で足りるのか、それともサイト固有の情報設計が必要なのか。
そこを判断して実装方法を決めています。
同じ人物を、サイト内で何度も定義しない
今回、一番考えたのがここです。
弊社の会社概要ページには、代表者の情報を構造化データとして定義しています。
氏名、肩書、経歴、専門分野、SNSなどです。
ここでブログ記事にも、同じ人物の情報をすべて書くことはできます。
しかし、それでは情報が重複します。
さらに、後から経歴を変更したときに、会社概要と記事側の情報が食い違う可能性があります。
そこで、人物そのものは一度定義し、記事側から参照する構成にしました。
この考え方が実際の検索結果にどう現れたかは、Google検索に会社のSNS投稿が表示された理由|WebサイトとSNSをつなぐエンティティ設計に書いています。
@idで人物を識別する
JSON-LDでは、@idを使って同じエンティティを識別できます。
たとえば会社概要側で、人物を次のように定義します。
{ "@type": "Person", "@id": "https://example.com/#person-yamada", "name": "山田 太郎", "jobTitle": "代表取締役", "sameAs": [ "https://x.com/..." ] }
そして記事側では、その人物を参照します。
"author": { "@type": "Person", "@id": "https://example.com/#person-yamada", "name": "山田 太郎", "url": "https://example.com/company.html" }
Schema.orgでも、authorにはPersonまたはOrganizationを指定できます。
また、Googleも著者をより正確に理解するため、著者のタイプやurl、sameAsなどを追加することを推奨しています。
つまり、記事に「山田太郎が書いた」と書くだけではなく、「この山田太郎は、サイト内で定義されているこの人物である」という関係を作るわけです。
名前も残している理由
「@idで参照するなら、名前まで書く必要はないのでは?」と思うかもしれません。
私は、あえて名前も残しています。
理由は、参照先の情報が何らかの理由で解決されなかった場合でも、記事そのものから著者名を読み取れる状態にしておきたいからです。
つまり、
- 参照が解決されれば、人物情報までつながる
- 参照されなくても、記事の著者名は分かる
という状態を狙っています。
これは構造化データだけの話ではありません。
同じ情報を何箇所にもコピーせず、正本を決めて参照する。
Webサイトの設計としても、かなり重要な考え方だと思っています。
DrupalではどのようにJSON-LDを出力したか
Drupalには、ページに追加の要素を添付するための仕組みがあります。
今回は hook_page_attachments_alter() を利用してJSON-LDを追加しています。
$attachments['#attached']['html_head'][] = [ [ '#type' => 'html_tag', '#tag' => 'script', '#attributes' => [ 'type' => 'application/ld+json' ], '#value' => Markup::create($json), ], 'my_article_jsonld', ];
テンプレートそのものを書き換える方法もありますが、今回は採用していません。
Drupal本体やテーマのテンプレートを大きく変更すると、将来的なアップデート時に差分を確認する箇所が増えるからです。
できるだけDrupalが用意している仕組みに乗せて、必要な部分だけを追加するようにしています。
記事ページだけに出力する
構造化データは、どのページにも同じように出せばよいわけではありません。
今回は記事ページだけを対象にしています。
if ($route_match->getRouteName() !== 'entity.node.canonical') { return ''; } if (!$node->isPublished()) { return ''; }
一覧ページやカテゴリページまで記事用の構造化データを出してしまうと、ページの種類と記述内容が一致しなくなります。
「そのページが何なのか」と、構造化データの内容を一致させる。
これは基本ですが、実装するときには意外と重要です。
公開日・更新日はISO 8601形式で出力する
記事の公開日や更新日は、機械が解釈しやすい形式で出力します。
$formatter->format( $node->getCreatedTime(), 'custom', 'c' )
Drupalの日付フォーマッターを使うことで、タイムゾーンを含んだISO 8601形式にできます。
こうした部分を自前で文字列生成するより、Drupal側の機能を使った方が事故を減らせます。
キャッシュにも注意する
Drupalで動的にページごとの情報を生成する場合、忘れてはいけないのがキャッシュです。
$attachments['#cache']['contexts'][] = 'url';
ページURLによって出力内容が変わることを、キャッシュシステムに明示します。
ここを考慮しないと、ある記事用に生成した情報が別の記事でも使われるなど、非常に分かりにくい不具合につながる可能性があります。
構造化データそのものより、CMSのキャッシュとどう付き合うかの方が実装上難しいケースもあります。
BlogPostingに何を入れているか
| 項目 | 内容 |
|---|---|
| headline | 記事タイトル |
| description | 記事の概要 |
| datePublished | 公開日時 |
| dateModified | 更新日時 |
| author | 著者。サイト内で定義した人物を参照 |
| publisher | 発行元。会社情報を参照 |
| image | アイキャッチ画像 |
| articleSection | 記事のカテゴリ |
| keywords | 記事のタグ |
| inLanguage | 日本語 |
GoogleのArticle構造化データでも、著者、タイトル、公開日、更新日、画像などの情報が推奨されています。
ただし、すべての項目を入れれば順位が上がる、という話ではありません。
実際にページに存在する情報を、機械にも理解できる形で表現する。
これが基本です。
descriptionは記事の概要から取得
descriptionには、記事投稿時に入力した概要を利用しています。
この概要は、meta descriptionや記事一覧の抜粋などにも利用しています。
同じ内容を何度も手入力するのではなく、1つの情報を複数の場所で利用する設計にしています。
こうしておけば、後から文章を変更したときにも修正漏れが起きにくくなります。
パンくずも構造化データにする
記事そのものだけでなく、サイト内での位置も構造化しています。
それが BreadcrumbList です。
たとえば、
HOME > ブログ > Drupal > 構造化データ
といった階層を機械にも伝えます。
ただし、階層がほとんどないページまで無理にパンくずとして扱う必要はありません。
今回は項目が1つ以下の場合は出力しないようにしています。
「ブログ」だけを表示しても、サイト内の位置関係を表す情報としては弱いからです。
実際、ブログトップについては当初パンくずを設置していませんでした。
後から「HOME > ブログ」という2階層にしたことで、ページの位置を明確にできるようになりました。
構造化データはAEOのためだけに入れるものではない
ここは誤解されやすいところです。
「構造化データを入れればAIに引用される」という話ではありません。
そのような保証はありません。
Googleが説明しているのも、構造化データによってページの内容を検索エンジンが理解しやすくなることです。
では、なぜAEOという観点でも考えているのか。
Webサイトに存在する情報を、機械が解釈しやすい形にしておくこと自体に意味があるからです。
ただし、AIへの引用を目的にするなら、構造化データだけでは不十分です。
本文そのものの品質、一次情報、見出し構造、著者情報、サイト内の関連性など、複数の要素を考える必要があります。
構造化データは、その中の一つです。
見出し構造の設計については、見出し構造とSEO・AEOの考え方|Drupalで目次を自動生成するでまとめています。
@idで人物・組織・記事をつなぐ
今回の構成で特に意識しているのが、@idによる関連付けです。
会社概要に存在する人物。
その人物が書いたブログ記事。
そのブログ記事を公開している会社。
これらを、それぞれ別々の情報として扱うのではなく、関係性を持たせます。
イメージとしては、
会社 → 人物 → 記事
という情報のつながりを作るわけです。
これは「AI対策」というより、Webサイトを構造化された情報として設計するという考え方に近いものです。
ただし、構造化データより先に実物が必要
ここは何度でも書いておきたいところです。
構造化データは、存在しない情報を作り出すものではありません。
実際には存在しない経歴を構造化データだけに書いても意味はありません。
内容の薄い記事に構造化データを追加したからといって、記事そのものが優れたコンテンツになるわけでもありません。
実物が先で、構造化データは後。
この順番を間違えないことが重要です。
実装した構造化データをどう確認するか
実装したら、必ず確認します。
Googleには、構造化データを検証するための「リッチリザルト テスト」があります。
URLを入力することで、Googleが認識できる構造化データを確認できます。
また、ブラウザでページのソースを表示し、
application/ld+json
を検索する方法でも確認できます。
JSON-LDは、1文字の記述ミスでも正しく解釈されないことがあります。
「コードを書いたから終わり」ではなく、実際に出力されたHTMLまで確認するところまでが実装です。
構造化データで重要なのは「何を書くか」より「どうつなぐか」
構造化データ自体は、それほど難しいものではありません。
決められた形式で、ページの情報を記述するだけです。
難しいのは、その先です。
「この記事を書いた人は誰なのか」
「その人はどの会社に所属しているのか」
「この会社は何をしているのか」
「この記事はサイト内のどこに位置しているのか」
こうした関係を整理していくと、サイトは単なるページの集合ではなくなります。
人物、組織、記事、カテゴリ、ページが関係性を持った情報の集合になります。
私が構造化データを実装するときに意識しているのは、まさにこの部分です。
この考え方を、Webサイトの外側まで広げたものがWebサイトとSNSの情報がバラバラになる問題|情報を一元管理してAPIで配信する仕組みです。
まとめ
今回、Drupalで実装した構造化データは、特別な技術ではありません。
BlogPosting、BreadcrumbListなどをJSON-LDで出力し、ページに存在する情報を機械が理解しやすい形に整理しただけです。
ただし、実装するときにはいくつか意識しました。
- 同じ人物を複数箇所で別々に定義しない
@idを使って既存の人物・組織情報と関連付ける- ページの種類と構造化データの内容を一致させる
- Drupalのキャッシュを考慮する
- 実際に存在する情報だけを構造化する
- 公開後にHTMLと検証ツールで出力を確認する
構造化データは、検索順位を直接上げる魔法のコードではありません。
まして、これだけでAIに引用される保証があるわけでもありません。
それでも、「このページは何なのか」「誰が書いたのか」「どの組織に属しているのか」を機械に明確に伝えるという意味では、Webサイトの情報設計に組み込む価値があります。
そして、ここまで考えると、構造化データはSEO対策だけの話ではなくなります。
サイトそのものを、どういう情報構造にするのか。
その設計の一部として考えるのが、私は一番しっくりきます。
自社サイトの構造化データや、会社・人物・記事の情報がきちんとつながっているか確認したい場合は、ご相談ください。
現在のサイト構成を確認したうえで、必要なものだけを整理します。
構造化データを増やすこと自体が目的ではありません。
必要な情報を、必要な場所に、正しくつなぐ。
それがWebサイトの設計だと考えています。