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

初のアドベントカレンダー参加。こんな内容でいいのかなと思いつつ…nuxt.jsにSEOに必要なmeta(OGP)タグを入れていきます。

# SEOに必要なmeta(OGP)タグを入れたい

nuxt.jsでは、metaタグのコントロールにvue-metaが使われている。これ、ページ数が増えると~~めんどくさい~~記入漏れなどのミスが起こる。なので、最低限必要なmeta(OGP)をフォーマット化しておく。あとから仕様を変更できればなおよし。

## 今回の目標

[The Open Graph protocol](http://ogp.me/)を参考にしつつ、meta(OGP)を以下のようにしたい。（icon関連はPWAの設定と一緒にやるので今回は保留。）

```html:最低限のmeta(OGP)
<html prefix="og: http://ogp.me/ns#">
<head>
  <!-- title/description -->
  <title>タイトル</title>
  <meta name="description" content="ディスクリプション">

  <!-- OGP -->
  <meta property="og:site_name" content="サイト名" >
  <meta property="og:type" content="タイプ" >
  <meta property="og:url" content="ページURL" >
  <meta property="og:title" content="ページタイトル" >
  <meta property="og:description" content="ディスクリプション" >
  <meta property="og:image" content="OGP画像URL" /> 

  <!-- FB/twitter ※必要ならば以下追加-->
  <!-- meta property="article:publisher" content="FacebookURL" -->
  <!-- meta property="fb:app_id" content="FacebookAppID" -->
  <!-- meta name="twitter:card" content="summary_large_image" -->
  <!-- meta name="twitter:site" content="@Twitter" -->
  ...
</head>
```

## meta(OGP)の仕様

当たり前のことを、一度書き出す。

### タイトル
- トップページ　-> サイト名
- 下層ページ -> ページタイトル - サイト名

### ディスクリプション
- トップページ　-> 共通ディスクリプション
- 下層ページ -> ページ個別のディスクリプション

### ページタイプ
- トップページ　-> website
- 下層ページ -> artice

### サイトのURLとページURL
- 絶対パスで入力

### OGP画像URL
- トップページ　-> 共通画像への絶対パス
- 下層ページ -> ページ個別画像への絶対パス


ということで、試していく。

## universal(SSR)モードにする！

まずは、universal(SSR)モードにする。`generate`する場合でもuniversal(SSR)モードにする。（大事なことなので２回言う。）
SPAモードは（たとえ`generate`したとしても）ページ毎のmeta(OGP)が反映されない。

```javascript:nuxt.config.js
module.exports = {
  mode: 'universal',
  ...
}
```

## 公式ドキュメントを参考にベースを作成

nuxt.jsの[公式ドキュメント](https://ja.nuxtjs.org/api/configuration-head/)と、vue-metaの[公式ドキュメント](https://www.npmjs.com/package/vue-meta)を参考にベース部分を作成。小規模のサイトならこれだけでもOK。

### nuxt.config.js

nuxt.config.jsにheadを追加。本来は、CDNの読み込みやicon関連なども入ってくるが、今回はSEOに関連するmeta(OGP)のみ記載。

```javascript:nuxt.config.js
module.exports = {
  head: {
    htmlAttrs: {
      prefix: 'og: http://ogp.me/ns#'
    },
    titleTemplate: '%s - サイト名',
    meta: [
      { hid: 'description', name: 'description', content: '共通ディスクリプション' },
      { hid: 'og:site_name', property: 'og:site_name', content: 'サイト名' },
      { hid: 'og:type', property: 'og:type', content: 'website' },
      { hid: 'og:url', property: 'og:url', content: 'https://example.com' },
      { hid: 'og:title', property: 'og:title', content: 'サイト名' },
      { hid: 'og:description', property: 'og:description', content: '共通ディスクリプション' },
      { hid: 'og:image', property: 'og:image', content: 'https://example.com/img/ogp/common.jpg' },
      // { property: 'article:publisher', content: 'FacebookURL' },
      // { property: 'fb:app_id', content: 'FacebookAppID' },
      // { name: 'twitter:card', content: 'summary' },
      // { name: 'twitter:site', content: '@Twitter' },
      ...
    ],
  },
}
```

### pagesファイル(トップページ)

トップページでは、`titleTemplate`の機能を`null`にしつつ、サイト名のみ表示されるようにする。

```vue:pages/index.vue
<template lang="pug">
.main
  h1 サイト名
  nuxt-link(to="/test") go to Test-Page
</template>

<script>
export default {
  head () {
    return {
      titleTemplate: null,
      title: 'サイト名',
    }
  },
}
</script>
```

### pagesファイル（トップページ以外）

下層ページでは、`titleTemplate`の機能を使いタイトルを表示。また、各種metaタグの内容を変える。

```vue:pages/test.vue
<template lang="pug">
.main
  h1 {{ meta.title }}
  nuxt-link(to="/") go to HOME
</template>

<script>
export default {
  data () {
    return {
      meta: {
        title: 'Test-Page!',
        description: 'ページ個別のディスクリプション',
        type: 'article',
        url: 'https://example.com/test',
        image: 'https://example.com/img/ogp/test.jpg',
      },
    }
  },
  head () {
    return {
      title: this.meta.title,
      meta: [
        { hid: 'description', name: 'description', content: this.meta.description },
        { hid: 'og:type', property: 'og:type', content: this.meta.type },
        { hid: 'og:title', property: 'og:title', content: this.meta.title },
        { hid: 'og:description', property: 'og:description', content: this.meta.description },
        { hid: 'og:url', property: 'og:url', content: this.meta.url },
        { hid: 'og:image', property: 'og:image', content: this.meta.image },
      ],
    }
  }
}
</script>
```

### 結果

```html:/
<html prefix="og: http://ogp.me/ns#" data-n-head="prefix">
  <head>
    <title data-n-head="true">サイト名</title>
    <meta data-n-head="true" data-hid="og:site_name" property="og:site_name" content="サイト名">
    ...
    <meta data-hid="description" name="description" content="共通ディスクリプション" data-n-head="true">
    <meta data-hid="og:type" property="og:type" content="website" data-n-head="true">
    <meta data-hid="og:url" property="og:url" content="https://example.com" data-n-head="true">
    <meta data-hid="og:title" property="og:title" content="サイト名" data-n-head="true">
    <meta data-hid="og:description" property="og:description" content="共通ディスクリプション" data-n-head="true">
    <meta data-hid="og:image" property="og:image" content="https://example.com/img/ogp/common.jpg" data-n-head="true">
  </head>
```

```html:/test
<html prefix="og: http://ogp.me/ns#" data-n-head="prefix">
  <head>
    <title data-n-head="true">Test-Page! - サイト名</title>
    <meta data-n-head="true" data-hid="og:site_name" property="og:site_name" content="サイト名">
    ...
    <meta data-hid="description" name="description" content="ページ個別のディスクリプション" data-n-head="true">
    <meta data-hid="og:type" property="og:type" content="article" data-n-head="true">
    <meta data-hid="og:title" property="og:title" content="Test-Page!" data-n-head="true">
    <meta data-hid="og:description" property="og:description" content="ページ個別のディスクリプション" data-n-head="true">
    <meta data-hid="og:url" property="og:url" content="https://example.com/test" data-n-head="true">
    <meta data-hid="og:image" property="og:image" content="https://example.com/img/ogp/test.jpg" data-n-head="true">
  </head>
```

いい感じ。`generate`で生成されたhtmlファイルにも反映されている。

## 何度も書く場所をmixin化

test.vueへ書いた`head () {...}`の部分。~~めんどくさいので~~ミス軽減のためmixinで共通化する。

### mixinファイル作成

mixinファイルを作成して、`head () {...}`をごそっと移植。

```javascript:assets/mixins/meta.js
export default {
  head () {
    return {
      title: this.meta.title,
      meta: [
        { hid: 'description', name: 'description', content: this.meta.description },
        { hid: 'og:type', property: 'og:type', content: this.meta.type },
        { hid: 'og:title', property: 'og:title', content: this.meta.title },
        { hid: 'og:description', property: 'og:description', content: this.meta.description },
        { hid: 'og:url', property: 'og:url', content: this.meta.url },
        { hid: 'og:image', property: 'og:image', content: this.meta.image },
      ],
    }
  }
}
```

### pagesファイル(トップページ)

トップページとなるindex.vueはそのままで問題ない。

### pagesファイル（トップページ以外）

下層ページに、`mixins: [Head]`を追加していく。mixinファイルを`import`するのを忘れずに。

```vue:test.vue
<template lang="pug">
...
</template>

<script>
import Meta from '~/assets/mixins/meta' // ←追加
export default {
  mixins: [Meta], // ←追加
  data () {
    return {
      meta: {
        title: 'Test-Page!',
        description: 'ページ個別のディスクリプション',
        type: 'article',
        url: 'https://example.com/test',
        image: 'https://example.com/img/ogp/test.jpg',
      },
    }
  },
  // ←`head ()`は削除。
}
</script>
```

GOOD!`generate`で生成されたhtmlも大丈夫。
mixin化が出来たので、あとは好みで使いやすいようにカスタマイズすればいい。

---

第２段、[nuxt.js(v2)でSEOに必要なmeta(OGP)で入力漏れの事故をなくす](https://qiita.com/amishiro/items/77e3c2546fa63cc69a62)。書きました。



# Link

以下、公開中のnuxt.js(v2)関連の記事一覧

## 技術よりの記事

0. [nuxt.js(v2)のインストール〜ESLint設定まで](https://qiita.com/amishiro/items/9cd8007d46f91c8b5da2)
0. [nuxt.js(v2)の作業ディレクトリを整理](https://qiita.com/amishiro/items/9c31a3a9424de27efe28)
0. [nuxt.js(v2)のベースURLをターミナルからコントロール](https://qiita.com/amishiro/items/7c7af250368083216d8e)
0. [nuxt.js(v2)でpug/stylusを利用する](https://qiita.com/amishiro/items/38fe1b102d7e91a93ada)
0. [nuxt.js(v2)でIE11対応をする（CSS編）](https://qiita.com/amishiro/items/84b14108c749a0a0c966)
0. [nuxt.js(v2)でIE11対応をする（JS編）](https://qiita.com/amishiro/items/db37c6a56ddf7e214144)
0. [nuxt.js(v2)で絶対パス(https~)を取得する方法](https://qiita.com/amishiro/items/9d7ca6b239f604a40424) 
0. [nuxt.js(v2)でSEOに必要なmeta(OGP)を入れたい](https://qiita.com/amishiro/items/b7260116b282d2cf2756)
0. [nuxt.js(v2)でSEOに必要なmeta(OGP)で入力漏れの事故をなくす](https://qiita.com/amishiro/items/77e3c2546fa63cc69a62)

## よく使うプラグインのお話

0. [nuxt.js(v2)で便利なvue-mqを使ってみるがSSRモードでコンソールエラーがでるので確認してみた。](https://qiita.com/amishiro/items/f2e302fedf03c3d0f79e)
0. [nuxt-linkでスムーズスクロールするならvue-scrolltoが便利で気が利いている…と思う。](https://qiita.com/amishiro/items/f70aca4797cd57bc51b5)
0. [nuxt.jsでパララックスをするならvue-parallax-jsがお手軽。Cool!](https://qiita.com/amishiro/items/ccca406a9bda1863ab01)

## まとめ系

0. [nuxt.js(v2)でgenerate納品する前にやっておきたい設定](https://qiita.com/amishiro/items/11bd642728f6b5838189)
0. [nuxt.jsにおける「components」ディレクトリの規約（案）](https://qiita.com/amishiro/items/c7493f5e9375a0609bdc)