Gridsomeでイチからブログを作る - metaタグやOGPを最適化する

前回、ようやく記事を書けるようになりました。一応ブログの形にはなりましたが、まだまだ機能として不十分です。これから少しづつ機能追加していきます。今回は検索やTwitterなどのシェア時に役立つmetadata(metaタグ)をカスタマイズしていきます。

metaタグis何?

metaタグとはHTMLのヘッダー部分に記述される<meta>というタグをさし、サイトやページの言語や説明、カバー画像などを指定します。本来はmetadataと称されるべきかもしれませんが、一般的にmetaタグと呼ばれているようです。検索サイトではこの情報をもとに説明を表示したり、SNSでリンクがシェアされた場合もこの情報を参照します。OGPと呼ばれるものもこのメタタグの一種です。

つまり検索流入やシェアされたリンクからの流入を期待するならば必須になってくる設定です。

一般的によく使うmetadata

metadataはサイトを表示するために必ずしも必須ではありません。特にTwitterやFacebook用のOGP設定はなくても表示にはなんら影響しません。ここでは一般的に良く使うものをサラっと紹介します。

一般的なもの

  • <meta charset="UTF-8">
  • <meta http-equiv="X-UA-Compatible" content="IE=edge">
  • <meta name="description" content="">

OGP

  • <meta property="og:site_name" content="">
  • <meta property="og:type" content="">
  • <meta property="og:title" content="">
  • <meta property="og:description" content="">
  • <meta property="og:url" content="">
  • <meta property="og:image" content="">

Twitter

  • <meta name="twitter:card" content="">
  • <meta name="twitter:title" content="">
  • <meta name="twitter:description" content="">
  • <meta name="twitter:image" content="">

こんな感じのタグが良く使われます(cotentに指する値は変動するのでここでは空にしています)。

見てわかるように、どのページなのかによって変わるもの(ページに依存する情報)と変わらないもの(サイトに依存する情報)があります。つまり、場合によっては値を動的に変化させる必要があります。

Gridsomeでmetadataを扱う

Gridsomeではmetadataを2つの方法、3箇所で設定する方法があります。

  • main.jsで設定
  • App.vueで設定
  • ページテンプレート用コンポーネントで設定

公式にはPopulating <head> - Gridsomeで解説されており、App.vueでの設定は、Overriding App.vue - Gridsomeで解説されています。

なお、metadata含むhead情報の指定はGridsomeでは内部的にvue-metaというライブラリを使っています。詳細な使いかたはそちらも合わせてみてみると良いでしょう。

この3種類の使いわけですが、

  • main.js: サイトに依存し、ページが変わっても変化しないもの
  • App.vue: ページに依存し、Index時の設定またはデフォルト値の指定
  • ページテンプレート: ページに依存するもの

を意識してみると良いでしょう。

main.jsで設定する

main.jsでの指定は少し特殊です。
公式ではこんなコードになっています。

export default function (Vue, { head }) {
  // Add inline CSS
  head.style.push({
    type: 'text/css',
    cssText: '.some-custom-css {color: red}'
  })

  // Add an external CSS file
  head.link.push({
    rel: 'stylesheet',
    href: 'https://some-server.com/external-styleheet.css'
  })
  
  // Add an external Javascript before the closing </body> tag
  head.script.push({
    src: 'https://some-server.com/external-script.js',
    body: true
  })

  // Add a meta tag
  head.meta.push({
    name: 'keywords',
    content: 'HTML,CSS,XML,JavaScript'
  })
}

第二引数のオブジェクトの中にheadを取り、head.{タグ}の配列に対してpushメソッドで追加していく設定方法ですね。今回はメタタグの話ですが、もちろんメタタグ以外のscriptlinkも同じように指定できます。

ここの情報はサイト全体で指定するものにしておくのがオススメです。しかし、後述するApp.vueでの指定と実質同じなので無理にこちらで設定する必要もありません。

App.vueで設定する

App.vue自体は必須の要素ではありませんが、使うようにしておくほうがなにかと便利です。

  • metadataの指定ができる(今回の話)
  • Layoutに左右されない共通要素の指定ができる

まず、簡単に見てみるとこんな感じになります

<template>
  <router-view />
</template>

