みなさん、サイトのリッチリザルト対応はしていますでしょうか?
対応することで、Googleの検索結果にリッチな表現で表示できます。これにより、他の検索結果ページよりも目を引くような表示でユーザーの目を引き、アクセスしやすいサイトになり、間接的なSEO向上が見込めるというメリットがあります。
動的コンテンツになると特に実装が難しそうというイメージもありますが、a-blog cms ならモジュールとカスタムフィールドを組み合わせていけば基本的にはHTMLのマークアップのみで実装可能です。
※リッチリザルトについての詳細は、Google 検索がサポートする構造化データ マークアップ | Google 検索セントラル | ドキュメント | Google for Developers でご確認いただけます。
公式テーマに入っている構造化データの紹介
a-blog cms の公式標準テーマ(siteテーマなど)には、あらかじめパンクズリスト構造化データが実装されています。この実装により、Googleの検索エンジンは対象ページの階層表示を検索結果に表示できます。
【例)themes/site/include/head/structured-data.html(a-blog cms Ver.3.1.3 の場合)】
<!-- BEGIN_MODULE Topicpath id="モジュールID" -->
<script type="application/ld+json">
{
"@context": "http://schema.org",
"@type": "BreadcrumbList",
"itemListElement":
[
<!-- BEGIN blog:loop --><!-- BEGIN glue -->,<!-- END glue -->{
"@type": "ListItem",
"position": {sNum},
"item":
{
"@id": "{url}",
"name": "{name}"
}
}<!-- END blog:loop --><!-- BEGIN category:loop --><!-- BEGIN glue -->,<!-- END glue -->
{
〜 割愛(blog:loopの中と同様の記述) 〜
}<!-- END category:loop --><!-- BEGIN entry --><!-- BEGIN glue -->,<!-- END glue -->
{
〜 割愛(blog:loopの中と同様の記述) 〜
}<!-- END entry -->
]
}
</script>
<!-- END_MODULE Topicpath -->
Topicpathモジュールにあらかじめ用意されている変数のみを使用しており、特別なカスタマイズは行っていません。
このように、パンクズリストに限らず、a-blog cms で標準実装されているモジュールを使用して、ほとんどの構造化データを実装できます。
記事の構造化データ実装方法紹介
比較的簡単に実装可能な記事の構造化データによるリッチリザルト対応方法を紹介します。
上記のように、お知らせやブログのエントリー詳細ページに追加できるのが記事の構造化データです。これを実装することで、Google検索や関連サービスの検索結果でサムネイルが表示されるようになります。
記事の構造化データの項目は大きく6つです。
全ての項目が「推奨」項目となります。
- author についての注意点
- タイプ(author.@type)とURL(author.url)について、推奨項目となっていますが、Googleのガイドラインにて「使用することを強くおすすめします。」と記載されているので、できれば指定することが望ましいようです。※Googleのガイドライン > 記事(Article)の構造化データ 作成者のマークアップのベストプラクティス
- image についての注意点
- 推奨項目となっていますが、Googleのガイドラインにて「すべてのページに画像を少なくとも 1 つ含める必要があります」と記載されているので、できれば画像を挿入するようにすることが望ましいようです。※Googleのガイドライン > 記事(Article)の構造化データタイプの定義
記事の構造化データ実装例
記事詳細は基本的に Entry_Bodyモジュール を使用していると思いますので、構造化データも Entry_Bodyモジュールの変数を使用して作成していきます。
【 _entry.html 】
@extends("/_layouts/継承元ファイル名.html")
@section(head-js)
@parent
<!-- GET_Rendered id="ld_json_article_entry" -->
@endsection
@section("main")
@include("/include/entry/body.html")
@endsection
【 include/entry/body.html 】
<!-- BEGIN_MODULE Entry_Body id="モジュールID" -->
<!-- BEGIN notFound -->〜割愛〜<!-- END notFound -->
<!-- BEGIN entry:loop -->
〜記事内容のページ出力割愛〜
<!-- BEGIN_SetRendered id="ld_json_article_entry" --><script type="application/ld+json">
{
"headline": "{title}",<!-- BEGIN_MODULE Ogp -->
"image": [
"%{HTTP_MEDIA_ARCHIVES_DIR}{image}[resizeImgFit(304,171)]",
"%{HTTP_MEDIA_ARCHIVES_DIR}{image}[resizeImgFit(260,195)]",
"%{HTTP_MEDIA_ARCHIVES_DIR}{image}[resizeImgFit(224,224)]"
],<!-- END_MODULE Ogp --><!-- BEGIN date:veil -->
"datePublished": "{date#Y}-{date#m}-{date#d}T{date#H}:{date#i}:{date#s}+09:00",<!-- END date:veil --><!-- BEGIN_IF [{date#Y}{date#m}{date#d}/neq/{udate#Y}{udate#m}{udate#d}] --><!-- BEGIN udate:veil -->
"dateModified": "{udate#Y}-{udate#m}-{udate#d}T{udate#H}:{udate#i}:{udate#s}+09:00",<!-- END udate:veil --><!-- END_IF --><!-- BEGIN userField:veil --><!-- BEGIN userField -->
"author": [{
"name": "{fieldUserName}",<!-- BEGIN fieldUserUrl -->
"url": "{fieldUserUrl}",<!-- END fieldUserUrl -->
"@type": "Person"
}],<!-- END userField --><!-- END userField:veil -->
"@type": "NewsArticle",
"@context": "https://schema.org"
}
</script><!-- END_SetRendered -->
<!-- END entry:loop -->
〜ページャー割愛〜
<!-- END_MODULE Entry_Body -->
イベント構造化データ実装方法紹介
2023年10月現在、イベントの開催地がオンラインで行われる機会が増えたことから、構造化データのオプションも増加し、実装がより複雑になりました。イベントに関しては、複数の専用カスタムフィールドを用意して実装する必要があるため、「難しめの構造化データ」の実装方法として紹介します。
上記のように、イベント構造化データを実装すると Googleサービスで強調表示してくれるため集客などにも繋げやすくなるのではないでしょうか。
イベント構造化データの項目は大きくわけると11項目です。
- startDate, endDate についての注意点
- 時間がわからない場合は時間指定しないでください。オンラインイベントの場合は開催時間の最後に日本の場合(JST)のUTC/GMTタイムオフセット「+09:00」が必須となります。
※イベント構造化データの詳細は、イベント(Event)の構造化データ | Google 検索セントラル | ドキュメント | Google for Developers でご確認いただけます。
イベント構造化データ実装例
1イベント1エントリーで作成した場合を想定し、実装例を出しました。使用するモジュールは Entry_Bodyモジュール です。
イベントに関しては、Entry_Bodyモジュールの標準変数だけでは情報不足なため、いくつかのカスタムフィールドを用意する必要があります。
エントリー編集ページへのカスタムフィールド追加
編集ページ側にエントリーのカスタムフィールドを追加します。
例)/admin/entry/field.html
イベント基本情報
<table class="acms-admin-table-admin-edit acms-admin-margin-bottom-mini">
<tr>
<th style="width: 130px;">イベント状況</th>
<td>
<select name="event_status">
<option value="EventScheduled" {event_status:selected#EventScheduled}>開催</option>
<option value="EventCancelled" {event_status:selected#EventCancelled}>中止</option>
<option value="EventMovedOnline" {event_status:selected#EventMovedOnline}>オフラインからオンラインに変更</option>
<option value="EventPostponed" {event_status:selected#EventPostponed}>延期だが日程未定</option>
</select>
<input type="hidden" name="field[]" value="event_status">
</td>
</tr>
<tr>
<th style="width: 130px;">開催日時<i class="acms-admin-icon-tooltip js-acms-tooltip" data-acms-tooltip="秒数は自動的に開始:00、終了:59として出力します"></i></th>
<td>
<table class="acms-admin-table-admin-edit">
<tr>
<th style="width: 110px;">設定</th>
<td>
<select name="event_time_status">
<option value="" {event_time_status:selected#}>---</option>
<option value="start_time_undecided" {event_time_status:selected#start_time_undecided}>イベント開始時間未定</option>
<option value="all_day" {event_time_status:selected#all_day}>終日イベント</option>
</select>
<input type="hidden" name="field[]" value="event_time_status" />
</td>
</tr>
<tr>
<th style="width: 110px;">開始日時</th>
<td>
<p class="acms-admin-margin-none">※エントリー日付で設定</p>
</td>
</tr>
<tr>
<th style="width: 110px;">終了日時<i class="acms-admin-icon-tooltip js-acms-tooltip" data-acms-tooltip="終日イベントの場合は入力不要"></i></th>
<td>
<input type="text" name="event_end_date" value="{event_end_date}" class="acms-admin-form-width-mini acms-admin-margin-right-mini js-datepicker2" placeholder="YYYY-MM-DD">
<input type="text" name="event_end_time" value="{event_end_time}" class="acms-admin-form-width-mini js-timepicker" placeholder="HH:MM:SS">
<input type="hidden" name="field[]" value="event_end_date">
<input type="hidden" name="field[]" value="event_end_time">
</td>
</tr>
</table>
</td>
</tr>
<tr>
<th style="width: 130px;">開催場所 <span class="acms-admin-label acms-admin-label-danger">必須</span></th>
<td>
<select name="event_attendance_mode">
<option value="" {event_attendance_mode:selected#}>未定</option>
<option value="OfflineEventAttendanceMode" {event_attendance_mode:selected#OfflineEventAttendanceMode}>オフライン</option>
<option value="OnlineEventAttendanceMode" {event_attendance_mode:selected#OnlineEventAttendanceMode}>オンライン</option>
<option value="MixedEventAttendanceMode" {event_attendance_mode:selected#MixedEventAttendanceMode}>オフラインとオンラインの両方</option>
</select>
<input type="hidden" name="field[]" value="event_attendance_mode">
</td>
</tr>
<tr>
<th style="width: 130px;">オフライン</th>
<td>
<table class="acms-admin-table-admin-edit">
<tr>
<th style="width: 110px;">会場名</th>
<td>
<input type="text" name="event_location_name" value="{event_location_name}" class="acms-admin-form-width-full">
<input type="hidden" name="field[]" value="event_location_name">
</td>
</tr>
<tr>
<th style="width: 110px;">会場住所<i class="acms-admin-icon-tooltip js-acms-tooltip" data-acms-tooltip="開催場所がオフラインの場合必須"></i></th>
<td>
<input type="text" name="event_location_address" value="{event_location_address}" class="acms-admin-form-width-full" placeholder="〇〇県〇〇市〜">
<input type="hidden" name="field[]" value="event_location_address">
</td>
</tr>
</table>
</td>
</tr>
<tr>
<th style="width: 130px;">オンライン</th>
<td>
<table class="acms-admin-table-admin-edit">
<tr>
<th style="width: 110px;">開催URL<i class="acms-admin-icon-tooltip js-acms-tooltip" data-acms-tooltip="開催場所がオンラインの場合必須"></i></th>
<td>
<input type="text" name="event_location_offline_url" value="{event_location_offline_url}" class="acms-admin-form-width-full" placeholder="https://~">
<input type="hidden" name="field[]" value="event_location_offline_url">
</td>
</tr>
</table>
</td>
</tr>
</table>
イベント詳細情報
<table class="acms-admin-table-admin-edit acms-admin-margin-bottom-mini">
<tr>
<th style="width: 130px;">イベント概要</th>
<td>
<textarea name="event_description" class="acms-admin-form-width-full" rows="5">{event_description}</textarea>
<input type="hidden" name="field[]" value="event_description">
</td>
</tr>
<tr>
<th style="width: 130px;">メイン画像</th>
<td class="js-media-field">
<div class="js-droparea" data-thumbnail="{main_image@thumbnail}" data-type="image" data-thumbnail-type="{main_image@type}" data-width="200px" data-height="200px"></div>
<p class="js-text acms-admin-text-danger" style="display:none">許可されていないファイルのため挿入できません。</p>
<div class="acms-admin-margin-top-mini">
<button type="button" class="js-insert acms-admin-btn" data-type="image">メディアを選択</button>
</div>
<input type="hidden" name="main_image" value="{main_image}" class="js-value">
<input type="hidden" name="field[]" value="main_image">
<input type="hidden" name="main_image:extension" value="media">
<p class="acms-admin-margin-none acms-admin-margin-top-mini">※推奨サイズ:幅720px以上</p>
</td>
</tr>
</table>
- performer, organizer, offers(出演者情報、主催者情報、チケット情報)について
- 推奨プロパティなので上記実装例からは除外しました。実際に実装する際は、イベントページに合わせて項目追加してみてください。
- previousStartDate(開催日変更をした場合の、変更前の開始日)について
- イベントの日程が変更された場合、変更前に予定されていたイベント開始日を設定できる previousStartDate という項目もありますが、実装するとより複雑化するため推奨プロパティということもあり、上記実装例からは除外しました。それに伴い eventStatus からは EventRescheduled(イベント日時を変更した)の選択肢を無くしています。
エントリー詳細ページ
【 _entry.html 】
@extends("/_layouts/継承元ファイル名.html")
@section(head-js)
@parent
<!-- GET_Rendered id="ld_json_event_entry" -->
@endsection
@section("main")
@include("/include/entry/body.html")
@endsection
【 include/entry/body.html 】
<!-- BEGIN_MODULE Entry_Body id="モジュールID" -->
<!-- BEGIN notFound -->〜割愛〜<!-- END notFound -->
<!-- BEGIN entry:loop -->
〜イベント内容のページ出力割愛〜
<!-- BEGIN_SetRendered id="ld_json_event_entry" --><!-- BEGIN_IF [{event_attendance_mode}/nem] --><script type="application/ld+json">
{
"name": "{title}",<!-- BEGIN_IF [{event_status}/nem] -->
"eventStatus": "https://schema.org/{event_status}",<!-- END_IF -->
"eventAttendanceMode": "https://schema.org/{event_attendance_mode}",<!-- BEGIN_IF [{event_time_status}/eq/all_day] -->
"startDate": "{date#Y}-{date#m}-{date#d}",
"endDate": "{date#Y}-{date#m}-{date#d}",<!-- ELSE_IF [{event_time_status}/eq/start_time_undecided] -->
"startDate": "{date#Y}-{date#m}-{date#d}",<!-- BEGIN event_end_date:veil -->
"endDate": "{event_end_date}",<!-- END event_end_date:veil --><!-- ELSE -->
"startDate": "{date#Y}-{date#m}-{date#d}T{date#H}:{date#i}:00+09:00",<!-- BEGIN_IF [{event_end_date}/nem] -->
"endDate": "{event_end_date}<!-- BEGIN event_end_time:veil -->T{event_end_time}[trim(5, '')]:59+09:00<!-- END event_end_time:veil -->",<!-- END_IF --><!-- END_IF --><!-- BEGIN event_description:veil -->
"description": "{event_description}[delnl]",<!-- END event_description:veil --><!-- BEGIN_IF [{main_image@path}/nem] -->
"image": [
"%{HTTP_MEDIA_ARCHIVES_DIR}{main_image@path}[resizeImgFit(720,720)]",
"%{HTTP_MEDIA_ARCHIVES_DIR}{main_image@path}[resizeImgFit(720,540)]",
"%{HTTP_MEDIA_ARCHIVES_DIR}{main_image@path}[resizeImgFit(720,405)]"
],<!-- END_IF --><!-- BEGIN_IF [{event_attendance_mode}/eq/OfflineEventAttendanceMode] -->
"location": {
"@type": "Place",<!-- BEGIN event_location_name:veil -->
"name": "{event_location_name}",<!-- END event_location_name:veil -->
"address": {
"@type": "PostalAddress",
"streetAddress": "{event_location_address}",
"addressCountry": "日本"
}
},<!-- ELSE_IF [{event_attendance_mode}/eq/OnlineEventAttendanceMode] -->
"location": {
"@type": "VirtualLocation",
"url": "{event_location_offline_url}"
},<!-- ELSE -->
"location": [{
"@type": "VirtualLocation",
"url": "{event_location_offline_url}"
},
{
"@type": "Place",<!-- BEGIN event_location_name:veil -->
"name": "{event_location_name}",<!-- END event_location_name:veil -->
"address": {
"@type": "PostalAddress",
"streetAddress": "{event_location_address}",
"addressCountry": "日本"
}
}],<!-- END_IF -->
"@type": "Event",
"@context": "https://schema.org"
}
<!-- END_IF --></script><!-- END_SetRendered -->
<!-- END entry:loop -->
〜ページャー割愛〜
<!-- END_MODULE Entry_Body -->
最後に
a-blog cms での構造化データ実装は、モジュールとカスタムフィールドの組み合わせで基本的にはどの構造化データの実装も可能です。まだチャレンジしたことがない方は以下にピックアップした実装時のコツを押さえつつ実装してみてください!
実装時のコツ