nuxt.js(v2)でSEOに必要なmeta(OGP)で入力漏れの事故をなくす

[nuxt.js(v2)でSEOに必要なmeta(OGP)を入れたい](https://qiita.com/amishiro/items/b7260116b282d2cf2756)で、最低限必要なmeta(OGP)をmixinにまとめた。このままでも使えるけど、共通化できるものを整理してつつ入力漏れの事故をなくしたい。

## 今回の目標

### 基本情報はnuxt.config.jsで管理する

以下をnuxt.config.jsで管理しつつ、環境変数でいつでも変更できるようにしておく。

- サイト名
- 共通のディスクリプション
- ドメイン
- ベースディレクトリ
- OGP画像置き場

### 下層ページのmeta(OGP)情報未入力時の対応をする

下層ページでmeta(OGP)情報の書き忘れが発生してもいいようにする。全ページ内容を変えるのはSEOにっとて大事。入力しなくても、全ページ違っていてほしい。~~そもそも、ページURLとか、毎回書かなくてよくない？~~

ということで、こんな感じを目標に…

#### タイトル

titleTemplate機能は使わない。

- トップページ&未入力　-> サイト名
- 下層ページ -> ページタイトル - サイト名

### ディスクリプション
- トップページ&未入力　-> 共通ディスクリプション
- 下層ページ -> ページ個別のディスクリプション
- 未入力＆ページタイトルあり　-> {サイト名}サイトの{ページタイトル}ページです。{共通ディスクリプション}

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

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

### OGP画像URL

OGP置き場のURLは毎回書かなくていいようにしておいた上で、

- トップページ　-> 共通画像への絶対パス
- 下層ページ -> ページ個別画像への絶対パス
- 未入力 -> 表示なし

やってきます。

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

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

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

## 環境変数を設定する

環境変数とディフォルト値を設定する。

```javascript:nuxt.config.js
const baseName = process.env.BASE_NAME || '株式会社アミテン'
const baseDesc = process.env.BASE_DISC || '共通のディスクリプション。得意分野で「ヒトの自立」「ヒトのつながり」を促進する。そんな会社にしたいです。'
const baseUrl = process.env.BASE_URL || 'http://localhost:3000'
const baseOgp = process.env.BASE_OGP || '/lib/img/ogp'
const baseDir = process.env.BASE_DIR || '/'
```

#### 使い方

##### MAC

```terminal:terminal
BASE_URL=https://example.com BASE_DIR=/yourDir/ npm run dev
```

##### WIN 
※windowsは環境変数の扱い方が違うので`cross-env`経由で行う。nuxt.js(v2)系からは、cross-envがはじめから入っている。

```terminal:terminal
npx cross-env BASE_URL=https://example.com BASE_DIR=/yourDir/ nuxt
```

#### よくつかう場合はpackage.jsonに書いておく。

```json:package.json
{
  "scripts": {
    "generate": "nuxt generate",
    "generate:production": "cross-env BASE_URL=https://example.com BASE_DIR=/yourDir/ nuxt generate",
    ...
  },
}
```

```terminal:terminal
npm run generate:production
```

OK。

## どこでも使えるようにする

nuxt.config.jsのrouterプロパティのbaseに`baesDir`を登録する。あとの項目は、envプロパティに登録。これで、バックエンド・フロントエンドともにどこからでも使えるようになる。

- [公式ドキュメント「routerプロパティ」](https://ja.nuxtjs.org/api/configuration-router/)
- [公式ドキュメント「envプロパティ」](https://ja.nuxtjs.org/api/configuration-env/)

```javascript:nuxt.config.js
const baseName = process.env.BASE_NAME || '株式会社アミテン'
const baseDesc = process.env.BASE_DISC || '共通のディスクリプション。共通のディスクリプション。得意分野で「ヒトの自立」「ヒトのつながり」を促進する。そんな会社にしたいです。'
const baseUrl = process.env.BASE_URL || 'http://localhost:3000'
const baseOgp = process.env.BASE_OGP || '/lib/img/ogp'
const baseDir = process.env.BASE_DIR || '/'

module.exports = {
  mode: 'universal',
  env: {
    baseName: baseName,
    baseDesc: baseDesc,
    baseUrl: baseUrl,
    baseOgp: baseOgp,
  },
  router: {
    base: baseDir,
  },
  ...
}
```

contextかprocessを経由してどこからでも使えるようになった。試しに使ってみる。

```vue:index.vue
<template lang="pug">
.main
  p context経由でアクセス：{{test1}}
  p process経由でアクセス：{{test2}}
  p おまけ（router.base取得）：{{test3}}
</template>

<script>
export default {
  asyncData (context) {
    console.log(context)
    return {
      test1: context.env.baseName,
      test2: process.env.baseDesc,
      test3: context.base,
    }
  },
}
</script>
```

Good!基本情報をnuxt.config.jsで管理できるようになりました。

## nuxt.config.jsにheadを追加

nuxt.config.jsにhead情報を追加していく。早速、さっき設定したbaseセットを利用する。
本来は、CDNの読み込みやicon関連なども入ってくるが、今回はSEOに関連するmeta(OGP)のみ記載。

```javascript:nuxt.config.js
const baseName = process.env.BASE_NAME || '株式会社アミテン'
const baseDesc = process.env.BASE_DISC || '共通のディスクリプション。共通のディスクリプション。得意分野で「ヒトの自立」「ヒトのつながり」を促進する。そんな会社にしたいです。'
const baseUrl = process.env.BASE_URL || 'http://localhost:3000'
const baseOgp = process.env.BASE_OGP || '/lib/img/ogp'
const baseDir = process.env.BASE_DIR || '/'

module.exports = {
  mode: 'universal',
  env: {
    baseName: baseName,
    baseDesc: baseDesc,
    baseUrl: baseUrl,
    baseOgp: baseOgp,
  },
  router: {
    base: baseDir,
  },
  ...
  head: { // 以下を追加
    htmlAttrs: {
      prefix: 'og: http://ogp.me/ns#'
    },
    title: baseName,
    meta: [
      { 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}/common.jpg` },
      // { property: 'article:publisher', content: 'FacebookURL' },
      // { property: 'fb:app_id', content: 'FacebookAppID' },
      // { name: 'twitter:card', content: 'summary' },
      // { name: 'twitter:site', content: '@Twitter' },
    ],
    ...
  },
}
```

OK。環境変数で指定した内容もバッチリ反映される。

## 本題

下層ページのmeta(OGP)情報未入力時の対応をしていく。
まずは、mixinファイルをカスタマイズ。

```javascript:assets/mixins/meta.js
export default {
  asyncData (context) {
    return {
      baseName: context.env.baseName,
      baseDesc: context.env.baseDesc,
      baseUrl: context.env.baseUrl,
      baseOgp: context.env.baseOgp,
    }
  },
  head () {
    const head = { meta: [] }

    // タイトル
    if (this.meta.title) {
      const title = `${this.meta.title} - ${this.baseName}`
      head.title = title
      head.meta.push({ hid: 'og:title', property: 'og:title', content: title })
    }

    // ディスクリプション
    if (this.meta.description) {
      head.meta.push({ hid: 'description', name: 'description', content: this.meta.description })
      head.meta.push({ hid: 'og:description', property: 'og:description', content: this.meta.description })
    } else if (!this.meta.description && this.meta.title) {
      const disc = `${this.baseName}サイトの${this.meta.title}ページです。${this.baseDesc}`
      head.meta.push({ hid: 'description', name: 'description', content: disc })
      head.meta.push({ hid: 'og:description', property: 'og:description', content: disc })
    }

    // ページタイプ
    if (this.meta.type) {
      head.meta.push({ hid: 'og:type', property: 'og:type', content: this.meta.type })
    } else if (this.$route.path === '/') {
      head.meta.push({ hid: 'og:type', property: 'og:type', content: 'website' })
    }

    // ページURL
    const url = `${this.baseUrl}${this.$router.history.base}${this.$route.path}`
    head.meta.push({ hid: 'og:url', property: 'og:url', content: url })

    // OGP画像URL
    if (this.meta.image) {
      const imageUrl = `${this.baseUrl}${this.$router.history.base}${this.baseOgp}${this.meta.image}`
      head.meta.push({ hid: 'og:image', property: 'og:image', content: imageUrl })
    }

    return head
  }
}
```

これで、index.vueでは何も設定しなくて良くなった。
変更したかったら、`head()`を使って修正すればよい。

```vue:index.vue
<template lang="pug">
.main
  nuxt-link(to="/test") goto testPage
