boring stuff

Google構造化データとOGPとTwitterカードを入れ子に組み合わせる

StructuredDataOGPGridsome

emmdee@colus.img

HTMLのheadにOGPなどのSNS向けのmetaデータを書く時、titleやdescriptionなど重複する要素が多く冗長だと感じる。今回サイトにGoogleの構造化データを追加することになり、さらに冗長化が進みそうであった。このような状態では、制作時には何度も同じ文章をコピペすることになり、改修時には修正漏れが発生しやすいと思われる。そこで、どこまで簡潔にこれらの情報を記述できるかを検証してみた。

記述するデータ

今回は冗長性を排除しつつも、実用性を重視しSEOに有用な情報を多く詰めこむことを目指し、FacebookのOGPタグとTwitterのTwitterカード、Googleの構造化データを記述する。また対象としては一般的な日記的ブログでの使用を想定している。

Facebook OGP

OGPの一般的な記述は下記のようなもので、形式としてはRDFa LiteをFacebookが独自拡張したもののようだ。

<head prefix="og: http://ogp.me/ns# fb: http://ogp.me/ns/fb# article: http://ogp.me/ns/article#">
  <title>The Great Minds Think Alike</title>
  <meta name="description" content="How much does culture influence creative thinking?">
  <link rel="canonical" href="https://example.com/2015/great-minds/">
  <meta property="og:url" content="https://example.com/2015/great-minds/">
  <meta property="og:type" content="article">
  <meta property="og:title" content="The Great Minds Think Alike">
  <meta property="og:description" content="How much does culture influence creative thinking?">
  <meta property="article:published_time" content="2015-02-05T08:00:00Z">
  <meta property="og:site_name" content="The Example Times">
  <meta property="og:image" content="https://example.com/img/2015/ogp.jpg">
  <meta property="og:image:width" content="1200">
  <meta property="og:image:height" content="600">
  <meta property="fb:app_id" content="000000000000000">
  ...

こうみるとこの時点で既に冗長なところがあり、titleタグ、meta description、link canonical の内容が、og:title、og:description、og:urlと重複している。検索結果とSNSでのシェアの際に別の情報を表示したいことはあまりないと思われるので、これらの記述をまとめられないかと考え、下記のValidatorを使い幾つかの記述のテストを行った。

試してみたのは例えば下記のようなHTMLだ。

<head>
  <title property="og:title">The Great Minds Think Alike</title>
  <meta name="description" property="og:description" content="How much does culture influence creative thinking?">
  <link rel="canonical" property="og:url" href="https://www.nytimes.com/2015/great-minds/">
  <meta property="og:article:published_time" content="2015-02-05T08:00:00Z">
  <meta name="fb:app_id" content="0000000000">
  ...

まず、headのprefixだが、og: http://ogp.me/ns#についてはusual URI schemeとして仕様に組み込み済みのため省略可能なようだ。

また、article: http://ogp.me/ns/article#についても、metaタグで使う際にarticle:...ではなくproperty="og:article:published_time"のようにog:article:...と記載する形で使用することで警告なしで省略可能であった。
fbのprefixだが、Facebookのシェアデバッガーでは省略してもエラーは出ない。RDFa 1.1 Validatorでは「valid」との判定ではあるが、「Unusual URI scheme used in <fb:app_id>」と「Warnings」が出た。微妙なところだが、書いた方がベターではあるが、なくてもvalidではあるというくらいのニュアンスだと考えておく。
また、fb:app_idをname属性に記載することでprefixを心置きなく削除できないかと試してみたのだが、Facebookのシェアデバッガーはname属性にappidがあるけどこの書き方は認識しないと、明確な警告が表示された上で、idは認識されなかった。
次に、titleタグ、meta description、link canonicalの統合だが、結果から言うと残念ながらこれらは統合できなかった。Facebookはheadのmetaタグのみを認識する仕様なようで、titleタグやlinkタグにpropertyを書いても全く認識しない。また、meta descriptionについてもname="description"があるとpropertyは読み込まないようで、認識しなかった。

Twitterカード

TwitterカードとOGPの重複する部分については、TwitterがOGPをフォールバックとして読み込むという柔軟な仕様なため、OGPだけを書いておけけば何も問題がない。Twitter独自の部分はRDFaなどではなく、単なるmetaタグの独自拡張で、HTML的にはどうかと思うところもあるが、独自のprefixを書く必要もなく使いやすいものになっている。追加する独自部分は下記のようなものである。

<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:site" content="@the_example_times">
<meta name="twitter:creator" content="@example_editor">

Google構造化データ

あとは構造化データをどのように他のデータとまとめるかである。
今回は一般的なブログでの使用が想定なので、OGPのog:typeにならって、トップページにWebSite、下層ページにArticleを記述することにした。
記述の確認にはGoogleの構造化データ テストツールを使用した。

記述形式

構造化データは記載できる形式として、JSON-LD、microdata、RDFaの3種類の形式が認められている。

JSON-LD

