Nuxt.js + Netlify + WP REST APIでブログサイトをつくってみる②【OGP設定】

Nuxt.js + Netlify + WP REST APIでブログサイトをつくってみる②【OGP設定】

前回の続き

前回の記事「Nuxt.js + Netlify + WP REST APIでブログサイトをつくってみる①」では、「WP REST APIとの連携」「Nuxt.jsでの静的生成の調整」「Netlifyへのデプロイ」についてメモしました。

今回はその続きで「OGP設定」をしてみようと思います。

OGPって?

「Open Graph Protocol」の略称です。
TwitterやFacebookなどのSNSでページをシェアした時に画像、タイトル、説明など、ページの内容を正しく伝えるために表示させる情報を設定できる仕組みのことです。

ブログ記事やECサイトの商品ページなどシェアすることが多いページでは必須と言っていいぐらい重要な設定ですね。

もしも設定していない場合はSNS側が勝手に適当な画像や説明を引用してしまうので、シェアされた時に意図しない情報が表示されてしまうことがあります。

Nuxtでの実装方法

そんなOGPをNuxtで設定する方法を調べて、自分なりの方法で実装してみました。

まず設定したいOGP情報をCMS(WordPress)に準備します。

画像・タイトル・ディスクリプションはWPで

WordPressの場合はカスタムフィールドを使って数個の設定項目を設けてあげるだけで簡単に記事にOGP情報を持たせることができます。WP REST APIとの連携で使いまわしたいので今回はその前提でメモしていきます。

カスタムフィールドはOGPを設定したい各ページに「タイトル」「ディスクリプション」「画像」を追加します。尚、WordPressのカスタムフィールドやその追加・設定方法についてはここでは趣旨から離れるので割愛します。個人的にカスタムフィールドの設定は「Smart Custom Fields」がおすすめです。

WP REST APIに情報を追加する

前回の記事で作った投稿記事の情報を取得する独自エンドポイントにOGPのカスタムフィールド情報も追加し、APIから情報取得できる状態にしておきます。

APIから取得する記事データに以下のようにOGP情報を追加します。

<?php
$post_data = [];

$post_args = [
  'post_type'      => 'post',
  'posts_per_page' => -1,
  'post_status'    => 'publish',
  'orderby'        => 'date',
  'order'          => 'DESC'
];

$post_query = new WP_Query( $post_args );
if ( $post_query->have_posts() ) :
  while ( $post_query->have_posts() ) :
    $post_query->the_post();
    global $post;

    //OGP画像
    //画像設定がない場合に代わりのデフォルト画像を用意しておく
    $ogp_img       = get_theme_file_uri('assets/images/ogp.jpg');
    $ogp_img_alt   = $post->post_title;
    $attachment_id = get_post_meta($post->ID, 'cf_ogp_image', true);
    
    if(!empty($attachment_id)){
      $image   = wp_get_attachment_image_src($attachment_id, 'ogp');
      $ogp_img = !empty($image) ? $image[0] : get_theme_file_uri('assets/images/ogp.jpg');
      $ogp_img_alt = get_post_meta($attachment_id, '_wp_attachment_image_alt', true);
    }

    $post_data[] = [
      'id'                 => $post->ID,
      //OGPタイトル
      'ogp_title'          => get_post_meta($post->ID, 'cf_ogp_title', true),
      //OGPディスクリプション
      'ogp_description'    => get_post_meta($post->ID, 'cf_ogp_description', true),
      //OGP画像
      'ogp_img'            => $ogp_img,
      'ogp_img_alt'        => $ogp_img_alt

      //・・・省略

    ];
  endwhile;
  wp_reset_postdata();
endif;

return $post_data;
?>

WP REST APIへのエンドポイントの詳しい追加方法については、過去記事をご覧ください。

head()メソッドを使って設定する

APIの準備が整ったらNuxtテンプレートの実装に入ります。

まず基本的なOGPの設定方法について。
OGPはhtmlの<head>タグ内に<meta>タグで設定します。
基本的には下記のような形式です。

<head prefix="og: http://ogp.me/ns#">
  <meta property="og:type" content="website もしくは article">
  <meta property="og:title" content="ページタイトル">
  <meta property="og:description" content="ページの説明">
  <meta property="og:url" content="ページのURL">
  <meta property="og:image" content="画像">
  <meta property="og:image:alt" content="画像の説明">
  <meta name="twitter:card" content="summary_large_image などTwiiterでの表示形式">
