前回の「スマホ用のヘッダーナビを作る」に引き続き、今回は「SEO対策をする」を実践してみようと思う。

SEO対策とは検索エンジン最適化のための対策だが、近年ではSNSでの最適化も必要になっているようだ。また、そもそもブラウザ表示にエラーが出ないようにHTMLやBloggerの設定を適切に読み込ませる設定も必要になる。

というわけで、今回はSEO対策に必要な「メタタグの基本設定」「OGP設定」「schema.orgの設定」についてまとめることにする。なお、本記事でテンプレートは完成するので、これでバージョン1.0の自作記事は終了となる。


概要


ここでは設定する予定のコードについて解かる範囲で説明しておこうと思う。

HTMLの基本設定について


・<meta charset='UTF-8' />:文字コードの設定(文字化けを防ぐ意味合いがあり、UTF-8が推奨されている)
 → XMLの宣言に設定されていれば省略可能っぽい
・<meta content='width=device-width, initial-scale=1' name='viewport' />:モバイル表示設定
 → これが無いと、モバイルで見た時にPCと同じスケールで表示されてしまう(初期値は980px)
 → これが無いと、モバイル表示のwidthが正しく計算されないため、レスポンシブデザインが崩れる

Bloggerの基本設定について


・<b:include data='blog' name='all-head-content' />:Bloggerで必要な設定を読み込む
 → headタグに記述するべきメタタグなど、SEOに必要になる一連のコードが読み込まれるっぽい(以下全文)
  ⇒ 参考:https://bloggerbook.blakbin.com/2018/11/blogger-all-head-content.html(英語サイト)
  ⇒ 参考:Bloggerブログのテンプレートのhead内のカスタマイズのために独自タグのall-head-contentを分解する
  Bloggerのバージョン2以降は自動で読み込まれるらしい
・<meta name='description' expr:content='data:blog.metaDescription' />:ブログの説明・検索向け説明
 → <b:include data='blog' name='all-head-content' />で読み込まれるので省略可能っぽい
・<b:include data='blog' name='google-analytics'/>:Googleアナリティクスを使うためのタグ
 → </head>の真上に書かれているいることが多い

OGP設定について


OGPとは、Open Graph Protco(オープン・グラフ・プロトコル)の略で、サイトをSNSでシェアした時にリンクに表示される画像・タイトル・説明文を正しく表示させるために設定するもの。例えばTwitterカードなんかに表示させる情報をどこから取得するのかといった設定に用いられるらしい。これを入れるとSNSでのクリック率が高まり、アクセスアップにつながるというメリットがある。また、これが設定されていないとSNS表示時にエラーがでることもある。

Twitterは設定にIDを含める必要はない(含めることもできる)。しかし、FacebookはIDが必要になるので記述は個々によって異なる。なので、テンプレートにデフォルトで組み込むことはできない。なお、Twitter・Facebookともに投稿しなくてもOGP設定を確かめることのできるテストページが用意されている。このテストページやOGP設定の記述詳細は下記のサイトを参考にするとわかりやすい。


schema.orgについて


HTMLにタグ付けして構造化データにし、検索エンジンにサイトの構造を認識させやすくすることをschema(スキーマ)というらしい。schema.orgは、このschemaの作成、維持、促進を使命とする団体で、Googleもschema.orgを使ってマークアップすることを推奨しているのだとか(よくわからないけど)。

要するに、ソースにschemaでタグ付けしてその部分が何を指しているのかを検索エンジンに分かりやすくしておくと、検索結果に詳細なデータが表示されるようになり、検索ユーザーにも分かりやすくなるというもの。で、これが将来検索順位に影響するのではないかといわれているっぽい。

で、Bloggerのテンプレートにおいては、パンくずリストにつけるべきみたいな記事が散見される。他所様のSEO対策済みのテンプレートを覗いてみるとパンくずリストはもちろん、ブログ内のパーツを構成するコードにちらほらつけられているのがわかる。つまり、SEO対策には必要な記述っぽい。

このschemaの記述方法をざっくり説明すると、以下のような感じになる。

【使用するプロパティ(抜粋)】

・itemscope:特定の意味を持つデータであること、またその範囲を指定するタグ(範囲指定のタグ)
・itemprop:そのデータ(アイテム)の意味(プロパティ名)を指定するタグ(itemprop="name"みたいに)
・itemtype:itemscopeに対し、適切なカテゴリを示すschemaのURLを指定する(itemscopeの後に記述)
 → <section itemtype="http://schema.org/Blog"> のように書くと、sectionがブログであることを示せる
⇒ URLは「schema.org」を参照して正しいURLを選択する

で、このタグを使ってブログ内の各パーツをマークアップしていくという流れになるのだが、これが付きすぎていると初心者がテンプレートのカスタマイズをする時に躊躇する原因になりかねない。

