Nuxt.js + GraphCMS + Netlifyでポートフォリオサイトを作成する

この記事は[Vue.js #2 Advent Calendar 2018](https://qiita.com/advent-calendar/2018/vuejs-2)の22日目の記事です。

# 前書き
- 自前でポートフォリオサイトを持ちたかった
- vueとnuxtに興味があった
- GraphQLに興味があった
- ヘッドレスCMSにグッときていた
- REST APIではなくGraphQLを使っているGraphCMSなんてものがあるのか。使おう
- Netlifyでデプロイして、公開までやりたいよね

というわけで、Nuxt + GraphCMS + NetlifyでWebサイトを作ってみようと思います。

この記事で一番伝えたいのは、GraphCMSで作成したコンテンツをNuxtで取得する部分です。

# 1. GraphCMSでModel作成
こちらの記事（[GraphCMS事始め](https://qiita.com/ryo2132/items/ff847079d6927f263f73)）で登録から作成まで丁寧に解説されています。

## 1-1. アカウント登録
書いてる通りに進めるだけなのでめちゃくちゃ簡単です。

プロジェクト作成時にリージョンを選択しますが、東京リージョンが用意されているのがイケてますね。

## 1-2. PostModelの作成
ブログといったらとりあえずタイトルと本文があればいいでしょ、ということで以下の内容でModelを作成。
<img src="https://qiita-image-store.s3.amazonaws.com/0/73516/294c0866-6e59-b035-0603-12bee7b36509.png" width=50%>

```
# モデルの構成
displayName： post
api key： Post

# フィールドの構成
title: single line text / required
content: mark down / required
thumbnail: asset picker
```

## 1-3. 記事の作成
登録が完了したら、早速取得テスト用の記事を作成。

<img src="https://qiita-image-store.s3.amazonaws.com/0/73516/554b93e4-53cb-8531-192f-4b76f0740c39.png" width=50%>
日本語も問題なく表示されるし、markdownプレビューが付いているUIがいい感じです。

# 2. nuxtでサイト作成
以下の要件でサイトを作成します。

- ホームとブログページがある
- ブログページを開くと、GraphCMSからデータを取得し表示する

## 2-1. プロジェクト作成
`yarn create nuxt-app` でサクッと作っていきましょう。Nuxtは本当にここが楽ですね。

```sh
yarn create nuxt-app my-portfolio
```

プロジェクトの設定について色々聞かれますが、以下のような設定を今回は使用。

```sh
? Use a custom server framework none
? Use a custom UI framework bulma
? Choose rendering mode Universal
? Use axios module no 
? Use eslint no
? Use prettier no
? Choose a package manager yarn
```

## 2-2. ガワの作成
プロジェクトができたら、最低限ポートフォリオっぽい見た目にしていきましょう。

```vuejs:layouts/default.vue
<template>
  <div>
    <section class="hero is-primary is-medium">
      <!-- Hero head: will stick at the top -->
      <div class="hero-head">
        <nav class="navbar">
          <div class="container">
            <div id="navbarMenuHeroA" class="navbar-menu">
              <div class="navbar-start">
                <nuxt-link active-class="is-active" to="/" class="navbar-item" exact>Home</nuxt-link>
                <nuxt-link active-class="is-active" to="/blog" class="navbar-item" exact>Blog</nuxt-link>
              </div>
            </div>
          </div>
        </nav>
      </div>

      <!-- Hero content: will be in the middle -->
      <div class="hero-body">
        <div class="container has-text-centered">
          <h1 class="title">
            My Portfolio
          </h1>
          <h2 class="subtitle">
            Nuxt + GraphCMS + Netlify Sample
          </h2>
        </div>
      </div>
    </section>

    <!-- displays the page component -->
    <nuxt/>
  </div>
</template>

<style>
  .main-content {
    margin: 30px 0;
  }
</style>
```

```vuejs:pages/index.vue
<template>
  <div>
    <section class="main-content">
      <div class="container">
        <h1 class="title">
          Home
        </h1>
        <div class="content">
          <p>my awesome profile</p>
        </div>
      </div>
    </section>
  </div>
</template>

<script>
  export default {
    head: {
      title: 'Home'
    }
  }
</script>
```

```vuejs:pages/blog.vue
<template>
  <div>
    <section class="main-content">
      <div class="container">
        <h1 class="title">
          Blog
        </h1>
        <div class="content">
          <p>my awesome blog</p>
        </div>
      </div>
    </section>
  </div>
</template>

<script>
  export default {
    head: {
      title: 'Blog'
    }
  }
</script>
```

ざっくり以下のような見た目に。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/73516/fc969f5a-db40-ffca-a9be-285288c0fee5.png)

# 3. GraphCMSからコンテンツを取得　
次はBlogタブを開いたら、GraphCMSから取得したテスト用の記事を表示できるようにします。

## 3-1. apollo-moduleの導入
NuxtでGraphQLを扱うには[apollo-module](https://github.com/nuxt-community/apollo-module)を使います。

ひとまずサクッと`yarn add`でモジュールを導入するのですが、こちらの記事（[Nuxt.jsでGraphQLを扱う方法](https://qiita.com/takanorip/items/d1e8618800d951780f4b)）に書かれているように、モジュール単体では足りないライブラリがあるのでそちらも導入。

また、クエリを外出しで管理したいので`graphql-tag`も導入します。 

その後、`nuxt.config.js`にapollo-moduleを使用する旨を記述。

```shell
yarn add @nuxtjs/apollo
yarn add apollo-link-http graphql
yarn add graphql-tag
```

```js:nuxt.config.js
・・・

  // modulesに先ほど導入したapolloを追加
  modules: [
    '@nuxtjs/bulma',
    '@nuxtjs/apollo'
  ],

・・・

  // apolloの設定ファイルのパス
  apollo: {
    clientConfigs: {
      default: '~/apollo/client-configs/default.js'
    }
  }
```

`apollo/client-configs/default.js`に設定を記述していきます。

```js:apollo/client-configs/default.js
import { HttpLink } from 'apollo-link-http'
import { InMemoryCache } from 'apollo-cache-inmemory'

// Replace this with your project's endpoint
const GRAPHCMS_API = "https://hogehoge" // GrahCMSで作成したスキーマのエンドポイント

export default () => ({
  link: new HttpLink({ uri: GRAPHCMS_API }),
  cache: new InMemoryCache()
})
```

GraphCMSで作成したスキーマのエンドポイントは「Settings」->「Endpoints」から確認できます。
<img src="https://qiita-image-store.s3.amazonaws.com/0/73516/06eda840-7001-6ef4-c670-15a3e91e28d8.png" width="50%">

##3-2. クエリ（GQL）の作成
GraphCMSからデータを取得するためのクエリを書きます。今回はシンプルに、postを全件取得するpostsという名前のクエリを作成します。

Model作成の際に`id`や`createdAt`は記述していませんが、GraphCMS側が自動で付与してくれているのでクエリに使用できます。

```:apollo/queries/posts.gql
query {
  posts(orderBy: createdAt_DESC, where: { status: PUBLISHED }) {
    id
    title
    content
    createdAt
    thumbnail {
      url
    }
  }
}
```
クエリは`.gql`という拡張子で保存。今回は`apollo/queries/`ディレクトリに保存。多分importさえできればどこでもいいのですが、ここが一番管理しやすい場所かなと思っています。

GraphCMSの「Api Explorer」で望む内容が返ってくるまで試して確認。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/73516/a1187cb5-9780-09e1-c53f-af60e8ff3f29.png)

## 3-3. 取得するViewの実装
クエリの作成が完了したら、いよいよ投稿内容を取得して表示するViewを作成します。

`pages\blog.vue`を以下のように。

```vue:pages\blog.vue
<template>
  <div>
    <div v-for="post in posts" :key="post.id">
      <section class="section">
        <div class="container">
          <h1 class="title">{{ post.title }}</h1>
          <h2 class="subtitle">{{ post.createdAt }}</h2>
          <div class="content">{{ post.content }}</div>
        </div>
      </section>
    </div>
  </div>
</template>

<script>
import posts from '~/apollo/queries/posts'

export default {
  head: {
    title: 'Blog'
  },
  name: 'posts',
  data () {
    return {
      posts: {}
    }
  },
  apollo: {
    posts: {
      query: posts
    }
  }
}
</script>
```

すると、以下のようにGraphCMSで作成した投稿を取得して表示できています！

![image.png](https://qiita-image-store.s3.amazonaws.com/0/73516/17fe8a71-d1b5-3610-d6ba-cead7558646c.png)

しかし日付はUTCフォーマットだったり、記事本文はMarkdownがそのまま表示されていたり、流石にこのままでは使えたものではないのでMarkdownをパースして表示できるようにしていきましょう。

## 3-4. Markdownのパース
[markdownitのモジュール](https://github.com/nuxt-community/modules/tree/master/packages/markdownit)を利用していきます。

```
yarn add @nuxtjs/markdownit
```

```js:nuxt.config.js
/* ~~略~~ */

  // modulesにmarkdownitを追加
  modules: [,
    // Doc:https://github.com/nuxt-community/modules/tree/master/packages/bulma
    '@nuxtjs/bulma',
    '@nuxtjs/apollo',
    '@nuxtjs/markdownit'
  ],

/* ~~略~~ */

  // markdownitの設定を記述
  markdownit: {
    injected: true, // $mdを利用してmarkdownをhtmlにレンダリングする
  }
```

導入が完了したら、`blog.vue`の`<div class="content">{{ post.content }}</div>`を以下のように変更。

```vue:pages/blog.vue
<div class="content" v-html="$md.render(post.content)"></div>
```

`$md.render`でcontentがHTMLとしてレンダリングされるので、それをv-htmlでHTMLとして利用してあげる、という感じです。確認すると、無事にMarkdownファイルがパースされて表示されています。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/73516/a165de23-6b00-a1a4-dbba-eb9cc00e49a9.png)

## 3-5. 日付をフォーマットする
[date-fns](https://github.com/date-fns/date-fns)を使ってサラッと。

```
yarn add date-fns
```

```vue:pages/blog.vue
<template>
・・・
  <h1 class="title">{{ post.title }}</h1>
  <h2 class="subtitle">{{ dateFormat(post.createdAt, "YYYY/MM/DD hh:mm:ss") }}</h2>
  <div class="content" v-html="$md.render(post.content)"></div>
・・・
</template>

<script>
import posts from '~/apollo/queries/posts'
import format from 'date-fns/format'
import ja from 'date-fns/locale/ja'
import parse from 'date-fns/parse'
・・・
  methods: {
    dateFormat: function(date = new Date(), formatStr) {
      return format(parse(date), formatStr, { locale: ja })
    }
  },
・・・
</script>
```

無事に日付がフォーマットできているのが確認できる。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/73516/6e2bf9b3-bd70-d23b-b1b2-2aa9f20dbac9.png)

# 4. 完成
![image.png](https://qiita-image-store.s3.amazonaws.com/0/73516/b2630906-bb08-2b20-be7d-5a82e5617160.png)

あまりにも簡素ですが、要件を満たしたページを作成することができました。

もう少し見た目の調整とか色々あるんですが、ひとまずここで一区切りにします。

# 5. Netlifyでデプロイ
ここまでのプロジェクトをpushし、「New site from Git」からPushしたリポジトリを選択します。

ビルドの設定はそれぞれ、以下のように指定。

- Bransh to deploy: `master`
- Build command: `npm run generate`
- Publish directory: `dist`

「Deploy site」をクリックすれば、すぐにデプロイが開始されます。

自分の場合は以下のURLが発行され、サイトが公開されました。ちょっと感動ですね。

https://heuristic-visvesvaraya-af1b46.netlify.com/

# 感想
Nuxt + ヘッドレスCMSの組み合わせではContentfulがよく使われていて、その組み合わせの記事は多いのですがGraphCMSを使っている事例があまりなかったのでぜひ紹介したいと思い、この記事を執筆しました。

Nuxtで1からサイトを構築するのは初めてだったのですが、`yarn create nuxt-app`から公開までかなりの速さで実現できて驚きました。Vue/Nuxt、GraphQL、GraphCMS、Netlifyと今自分が興味のある技術を使えてとても充実した時間を過ごせましたね。

今回はポイントになる部分の紹介に留めたので簡素な内容になっていますが、もう少し見た目や内容を充実させ、自分のポートフォリオサイトとして育てていきたいなと思っています。このポートフォリオサイト自体が自分のプロダクトの一つになるのもいいですね。

プロジェクトのリポジトリは以下で公開されています。

https://github.com/ryuhei4k9/my-portfolio

NuxtもVueもとても楽しい！技術を磨いていくぞ！