</head>
  • <head>タグにprefix="og: http://ogp.me/ns#"を付ける。これは「property="og:」で始まるmetaタグ設定をする場合に必要となるみたいです。
  • og:type => ページタイプを指定。TOPページはwebsiteでそれ以外の下層ページはすべてarticleでOK。
  • og:title => ページタイトル。シェアされる時に表示されます。
  • og:description => ページの説明。シェアされる時表示されます。80~90文字程度が最適とされています。
  • og:url => ページのURL。
  • og:image => シェアする時のサムネイル画像のURLを設定します。
  • og:image:alt => og:imageに設定した画像の説明
  • twitter:card => Twitterでシェアした時の表示形式を設定します。タイトル・説明を大きく見せたい場合はsummary、画像を大きく見せたい場合はsummary_large_imageを指定します。

これをNuxtで設定する時は、ページテンプレートから個別にhead()メソッドで設定できます。

head()メソッド

<script>
  export default {
    head() {
      //storeからOGP情報の入った記事データを取得する
      const page = this.$store.getters.currentPage;
      if(!page){
        return;
      }
      return {
        titleTemplate: null,
        title : page.title,
        meta  : [
        { hid: 'description', name: 'description', content: page.description },
        { hid: 'og:type', property: 'og:type', content: 'article' },
        { hid: 'og:title', property: 'og:title', content: page.ogp_title },
        { hid: 'og:description', property: 'og:description', content:page.ogp_description },
        { hid: 'og:url', property: 'og:url', content: `${process.env.baseUrl}/${this.$route.name}` },
        { hid: 'og:image', property: 'og:image', content: page.ogp_img },
        { hid: 'og:image:alt', property: 'og:image', content: page.ogp_img_alt },
        { name: 'twitter:card', content: 'summary_large_image' }
        ]
      }
    }
  }
</script>

内部でthisを使ってコンポーネントにアクセスできるので、data()に設定した値や、storeから取得したデータも設定できます。ここではOGP情報の他に、APIにタイトルタグやディスクリプションタグの情報も入っているものとして設定しています。

mixinを使う方法は保留

よく紹介されているのが、head()の記述がページごとに何度も出てきて面倒臭いしミスりそうってことでmixin化して設定しやすくする方法です。

nuxt.js(v2)でSEOに必要なmeta(OGP)を入れたい

https://qiita.com/amishiro/items/b7260116b282d2cf2756

便利で簡単かもと思ったのですが、今回の連携ではVuexを使っていることもありdata()から初期値に直書きで設定するのは、CMSと連携して動的なページを作る場合あまり向かないなぁという印象でした。

個人的には普通にhead()内でstoreからデータを引っ張る方がわかりやすかったので今回はこの方法は採用しませんでした。

nuxt.config.jsにデフォルトのメタタグを設定しておく

どのページでも共通のOGP設定・メタタグがある場合、nuxt.config.jsheadプロパティからデフォルトを設定しておくと省略することができます。

require("dotenv").config()
const baseName = process.env.BASE_NAME || 'notes by SHARESL'
const baseDesc = process.env.BASE_DESC || 'notes by SHARESLは大阪のWeb制作会社・株式会社SHARESLの開発者ブログです。制作者が日々考えていることのアウトプットやメモとして残しておきたい備忘録としての記事を更新していきます。WEB制作の実務の中で気づいたことをまとめて後で見返せるノートのような役割を目的としています。'
const baseUrl = process.env.BASE_URL || 'http://localhost:3000'
const baseOgp = process.env.BASE_OGP || `${baseUrl}/img/ogp.png`

export default {
  mode: 'universal',
  /*
  ** Headers of the page
  */
  head: {
    htmlAttrs: {
      prefix: 'og: http://ogp.me/ns#'
    },
    titleTemplate: `%s|${baseName}`,
    meta: [
    { charset: 'utf-8' },
    { name: 'viewport', content: 'width=device-width, initial-scale=1' },
    { hid: 'description', name: 'description', content: baseDesc },
    { hid: 'og:site_name', property: 'og:site_name', content: baseName },
    { hid: 'og:type', property: 'og:type', content: 'article' },
    { hid: 'og:url', property: 'og:url', content: baseUrl },
    { hid: 'og:title', property: 'og:title', content: baseName },
    { hid: 'og:description', property: 'og:description', content: baseDesc },
    { hid: 'og:image', property: 'og:image', content: baseOgp },
    { name: 'twitter:card', content: 'summary_large_image' },
    { name: 'twitter:site', content: '@notesbysharesl' }
    ]
    ]
  },

  //・・・省略
}

こんな感じで前回の記事で扱った環境変数と組み合わせておくと便利かなーと思って設定してみました。こうしておくことで、たとえ記事側でOGP情報を設定しなかったとしても、デフォルトの情報が表示されるようになります。

また、あくまでもデフォルトの設定なのでコンポーネント側のhead()メソッドで設定は上書きできます。

例えば、og:typeは「article」に設定していますが、TOPページ(pages/index.vue)のみhead()メソッドで「website」に上書きすれば、後のページはog:typeは書かなくても「article」になります。