このタグは現状SEOに深く関わってくるというわけではないようなので、ひとまず必須といわれているパンくずリストにつけて様子を見ることにする。なお、構造化のテストは下記の「構造化データテストツール(Google)」で確かめることができる。


また、schemaのタグやitemtypeのURLについては下記のURLが参考になる。


作業工程


HTMLの基本設定を記述する


<!-- 文字化けを防ぐ -->
<meta charset='UTF-8' />

<!-- モバイル表示の設定 -->
<meta content='width=device-width, initial-scale=1' name='viewport' />

ここはお約束部分。charsetはxmlの宣言でも設定しているので省略可能かもしれないが一応記述。

Bloggerの設定を記述する


<!-- Bloggerの設定読み込み -->
<b:include data='blog' name='all-head-content' />

ここもお約束部分。これが無いと文字化けなど、色々な弊害が生じる可能性がある。

OGP設定を記述する


<!-- OGP設定 -->
<meta expr:content='data:blog.title' property='og:site_name'/>

<!-- Twitterカード -->
<meta name='twitter:card' content='summary_large_image'/>
<meta name='twitter:domain' expr:content='data:blog.homepageUrl'/>

<b:if cond='data:blog.pageType == &quot;item&quot;'>
  <meta name='twitter:url' expr:content='data:blog.canonicalUrl'/>
  <meta name='twitter:title' expr:content='data:blog.pageName'/>
  <meta name='twitter:image:src' expr:content='data:blog.postImageUrl'/>
<b:if cond='data:blog.pageType == &quot;index&quot;'>
  <meta name='twitter:title' expr:content='data:blog.title'/>
  <meta content='トップページ用画像URL'/> <!-- トップページがリンクされた時に表示される画像 -->
<b:else/>
  <meta name='twitter:url' expr:content='data:blog.homepageUrl'/>
  <meta name='twitter:title' expr:content='data:blog.pageTitle'/>
  <meta name='twitter:image:src' content='Image URL'/>
</b:if>
</b:if>

<b:if cond='data:blog.metaDescription'>
  <meta name='twitter:description' expr:content='data:blog.metaDescription'/>
</b:if>
※OGP設定のコードが間違っていたので修正しました(20/11/10)

この記述だと投稿ページの場合の要約文は、検索向け説明が存在する場合はそれ、存在しない場合はブログ自体の検索向け説明が適用される。また、Twitterカードは4種類ある内の「summary_large_image(サムネイルが大きいもの)」を選択している。Twitterカードの <meta content='トップページ用画像URL'/> の部分は、インデックスページ(個別記事、固定ページ以外)のURLがシェアされた時に表示される画像URLを指定する。この部分の条件分岐を破棄すると、画像がない場合のアイコンが表示される。投稿記事もどうようにサムネイルが無い場合は同じアイコンが表示される。

※この記述で上手く行かない場合は、下記の「Twitterカードの画像が想定外の場合」を参照

パンくずリストをschema.orgでマークアップをする


