Nuxt.js + Contentful + Netlify でマイブログを公開したお話

※2019/6/6 追記 PWA設定
※2019/7/5 追記 ドメイン設定

---

「Nuxt.jsって便利だなー。でも何作ろう…。ブログかな…。でもどうやって…??」

検索「Nuxt ブログ」 ⇒ 「Nuxt.js + Contentfulで・・・」

「これだ！！」

まだまだわからないことが多くかなり苦戦しましたが、
やっとこさ完成したので公開までにやった内容を簡単に紹介していきます。

細かい点は省いていきますのであしからず…。

~~完成品: b1san Tech Blog~~
※現在は停止しています。

# 1. Nuxtプロジェクトの作成

プロジェクトの作成についてはほぼほぼカットします。

```bash
$ create-nuxt-app my-blog
```

ページ構成はこんな感じです。
ホームと記事ページ、プライバシーポリシー、記事一覧の4ページ構成です。

```
pages/
  ┣posts/
  ┃    ┗_slug.vue
  ┣index.vue
  ┣policy.vue
  ┗all.vue
```

# 2. Contentfulとの連携

Contentful公式にNuxt.jsとの連携について記載されています。

[Integrate Contentful with Nuxt.js](https://www.contentful.com/developers/docs/javascript/tutorials/integrate-contentful-with-vue-and-nuxt/)

## 事前準備

### アカウントの作成

あらかじめContentfulアカウントを作成しておいてください。
この記事ではアカウントの作成については割愛します。

[Contentful](https://www.contentful.com/)

### Contentful CLI インストール

```bash
$ npm i -g contentful-cli
```

### Contentful CLI ログイン

```bash
$ contentful login

A browser window will open where you will log in (or sign up if you don’t have an account), authorize this CLI tool and paste your CMA token here:

? Open a browser window now? Yes
? Paste your token here: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

Great! Your CMA token is now stored on your system. (Located at XXXX\.contentfulrc.json)
You can always run contentful logout to remove it.
```

Open a browser window now?(Y/n)でブラウザ上にContentfulのアクセストークンが表示されます。
Paste your token here: に貼り付ければ完了です。

### spaceの作成

ブログ用のspaceを作成していきます。

```bash
$ contentful space create --name <your-space-name>

? Do you want to confirm the space creation? Yes

  Successfully created space Nuxt tutorial (<your-space-id>)
```

space-idが発行されるので、それを基にブログ用のテンプレートを読み込みます。

```bash
$ contentful space seed --template blog --space-id <your-space-id>
```

ContentfulのWebページで確認すると、新たにspaceが作成されており、Content modelに「Blog Post」と「Person」が設定されています。
またContentを確認するとあらかじめサンプルデータが用意されています。

### アクセストークンの取得

使用するspaceを選択します。

```bash
$ contentful space use
```

先程作成したspace-nameが表示されますので選択してください。
次にアクセストークンを発行します。

```bash
$ contentful space accesstoken create --name <your-space-name>

  Successfully created access token <your-space-name> (<your-accesstoken>)
```

## Nuxt.jsとの連携

### Contentful CDA Javascript SDK のインストール

作成したプロジェクトにContentful CDA Javascript SDKをインストールします。

```bash
$ npm i --save contentful
```

### 連携設定

プロジェクトフォルダ直下に「.contentful.json」を作成します。

```json:.contentful.json
{
  "CTF_PERSON_ID": "15jwOBqpxqSAOy2eOO4S0m",
  "CTF_BLOG_POST_TYPE_ID": "blogPost",
  "CTF_SPACE_ID": "YOUR_SPACE_ID",
  "CTF_CDA_ACCESS_TOKEN": "YOUR_ACCESS_TOKEN"
}
```
YOUR_SPACE_IDとYOUR_ACCESS_TOKENには先程作成したものを設定してください。
作成した「.contentful.json」を「nuxt.config.js」で読み込み、envの設定をします。

```javascript:nuxt.config.js
const config = require('./.contentful.json')

module.exports = {
  // ...
  env: {
    CTF_SPACE_ID: config.CTF_SPACE_ID,
    CTF_CDA_ACCESS_TOKEN: config.CTF_CDA_ACCESS_TOKEN,
    CTF_PERSON_ID: config.CTF_PERSON_ID,
    CTF_BLOG_POST_TYPE_ID: config.CTF_BLOG_POST_TYPE_ID
  }
  // ...
}
```

最後にpluginフォルダにSDKクライアントを作成するための処理を用意しておきます。（pluginフォルダが適切かは疑問です。）

```javascript:plugin/contentful.js
const contentful = require('contentful')
// use default environment config for convenience
// these will be set via `env` property in nuxt.config.js
const config = {
  space: process.env.CTF_SPACE_ID,
  accessToken: process.env.CTF_CDA_ACCESS_TOKEN
}

// export `createClient` to use it in page components
module.exports = {
  createClient () {
    return contentful.createClient(config)
  }
}
```

### コンテンツの取得

pages/index.vueを次のように書き換えます。

```vue:pages/index.vue
<template>
  <div>
    <!-- render data of the person -->
    <h1>{{ person.fields.name }}</h1>
    <!-- render blog posts -->
    <ul>
      <li v-for="post in posts">
        {{ post.fields.title }}
      </li>
    </ul>
  </div>
</template>

<script>
  import {createClient} from '~/plugins/contentful.js'

  const client = createClient()

  export default {
    // `env` is available in the context object
    asyncData ({env}) {
      return Promise.all([
        // fetch the owner of the blog
        client.getEntries({
          'sys.id': env.CTF_PERSON_ID
        }),
        // fetch all blog posts sorted by creation date
        client.getEntries({
          'content_type': env.CTF_BLOG_POST_TYPE_ID,
          order: '-sys.createdAt'
        })
      ]).then(([entries, posts]) => {
        // return data that should be available
        // in the template
        return {
          person: entries.items[0],
          posts: posts.items
        }
      }).catch(console.error)
    }
  }
</script>
```

asyncData「Blog Post」と「Person」の内容を取得してデータとしてセットしています。
画面に内容が表示されていれば完了です。

「Blog Post」について特定の記事のみ取得したい場合は次のようにします。

```javascript
client.getEntries({
  'content_type': env.CTF_BLOG_POST_TYPE_ID,
  'fields.slug': 'your-slug-value'
})
```

# 3. ページデザイン

## CSSフレームワーク・コンポーネント

普段はBulmaやBuefy、Vuetifyなどを利用しているのですが、今回は全く使用していません。
Reset CSSのみ利用していますが、それ以外は全て自分でスタイルを書いています。

## Markdown

ブログ記事はMarkdownで記載するため、変換する必要があります。
今回は「marked」と「highlight.js」を使用しました。

```bash
$ npm i --save marked highlight.js
```

```vue:pages/_slug.vue
<template>
//・・・
  <div v-html="mkbody" />
//・・・
</template>
<script>
//・・・
import marked from 'marked';
import hljs from 'highlight.js';
//・・・
export default {
  //・・・
  computed: {
    mkbody() {
      return marked(this.post.fields.body);
    }
  }
  //・・・
  created() {
    marked.setOptions({
      langPrefix: '',
      highlight: function(code, lang) {
        return hljs.highlightAuto(code, [lang]).value
      }
    })
  }
}
</script>
<style src='highlight.js/styles/tomorrow-night.css'></style>
```

スタイル自体は自作しないといけないのですが個人的にはこれで十分でした。

## エラーページ

Nuxtではlayoutsフォルダに「error.vue」というファイルを作成すると、エラー発生時にそれが表示されるようになります。
意図的にエラーページに飛ばしたい場合はerrorメソッドを利用します。

```javascript
error({
  statusCode: statusCode,
  message: "error message"
})
return;
```

# 4. OGP設定

OGPの設定については以下の記事がとても参考になりました。感謝感謝です。
私が書くよりも圧倒的に詳しいためこちらを参照してください。

[nuxt.js(v2)でSEOに必要なmeta(OGP)を入れたい](https://qiita.com/amishiro/items/b7260116b282d2cf2756)

# 5. Googleアナリティクス設定

Nuxt.jsの公式ページにも記載されていますが、[公式Googleアナリティクスモジュール](https://github.com/nuxt-community/analytics-module)が提供されています。

```bash
npm i --save @nuxtjs/google-analytics
```

設定ファイルにちょちょいと加えるだけで設定できます。

```javascript:nuxt-config.js
modules: [
  ['@nuxtjs/google-analytics', {
    id: 'your id'
  }]
]
```

# 6. デプロイ

## ビルド設定

そのままビルドを行うと動的ページ（今回でいうとpages/_slug.vue）が生成されません。
といってもNetlify上ではちゃんとページとして表示されるのですが、OGPが効きません。
これが個人的なハマりポイントでした。

```javascript:nuxt.config.js
generate: {
  routes() {
    return client.getEntries({
      'content_type': process.env.CTF_BLOG_POST_TYPE_ID,
      order: '-sys.createdAt'
    }).then(entries => {
      return entries.items.map(entry => {
        return {
          route: `posts/${entry.fields.slug}`,
          payload: entry
        }
      })
    })
  }
}
```

## 構成

構成についてはこちらを参考にしました。

[Nuxt.js + Netlify + ContentfulでサーバレスなWebサイト制作](https://corp.ekanlab.co.jp/blog/2/)

通常はGitHubへのプッシュをトリガーにNetlifyでビルド・デプロイされます。

それともう一つ、Contentfulで記事の更新・作成を行った場合に、動的ページを更新・作成するために再ビルドする必要があります。
そのためContentfulの「Blog Post」の更新をトリガーにNetlifyでビルド・デプロイを実行するようにWebhookを設定します。
ContentfulにはNetlify用のWebhookが用意されているので、簡単に設定することができます。

# 7. PWA設定

個人的にスマホで記事を確認するので、せっかくなのでPWAに対応しました。
プッシュ通知は実装していません。

## インストール

Nuxt.jsでPWAを設定するために`@nuxtjs/pwa`を使用します。

```bash
$npm i --save @nuxtjs/pwa
```

## manifest設定

nuxt.config.jsにmoduleの追加とmanifestを設定します。

```javascript
modules: [
  '@nuxtjs/pwa',
],
manifest: {
  name: 'アプリ名',
  short_name: 'アプリ名略',
  title: 'アプリ名',
  'og:title': 'アプリ名',
  lang: 'ja',
  background_color: '背景色',
  theme_color: 'テーマカラー',
  display: 'standalone',
  start_url: '/'  //開始URL
}
```

manifestのプロパティについては[MDN](https://developer.mozilla.org/ja/docs/Web/Manifest)を参考にしてください。

## Nuxt.js のバージョンアップ

このブログはNuxt.jsのバージョンが2.5.1だったのですが、pwaを導入するとcore-jsの警告がでるようになりました。
詳しい原因はわかっていないのですが、この問題はNuxt.jsのバージョンを上げることで解決しました。

## その他設定

その他workboxの設定やプッシュ通知の設定は今回行っていません。

# 8. ドメイン設定

Netlifyドメインでもよかったのですが、せっかくなので独自ドメインを[お名前ドットコム](https://www.onamae.com/)で取得しました。

ドメインの設定については「[お名前ドットコムで取得したドメインをNetlifyで使う](https://qiita.com/hitochan777/items/7acce5b4398af7f4c358)」を参考にさせていただきました。

SSLについては、Neylify内でLet's Encryptによって設定してくれます。
私の場合、適用されるまでに1日ほどかかったので気長に待ちましょう。

あともう一つ、元のNetlifyドメインも活きたままなので、独自ドメインにリダイレクトするための設定が必要になります。
Nuxtのstaticディレクトリに`_redirects`ファイルを作成し、以下の内容を設定します。

```
https://[Netlifyドメイン]/* https://[独自ドメイン」/:splat 301!
```

あとはデプロイするだけでOKです。


# 感想

やってみた感想は、**簡単とは言えないけど楽!!**って感じです。
実装のために結構あちこち調べました。

少し不便なこともありますが、ちょっとしたブログ程度なら十分ではないかと思います。

せっかくなので頑張って運用していこうと思います。