シェアボタンをオリジナルデザインにする

次にシェアボタンについても少しだけカスタマイズします。
デフォルトのボタンだと浮くのでオリジナルにします。オリジナルデザインとか言っちゃってますが、シンプルなSVGのアイコンに変えるだけです。笑

このサイトで使っているFacebook・Twitter・はてなブックマークのSNSボタンをオリジナルの画像に変える方法は下記の通りです。

facebook

<a href="https://www.facebook.com/share.php?u={このページのURLをエンコードしてここに入れる}" target="_blank" rel="noopener">
  <!-- ここにオリジナルの画像を入れる -->
</a>

twitter

<a href="https://twitter.com/share?url={このページのURLをエンコードしてここに入れる}" target="_blank" rel="noopener">
  <!-- ここにオリジナルの画像を入れる -->
</a>

はてなブックマーク

<a href="http://b.hatena.ne.jp/add?mode=confirm&url={このページのURLをエンコードしてここに入れる}&title={記事タイトルをエンコードして入れる}" target="_blank" rel="noopener">
  <!-- ここにオリジナルの画像を入れる -->
</a>

このサイトではシェアボタンを他で使い回すことが全くないので、各SNSごとに個別にコンポーネント化せず3つともまとめてコンポーネント化しました。

ShareButton.vue

<template>
  <aside class="c-share">
    <p class="c-share__caption u-futura-pt-condensed">SHARE</p>
    <div class="c-share__list">
      <a class="fb" :href="facebookShareUrl" target="_blank" rel="noopener">
        <span class="icon"><FacebookSvg/></span>
      </a>
      <a class="tw" :href="twitterShareUrl" target="_blank" rel="noopener">
        <span class="icon"><TwitterSvg/></span>
      </a>
      <a class="hatena hatena-bookmark-button" :href="hatenaShareUrl" target="_blank" rel="noopener">
        <span class="icon"><HatenaSvg/></span>
      </a>
    </div>
    <!-- /.c-share__list -->
  </aside>
  <!-- /.c-share -->
</template>

<script>
  import FacebookSvg from '~/assets/svg/icon-facebook.svg'
  import TwitterSvg from '~/assets/svg/icon-twitter.svg'
  import HatenaSvg from '~/assets/svg/icon-hatebu.svg'

  export default {
    components: {
      FacebookSvg,
      TwitterSvg,
      HatenaSvg
    },
    props: {
      title : {
        type     : String,
        default  : 'notes by SHARESL'
      }
    },
    data() {
      return{
        url : null
      }
    },
    mounted(){
      //このページのURLを取得
      this.url = encodeURIComponent(window.location.href);
    },
    computed : {
      facebookShareUrl(){
        if(!this.url){
          return
        }
        return `https://www.facebook.com/share.php?u=${this.url}`;
      },
      twitterShareUrl(){
        if(!this.url){
          return
        }
        return `https://twitter.com/share?url=${this.url}`;
      },
      hatenaShareUrl(){
        if(!this.url){
          return
        }
        return `http://b.hatena.ne.jp/add?mode=confirm&url=${this.url}&title=${encodeURIComponent(this.title)}`;
      }
    }
  }
</script>

大規模サイトや、他で使い回すことが初めから想定される場合はコンポーネントを最小化しておいた方が実装の都合が良いかもしれません。

OGPの確認

シェアボタンを設置できたので、nuxt generateして実際に設定できているか確認してみましょう。

ソースコードを見てみる

まずはソースコードにOGPの<meta>タグがちゃんと設定されているか確認します。

確認にはChromeブラウザを利用します。
Windowsの場合はキーボードのショートカットキーでCtrl + U、Macの場合はcommand + option + Uでソースコード全体を開きます。

<head>タグ内にちゃんとWPのカスタムフィールドで設定した情報が入ってればOKです。空欄になっていたり違う文言が入っている場合はどこかで実装が間違えている可能性があります。

デバッガーを使う

実際にSNSをシェアした時に反映される情報が正しいかどうか、FacebookとTwitterは公式のデバッガーで確認できます。

シェアデバッガー - Facebook for Developers

https://developers.facebook.com/tools/debug/

Card validator

https://cards-dev.twitter.com/validator

どちらもシェアしたい記事のURLを貼り付けてボタンを押すだけで結果を確認できます。意図した通りの表示になっていたらOKです。

さいごに

今回はSNSでシェアする時に必要となるOGPの設定をNuxt + WP REST APIで行う方法をメモしました。OGP画像だけでなく、SEOに必要なタイトルタグ・ディスクリプションタグも同様の方法でCMS側の記事に情報を持たせてしまう方が便利なので実案件なら必須の対応になりますね。

今回は以上です!