<!-- パンくずリスト -->
<b:includable id='breadcrumb' var='posts'>
  <!-- 固定ページのパンくずリスト -->
  <b:if cond='data:blog.pageType == &quot;static_page&quot;'>
    <div class='breadcrumbs' itemscope='itemscope' itemtype='http://schema.org/BreadcrumbList'><span
        itemprop='itemListElement' itemscope='itemscope' itemtype='http://schema.org/ListItem'><a
          expr:href='data:blog.homepageUrl' itemprop='item'><span itemprop='name'>ホーム</span></a>
        <meta content='1' itemprop='position' /></span> <i class="fa fa-chevron-right pause-icon" /> <span
        itemprop='name'>
        <data:blog.pageName /></span></div>
    <b:else />
    <b:if cond='data:blog.pageType == &quot;item&quot;'>
      <!-- 個別記事ページのパンくずリスト -->
      <b:loop values='data:posts' var='post'>
        <b:if cond='data:post.labels'>
          <div class='breadcrumbs' itemscope='itemscope' itemtype='http://schema.org/BreadcrumbList'>
            <span itemprop='itemListElement' itemscope='itemscope' itemtype='http://schema.org/ListItem'><a
                expr:href='data:blog.homepageUrl' itemprop='item'><span itemprop='name'>ホーム</span></a>
              <meta content='1' itemprop='position' /></span>
            <b:loop values='data:post.labels' var='label'>
              <i class="fa fa-chevron-right pause-icon" /> <span itemprop='itemListElement' itemscope='itemscope'
                itemtype='http://schema.org/ListItem'><a expr:href='data:label.url + &quot;?&amp;max-results=10&quot;' itemprop='item'>
                  <span itemprop='name'>
                    <data:label.name /></span></a>
                <meta content='2' itemprop='position' /></span>
            </b:loop>
            <i class="fa fa-chevron-right pause-icon" /> <span itemprop='name'>
              <data:post.title /></span>
          </div>
          <b:else />
          <!-- タグがない場合のパンくずリスト -->
          <div class='breadcrumbs' itemscope='itemscope' itemtype='http://schema.org/BreadcrumbList'><span
              itemprop='itemListElement' itemscope='itemscope' itemtype='http://schema.org/ListItem'><a
                expr:href='data:blog.homepageUrl' itemprop='item'><span itemprop='name'>ホーム</span></a>
              <meta content='1' itemprop='position' /></span> <i class="fa fa-chevron-right pause-icon" />
            <span itemprop='name'>未分類</span> <i class="fa fa-chevron-right pause-icon" /> <span itemprop='name'>
              <data:post.title /></span></div>
        </b:if>
      </b:loop>
      <b:else />
      <b:if cond='data:blog.pageType == &quot;archive&quot;'>
        <!-- アーカイブページのパンくずリスト -->
        <div class='breadcrumbs' itemscope='itemscope' itemtype='http://schema.org/BreadcrumbList'>
          <span itemprop='itemListElement' itemscope='itemscope' itemtype='http://schema.org/ListItem'><a
              expr:href='data:blog.homepageUrl' itemprop='item'><span itemprop='name'>ホーム</span></a>
            <meta content='1' itemprop='position' /></span> <i class="fa fa-chevron-right pause-icon" /> アーカイブ: <span
            itemprop='name'>
            <data:blog.pageName /></span>
        </div>
        <b:else />
        <!-- インデックスページ(ホーム・ラベル・検索)のパンくずリスト -->
        <b:if cond='data:blog.pageType == &quot;index&quot;'>
          <div class='breadcrumbs' itemscope='itemscope' itemtype='http://schema.org/BreadcrumbList'>
            <!-- 記事リストページのパンくずリスト -->
            <b:if cond='data:blog.pageName == &quot;&quot;'>
              <span itemprop='itemListElement' itemscope='itemscope' itemtype='http://schema.org/ListItem'><a
                  expr:href='data:blog.homepageUrl' itemprop='item'><span itemprop='name'>ホーム</span></a>
                <meta content='1' itemprop='position' /></span> <i class="fa fa-chevron-right pause-icon" /> <span
                itemprop='name'>投稿一覧</span>
              <b:else />
              <!-- その他場合のパンくずリスト -->
              <span itemprop='itemListElement' itemscope='itemscope' itemtype='http://schema.org/ListItem'><a
                  expr:href='data:blog.homepageUrl' itemprop='item'><span itemprop='name'>ホーム</span></a>
                <meta content='1' itemprop='position' /></span> <i class="fa fa-chevron-right pause-icon" /> <span
                itemprop='name'>
                <data:blog.pageName /></span>
            </b:if>
          </div>
        </b:if>
      </b:if>
    </b:if>
  </b:if>
</b:includable>

前回のパンくずリスト」に対してschemaのタグを付与すると上のようなコードが出来上がった。

どこを改変したのかと言うと、

・トップページに適用しない条件分岐を削除した(これがあるとトップページURLが構造化非対称になる)
・「data-vocabulary.org」の記述を削除(サポート切れになるっぽいのでschema.orgの記述に切り替えた)
・<div class='breadcrumbs'>に「itemscope='itemscope' itemtype='http://schema.org/BreadcrumbList'」をつける
 → このdivタグがschemaのsectionであることを示す(パンくずの範囲の開始ということを示すもの)
・上の直後のspanタグに「itemprop='itemListElement' itemscope='itemscope' itemtype='http://schema.org/ListItem'」をつける
 → パンくず内の要素をリストアップするということを示す(検索エンジンに要素を正しく認識させるもの)
・aタグに「itemprop='item'」をつける
 → リンクはitemにするのがセオリーっぽい
・パンくず内の名前(ホーム、タグ名など)にspanタグを付けて「itemprop='name'」をつける
 → 名前はnameにするのがセオリーっぽい

このように改変してテストツールで検証すると問題なく構造化できていることが確認できた。だけど、所詮は付け焼き刃の知識で書き上げたコードなので正しいのかどうかにはあまり自身がない。誤りがあればコメント欄で教えていただけるとありがたい。

なお、このコードを使うと、記事リスト上部に「ホーム > 投稿一覧」というパンくずリストが表示されることになる。これが邪魔ならば、「記事リストページのパンくずリスト」の条件分岐以下をクラスを付けたdivタグなんかで囲み、その部分をCSSの「visibility: hidden;」で非表示にすれば良い。これで非表示にしても構造化に関するデータは読み込まれる。また、パンくずリスト自体を非表示にしたい場合は「.breadcrumbs」に「visibility: hidden;」を適用させればOK。