<script>
export default {
  metaInfo() {
    return {
      meta: [
        { property: `og:locale`, content: `ja_JP` },
        { key: `og:type`, property: `og:type`, content: `website` },
        {
          key: `og:site_name`,
          property: `og:site_name`,
          content: `my site`,
        },
        {
          key: `og:title`,
          property: `og:title`,
          content: `Home | my site`,
        },
      ]
    }
  }
}
</script>

Vue.jsのSFCのscript要素の中でmetaInfoというメソッドが使えます。ここに記述していくことになります。この時、ページによって変更されるものはkeyを指定します。これは元のvue-metaでのhid要素と同一です。子コンポーネント側で同一keyの指定があった場合、子コンポーネントの指定でオーバーライドします。

つまり子コンポーネント側で指定がない場合はここで指定した値が使われます。指定がある場合、子コンポーネントの指定(=ページごとの指定)となります。デフォルトの設定と考えると良いでしょう。

しかし、ここで要素をベタ書きにするのはイマイチなアプローチです。実はgridsome.config.jsで指定した内容はクエリのmetadata要素で取得できます。このApp.vueではサイトの設定もしくはデフォルト設定として使うので、内容はconfigを参照することで設定を1箇所にまとめられます。

例えばgridsome.config.js

module.exports = {
  siteName: `My Site`,
  titleTemplate: `%s | My Site`,
  siteUrl: `https://my_site.example.com`,
  siteDescription: `僕のすごいサイト`,
  metadata: {
    siteOgImage: `ogp.png`,
  }
}

のように指定して、App.vue

<template>
  <router-view />
</template>

<static-query>
query {
  metadata {
    siteName
    siteDescription
    siteUrl
    siteOgImage
  }
}
</static-query>

<script>
export default {
  metaInfo() {
    return {
      link: [
        {
          key: `canonical`,
          rel: `canonical`,
          href: this.$static.metadata.siteUrl
        },
      ],
      meta: [
        { key: `og:locale`, property: `og:locale`, content: `ja_JP` },
        { key: `og:type`, property: `og:type`, content: `website` },
        {
          key: `og:url`,
          property: `og:url`,
          content: this.$static.metadata.siteUrl,
        },
        {
          key: `og:site_name`,
          property: `og:site_name`,
          content: this.$static.metadata.siteName,
        },
        {
          key: `og:image`,
          property: `og:image`,
          content:  this.$static.metadata.siteOgImage,
        },
      ]
    }
  }
}
</script>

のような感じです。

このようにStatic-Queryで取得したデータをmetaInfoで使うようにします。なお、gridsome.config.jsではmetadataとして取得できる情報はいくつかのものに決まっています。それ以外にカスタムした任意の要素を増やす場合は、metadataの中で任意のkey-valueを指定するとmetadataクエリに載せることができます。もしくはgridsome.server.jsのほうでaddMetadataメソッドを使っても可能です。

ページコンポーネントで設定する

ページコンポーネントは例えばpages/templates/の中にあるコンポーネントファイルです。例えば前回まで作ってきたブログの場合、1ページはtemplates/Post.vueがページ用のコンポーネントになります。

この1記事を表示するとき、タイトルやURLなどはサイト全体のものではなく、ページ用のものがふさわしいはずです。App.vueでkeyを指定したものは同じkeyを使うことで子コンポーネント(=ページコンポーネント)側のものが優先されるようにオーバーライドされます。

簡単にPost.vueを見てみましょう。

<template>
  <article>
    <h1>{{ $page.post.title }}</h1>
    <VueRemarkContent />
  </article>
</template>

<page-query>
query Post  ($id: ID!) {
  post (id: $id) {
    title
    content
    path
  }
  metadata {
    siteName
    siteUrl
  }
}
</page-query>

<script>
export default {
  metaInfo() {
    return {
      title: this.$page.post.title,
      link: [
        {
          key: `canonical`,
          rel: `canonical`,
          href: this.$page.metadata.siteUrl + this.$page.post.path,
        },
      ],
      meta: [
        { key: `og:type`, property: `og:type`, content: `article` },
        {
          key: `og:url`,
          property: `og:url`,
          content: this.$page.metadata.siteUrl + this.$page.post.path,
        },
        {
          key: `og:title`,
          property: `og:title`,
          content: `${this.$page.post.title} | ${this.$page.metadata.siteName}`,
        },
      ]
    }
  }
}
</script>