</template>

<script>
export default {
  // head () {
  //   return {
  //     titleTemplate: null.
  //     title: 'すごくかっこいいサイト名をつかったタイトル'
  //   }
  // }
}
</script>
```

からの、下層ページでは、mixinを利用する。

```vue:pages/test.vue
<template lang="pug">
.main
  nuxt-link(to="/") goto top
</template>

<script>
import Meta from '~/assets/mixins/meta' // ←追加
export default {
  mixins: [Meta], // ←追加
  data () {
    return {
      meta: {
        title: 'Test-Page!', // ←未入力も対応
        description: 'ページ個別のディスクリプション', // ←未入力も対応
        type: 'article', // ←未入力も対応
        image: '/test.jpg', // ←OGP画像置き場からのURLだけでOK、未入力も対応
      },
    }
  },
}
</script>
```

極端な話、titleだけでも大丈夫。

```vue:pages/ex.vue
<template lang="pug">
.main
  nuxt-link(to="/") goto top
</template>

<script>
import Meta from '~/assets/mixins/meta'
export default {
  mixins: [Meta],
  data () {
    return {
      meta: {
        title: 'サンプルページ', // ←これだけでもなんとかなる。
      },
    }
  },
}
</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-n-head="true" data-hid="og:type" property="og:type" content="artice">
  <meta data-n-head="true" data-hid="og:image" property="og:image" content="/lib/img/ogp/common.jpg">
  <meta data-hid="og:title" property="og:title" content="サンプルページ - 株式会社アミテン" data-n-head="true">
  <meta data-hid="description" name="description" content="株式会社アミテンサイトのサンプルページページです。共通のディスクリプション。得意分野で「ヒトの自立」「ヒトのつながり」を促進する。そんな会社にしたいです。" 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="http://localhost:3000/ex" data-n-head="true">
</head>
```

入力漏れの事故がなくなりました。＼(^o^)／


# 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)