Googleの推奨形式で、JSONをscriptタグで埋め込む形式である。既存のタグに埋め込みで書く形式ではないため、今回の目的には合わず、テストでは除外した。

microdataとRDFa

どちらも既存のタグに埋め込みで書く形式であり、埋め込むタグの親子関係で構造化データ同士の帰属関係を表現するのが基本的な使い方である。body内のタグに記述する場合はこの使い方で問題ないが、今回のようにheadのmetaタグに書く場合にはmetaタグが親子関係を持てないためちょっとした工夫が必要である。

RDFa

RDFaで帰属関係を表現するものとして、OGPのog:image:width形式の書き方がある。この書き方でog:imageのwidthについての記述であることは明確である。しかし例えばog:imageが複数あった場合に、この書き方ではog:image:widthがどちらのimageのwidthなのかを明確に記述できないという問題がある。この問題をOGPでは記載の順序を基準にすることで解決している。例えば下記の場合のog:image:widthはogp1.jpgではなくogp2.jpgのwidthを表す。

<meta property="og:image" content="https://example.com/img/ogp1.jpg">
<meta property="og:image" content="https://example.com/img/ogp2.jpg">
<meta property="og:image:width" content="1200">

明確さに欠ける気もするが使いやすい形式ではある。しかしこの形式は、Googleでは認識されない。 テストツールでいろいろ試しながら気づいたのだが、構造化データのRDFaはRDFa LiteではなくRDFa Coreのようである。
RDFa Coreについては下記サイトがとても参考になった。

RDFa Coreで帰属関係を表現するには、resourceとaboutを下記のように使うのが正解のようだ。

<meta typeof="schema:Website" resource="website">
<meta property="schema:Author schema:Publisher" about="website" resource="author">
<meta typeof="schema:Organization" about="author">
<meta property="schema:name" content="The Example Times" about="author">

この例では「Website > Author, Publisher > Organization, name」のような帰属関係が表現されている。また、この場合のようにAuthorとPublisherが同一の場合は、同じpropertyに併記することも可能だ。
OGPはRDFa Liteを独自拡張したものなので、別々に併記するのであれば組み合わせとして良さそうではあるが、入れ子にして組み合わせてみると、テストツールではエラーになるところも出てきた。例えば上記にog:site_nameを組み入れて下記のようにしてみると、タイプOrganizationのオブジェクトではプロパティhttp://ogp.me/ns#site_nameはGoogleで認識されません。とエラーが表示される。

<meta typeof="schema:Website" resource="website">
<meta property="schema:Author schema:Publisher" about="website" resource="author">
<meta typeof="schema:Organization" about="author">
<meta property="schema:name og:site_name" content="The Example Times" about="author">

この場合でも、併記してあるschema:nameの方は正しく認識されているので実質的に問題はないのかもしれないが、エラー表示はできれば避けたいため、今回はmicrodataで記述することにした。

microdata

microdataで帰属関係を表現するにはitemrefとidを使用する。RDFaで例示したものと同じ内容をmicrodataで記述してみた。microdataについてはMDNのマイクロデータ | MDNを参照した。

<meta itemscope itemtype="http://schema.org/WebSite" itemref="mdAuthor">
<meta id="mdAuthor" itemprop="author publisher" itemscope itemtype="http://schema.org/Organization" itemref="mdName">
<meta id="mdName" itemprop="name" property="og:site_name" content="The Example Times">

ここで使うidはhtmlのidでもあるため同じ名前を使用できないので、帰属する情報が多い場合、itemrefにid名を列挙する形となり、RDFaに比べ多少記述が増える傾向がある。実際今回試してみたArticleの記述では1割程度microdataの方が記述量が多かった。RDFaと同様、authorとpublisherが同一の場合は、同じitempropに併記することが可能である。 OGPやカードとの組み合わせではGoogle、Facebook、Twitterそれぞれのvalidatorでエラーが出ず問題ないものになった。

完成した記述

記事ページの記述例

<!DOCTYPE html>
<html lang="en">
<head>
  <title>The Great Minds Think Alike</title>
  <meta name="description" content="How much does culture influence creative thinking?">
  <meta itemscope itemtype="http://schema.org/Article" itemref="mdHeadline mdDescription mdImage mdDatePublished mdDateModified mdMainEntityOfPage mdAuthor" property="og:type" content="article">
  <meta id="mdHeadline" property="og:title" itemprop="headline" content="The Great Minds Think Alike">
  <meta id="mdDescription" property="og:description" itemprop="description" content="How much does culture influence creative thinking?">
  <meta id="mdImage" property="og:image" itemprop="image" content="https://example.com/img/ogp.jpg">
  <meta id="mdDatePublished" property="article:published_time" itemprop="datePublished" content="2015-02-05T08:00:00Z">
  <meta id="mdDateModified" property="article:modified_time" itemprop="dateModified" content="2015-02-06T08:00:00Z">
  <meta id="mdMainEntityOfPage" property="og:url" itemprop="mainEntityOfPage" content="https://example.com/2015/great-minds/">
  <meta id="mdAuthor" itemprop="author publisher" itemscope itemtype="http://schema.org/Organization" itemref="mdAuthorName mdLogo">
  <meta id="mdAuthorName" itemprop="name" property="og:site_name" content="The Example Times">
  <meta id="mdLogo" itemprop="logo" itemscope itemtype="http://schema.org/ImageObject" itemref="mdLogoUrl">
  <meta id="mdLogoUrl" property="og:image:alt" itemprop="url" content="https://example.com/img/logo.png">
  <meta property="fb:app_id" content="000000000000000">
  <meta name="twitter:card" content="summary_large_image">
  <meta name="twitter:site" content="@the_example_times">
  <meta name="twitter:creator" content="@example_editor">
  <link rel="canonical" href="https://example.com/2015/great-minds/">