ちなみにラベルURLに「 + &quot;?&amp;max-results=10&quot;」がついているのは番号付きページャに対応させるため。

ブログ自体をschema.orgでマークアップをする


<body>

<!-- schema1 -->
<b:if cond='data:blog.pageType == &quot;index&quot;'>
  <div itemscope='itemscope' itemtype='http://schema.org/Blog' style='display: none;'>
    <meta expr:content='data:blog.title' itemprop='name' />
    <b:if cond='data:blog.metaDescription'>
      <meta expr:content='data:blog.metaDescription' itemprop='description' />
    </b:if>
  </div>
</b:if>

<!-- 中略 -->

<!-- 記事リスト -->
<b:includable id='articles'>
  <!-- schema2 -->
  <div class="articles-area" itemscope='itemscope' itemtype='http://schema.org/BlogPosting'>
    <b:if cond='data:post.thumbnailUrl'>
      <meta expr:content='data:post.thumbnailUrl' itemprop='image_url' />
    </b:if>
    <meta expr:content='data:blog.blogId' itemprop='blogId' />
    <meta expr:content='data:post.id' itemprop='postId' />
    <!-- 中略 -->
    <h2 class="article-title" itemprop='name'>
<!-- 以下略 -->

<!-- 個別記事ページ -->
<b:includable id='single-post'>
  <!-- schema3 -->
  <div class="post-area" itemprop='blogPost' itemscope='itemscope' itemtype='http://schema.org/BlogPosting'>
    <b:if cond='data:post.firstImageUrl'>
      <meta expr:content='data:post.firstImageUrl' itemprop='image_url'/>
    </b:if>
    <meta expr:content='data:blog.blogId' itemprop='blogId'/>
    <meta expr:content='data:post.id' itemprop='postId'/>
    <h1 itemprop='name'>
<!-- 以下略 -->

とりあえず、シンプルテンプレートを参考にして上記の記述を追加してみた。詳しい意味合いまでは理解していないが、おそらく以下のような意味合いなのではないかと思うのでここにメモしておく。ちなみに、この記述が間違っていてもデザインに影響はない(検索エンジンの認識に影響あり)。

<!-- schema1 --> は、インデックスページ(トップ、ラベル、検索など)の時にブログタイトルを「name」、説明文を「description」としてマークアップするということだと思う。

<!-- schema2 --> は、記事リストにサムネイルがあれば それを「image_url」に、またブログIDを「blogId」、ポストIDを「PostId」、そして記事タイトルを「name」としてマークアップしている。これは <!-- schema3 --> も同様。

シンプルテンプレートには、ブログ冒頭、記事リスト、個別記事、プロフィールなど著作者情報がわかるもの のみにschemaのマークアップが付いている(以前にDLしたものなので現在は違うかも)。でも、他所様のテンプレートにはこれらが付いていたり付いてなかったり、また、これ以上にマークアップされていたりと様々だった。要するにschemaの記述は必須ではなく、検索結果へのこだわりの分だけつければいいのではないだろうか?

というわけで、最低限のSEO対策は完了とし、これで このテンプレートのversion1.0は完成ととする。

余談


Twitterカードの画像が想定外の場合


上記のOGP設定は、テストブログの場合は上手くいったのだが、このブログの場合はなぜかおかしなことになる。具体的にはインデックスページのURLのときの画像がどういうわけか、適当な記事のアイキャッチ画像になってしまうという不具合が発生し、指定したURLから引っ張って来てくれない。というわけで、コードを書き換えていろいろ試してみたところ、以下のようなことが分かった。

Twitterカードの記述は不要かもしれない


とりあえず、他所様のテンプレートを参照してみたところ、この記事で扱ったような複雑な記述はされておらず、単純に

<meta content='summary_large_image' name='twitter:card'/>

のみのものもあった。で、これだけで試してみたところ、インデックスページ以外は上手く記事情報を参照してくれた。なので、インデックスページがシェアされた時の画像にこだわりがなければこれだけで良いのかも知れない。

インデックスページの画像を変更する


上記のことがわかったので、次はインデックスページの条件分岐を作り、その中に指定した画像をURLを引っ張って来るような記述をした。その記述は以下のようなものになる。

<b:if cond='data:blog.pageType == &quot;index&quot;'>
  <!-- 自分の画像URLに変更する -->
  <meta property="og:image" content='画像URL' name='twitter:image:src' />
</b:if>

すると、上手い具合にインデックスページに指定した画像が表示されるようになった。

まとめ


まとめると、Twitterカードの記述を以下のような記述に変更すれば良いということになる。

<!-- Twitterカード -->
<meta content='summary_large_image' name='twitter:card'/>

<b:if cond='data:blog.pageType == &quot;index&quot;'>
  <!-- 自分の画像URLに変更する -->
  <meta property="og:image" content='画像をURL' name='twitter:image:src'/>
</b:if>