先程のStatic-QueryのようにPage-Queryで取得した値ももちろん使えるため、それらを組み合わせていきます。ここで指定しないものは親であるApp.vueのものが使われますので、こちらではオーバーライドするものだけを指定します。

画像を指定する際の注意点

これはmetadataの指定に限ったことではないのですが、ogp:imagetwitter:imageのように画像を扱うことが多いので触れておきます。

Gridsomeでは、画像の置き場所として2箇所候補があります。
1. /static/
2. /src/assets/

結論から言えば、基本的に画像は2のアセット用のディレクトリに置くべきでしょう。

/staticディレクトリは、ビルドされるとビルドディレクトリのトップにそのまま置かれます。assets内のものは、assetsとして処理されファイル名にダイジェストが追加されて配置されます。このassetsの仕組みは他のフレームワークで見られるものと同様のものです。

なぜassetsにダイジェスが付与されるかというと、簡単に言えばファイルが更新されたときに適切に表示するためです。ブラウザはファイル名がそのままの場合キャッシュを優先的に使うことがあります。その場合、サイトを更新したにもかかわらずユーザーは古いデータを表示しつづけてしまうことがあります。これを防ぐためにキャッシュを使わせない仕組みとしてダイジェストを付与しています。

そのため、/src/assets/内に置いたファイルはただファイルを指定しただけだとGridsomeは表示できません。それを解決するためにasset-loaderを使います(asset-loaderはGridsomeに組み込まれているので別途ライブラリのインストールなどは必要ありません)。

asset-loaderを使う

今回は画像を扱うのは/src/assets/images/というディレクトリで扱うことにします。

まず、girdsome.config.js

module.exports = {
  // 中略
  chainWebpack(config) {
    config.resolve.alias.set(`@images`, `@/assets/images`)
  },
}

と追加して、エイリアスを張ります。これで/src/assets/images/ディレクトリには@imagesでアクセス可能なようパスが解決されるようになります。

あとは使うときに
require('!!assets-loader!@images/{ファイル名}').srcのように書きます。
assets-loaderから返るのはオブジェクトで、srcに実際のパスが入っているため最後に.srcをつけて取り出しています(なおここでは詳しくassets-loaderについては触れません)。

assetsに置いた画像をOGPで扱う

これをOGP画像指定にも応用してみます。

まずは/src/assets/images/ogp.jpgとして何か適当に画像を配置してみます。

次に前回追加してみたブログ記事のfrontmatterに画像を追加してみましょう。
前回までの流れで/src/contents/posts/my-first-article.mdがあるはずなので、そこにcover_imageとして追記します(ちなみにcover_imageは任意のキー名に変えても問題ありません)。

---
title: 最初の記事
cover_image: ogp.jpg
---
これは私の最初の記事です

そして、ページコンポーネントの/src/templats/Post.vueに追記します。

<template>
  <!-- 中略 -->
</template>

<page-query>
query Post  ($id: ID!) {
  post (id: $id) {
    title
    content
    path
    cover_image
  }
  metadata {
    siteName
    siteUrl
  }
}
</page-query>

<script>
export default {
  metaInfo() {
    return {
      meta: [
        // 中略
        {
          key: `og:image`,
          property: `og:image`,
          content: this.$page.metadata.siteUrl +
              require(`!!assets-loader!@images/${this.$page.post.cover_image}`).src
        },
      ]
    }
  }
}
</script>

こんな感じです。og:imageは絶対パスである必要があるので、siteUrlと組み合わせて絶対パスにしています。このassets-loaderを使う方は公式ドキュメントに言及がないので注意してください。

GridsomeのリポジトリのIssueのg-images with dynamic paths not correctly generating full feature set · Issue #292 · gridsome/gridsomeまたはGridsome g-images with dynamic paths - DEV Community 👩‍💻👨‍💻の記事などが参考になります。

まとめ

今回はブログとしても大切なメタタグについて設定してみました。vue-metaが良くできているのでそれにのっかていてとても簡単に実装できます。また、Gridsomeはgenerateする前提なのでSSRとかSSRしないとか悩む必要もなく、設定さえしておけば問題なく処理されるので楽でいいですね。

次回移行も「ブログ」としてまずはこれは必要だろう、という機能を優先的にどんどん強化していきます。

Gridsomeでイチからブログを作る - RSSを配信するブログリニューアルは式年遷宮 - ブログをVuePressからGridsomeに変えました