</head>
...

トップページの記述例

<!DOCTYPE html>
<html lang="en">
<head>
<title>The Example Times</title>
  <meta name="description" content="Top page of The Example Times">
  <meta itemscope itemtype="http://schema.org/WebSite" itemref="mdUrl mdTitle mdDescription mdImage mdDatePublished mdDateModified mdAuthor" property="og:type" content="website">
  <meta id="mdUrl" property="og:url" itemprop="url" content="https://example.com/">
  <meta id="mdTitle" property="og:title" itemprop="name" content="The Example Times">
  <meta id="mdDescription" property="og:description" itemprop="description" content="Top page of The Example Times">
  <meta id="mdImage" property="og:image" itemprop="image" content="https://example.com/img/ogp.jpg">
  <meta id="mdDatePublished" property="article:published_time" itemprop="datePublished" content="2015-02-05T08:00:00Z">
  <meta id="mdDateModified" property="article:modified_time" itemprop="dateModified" content="2015-02-06T08:00:00Z">
  <meta id="mdAuthor" itemprop="author publisher" itemscope itemtype="http://schema.org/Organization" itemref="mdAuthorName mdLogo">
  <meta id="mdAuthorName" itemprop="name" property="og:site_name" content="The Example Times">
  <meta id="mdLogo" itemprop="logo" itemscope itemtype="http://schema.org/ImageObject" itemref="mdLogoUrl">
  <meta id="mdLogoUrl" property="og:image:alt" itemprop="url" content="https://example.com/img/logo.png">
  <meta property="fb:app_id" content="000000000000000">
  <meta name="twitter:card" content="summary_large_image">
  <meta name="twitter:site" content="@the_example_times">
  <meta name="twitter:creator" content="@example_editor">
  <link rel="canonical" href="https://example.com/">
</head>
...

なるべく簡潔にと考えていたのだが、残念ながらあまり簡潔にはなっていない。また、HTML5のvalidatorは幾つかエラーが表示される。HTML5の仕様としてはitempropとpropertyが併記されていることは問題なようであるが、ここで対象としているサイトではその併記を同時に読もうとはしないため、実用的には問題ないようだ。

それぞれの仕様について

Facebookのheadのmetaタグ以外読まないという仕様は理解できない。そもそもRDFaは既存のタグにメタデータを付与するための形式であり、専用のタグの使用を強制するのは本末転倒だとも思う。OGPはそのページ自体のメタデータを表現するため、記述する場所としてheadがふさわしいのは理解できるが、少なくともlinkタグくらいは認識してほしい。
Twitterについては、比較的ゆるい仕様であるが、意外と使い勝手が良いと感じた。先行したOGPに対して現実的な解決策を示した姿勢は評価できる。
Googleの構造化データだが、今回作業してみて、headに書くならGoogleの推奨どおりJSON-LDが使いやすそうだなというのが率直な感想だ。RDFaやmicrodataはパンくずリストのようにbodyの中で既存のタグにデータを追加する形で使う場合は使いやすい面もあるが、階層がつくれないmetaタグに書くのは使いにくい上に、書かれたデータも人間には読みにくい。OGPに比べ複雑なデータを要求するため仕方ない部分もあるのかもしれないが、既に広く使われているOGPを拡張するような方向では考えられなかったのか疑問がある。また、RDFaについてはGoogleが認識できないデータに対してエラーを出すのは仕様としてどうかと思う。RDFaはさまざまなschemaのメタデータを埋め込めるのが利点の一つであるのに、このような仕様ではそれが台無しだ。実際ここでは改めて検証していないが、パンくずリストでそのページ自身へのリンクを記述すると、そのページのOGPを読み込もうとしてエラーが頻発する。警告ならまだ理解できるがエラーだ。こんな仕様ならむしろサポートしないでいてくれた方が良いくらいだと思う。

公式ガイドとテストツール

実装例

上記の内容をVueの静的サイトジェネレーターであるGridsomeのプラグインとして実装した。
GridsomeのOGPプラグインである、brandonpittman/gridsome-plugin-ogpをフォークして制作のベースとした。機能の作り方は元のプラグインをほぼ流用させていただいた。
一般的な個人サイトであればこのまま使用しても問題ない場合もあるかと思われるが、残念ながらカスタマイズ性は全く考慮できていないので表示項目を変更したい場合は、フォークして改修していただくしかない。