【前編】GridsomeとContentfulを使って、簡単に更新できる静的LPページを作ってみる

diffeasyアドベントカレンダー16日目の記事です。


最近開発しているアプリケーションのLPサイトを作るのに、お知らせ機能を作りたいという話がでていました。
ただ、お知らせ機能を追加するにあたって、

- LPサイトは静的で書き出したい（SEO対策とかで）
- アプリケーションAPI側にトピックスやブログコンテンツを持ちたくない
- だからといって、Wordpressはサーバー運用だったりバージョンアップデート等で今後の辛みもあるから個人的に使いたくない

ということで、静的サイトジェネレーター＆Headless CMSを使ってみました！

## やっていくこと

1. フロントをGridsomeで作る
2. Contentfulでお知らせ内容を書く
3. Github Actionsを使って更新を検知し、Firebase Hostingに自動デプロイする

です！

ホスティング先が、なぜよくHeadless CMSを使うときによく勧められてる[Netlify](https://www.netlify.com/)ではなくFirebase Hositngかというと、単に自分が使い慣れているというのと、あんまりFirebase Hostingで使ってるって記事を見かけなかったので使ってみました。

また、書いている途中で膨大な量になりそうだなと思ったので【前編】と【後編】に分けることにしました！

- 前編ではContentfulとGridsomeを使ってローカルで動きを確認するところまで
- 後編ではFirebase Hostingにデプロイ&Github Actionsでのデプロイの設定

を書いていこうと思います💪


## Gridsomeとは

[Gridsome](https://gridsome.org/)はVue.js製の静的サイトジェネレーターで、

- いろんなCMSと繋ぐためのプラグインがある
- GraphQLを使ってデータ成型できる
- Vue製なので、Vueに慣れてたら結構簡単に扱える

ことができます。

ちなみに、React製の静的サイトジェネレーターに[Gatsby.js](https://www.gatsbyjs.org/)というのがあるのですが、GridsomeはGatsby.jsを参考にして作られたそうです。

また、弊社ではフロントエンドにNuxt.jsを採用しているので、Generateしたら静的吐き出しできるし、これでいいじゃんって思うかもしれないですが、新しいものを使いたかったっていうのと、データをGraph QLで整形できるのめちゃんこ便利じゃんって思ったので、今回使ってみました。


## Contentful

[Contentful](https://www.contentful.com/)はHeadless CMSの話題が出てきたときから結構有名どころ（だと思ってる）のCMSです。
英語版しかないですが、一回設定したら基本はコンテンツを追加するだけなのでそんなに困らないかなと思います（英語の勉強にもなるし）。

英語慣れしていない人でも更新できるよう、日本製Headless CMSの[microCMS](https://microcms.io/)とも迷ったのですが、WebhookがmicroCMSだとSlack or Netlifyに対して、ContentfulだとTravisCIやCircleCIなどにも飛ばせるほか、カスタムWebhookを作成できるので、設定次第で色んなことができそうなContentfulを選びました。


## Contentfulのセットアップ

### Content modelを作成する

Contentfulでは、お知らせページやブログの内容を構成する情報のことを`Content model`と言います。
まずはContentfulにログインし、Content modelタブに移動します。

![スクリーンショット 2019-12-15 17.32.12.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/222222/f37e19e3-6f53-3034-ca1d-f8d89d2e4f2d.png)

次に、「Add content type」を選択し、Content modelを作っていきます。
ここで注意すべき点は、下の画像のように、名前は英語で入力してください。
Gridsome側のプラグインで読み込む際、Nameが日本語だと読み込めません。

![スクリーンショット 2019-12-15 17.33.17.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/222222/55bcac2a-d291-e5b6-c05a-2001222b2595.png)

次に、サイドバーにある「Add field」を選択し、お知らせページを構成する要素を追加していきます。
内容（Articles）については、`Rich Text` or `Text`が選択できると思いますが、僕は`Text`の`Long text`を選択することをおすすめします。
理由は

1. Long textだとMarkdownで書けるから
2. Rich Textだとコードブロックが置けない？（自分が分かってないだけかも…）
3. Rich Textは公式が[@contentful/rich-text-html-renderer](https://www.npmjs.com/package/@contentful/rich-text-html-renderer)を提供しているけど、コードをまとめれなかったり（一行ずつに分割される）、Vueだと拡張が結構大変に感じた

からです。
Rich Textも全然悪くはないと思うし、Wordpressで慣れてる人とかはこっちのほうがいいって人もいるかもなので、今後のアップデートには期待しています🎉

![スクリーンショット 2019-12-15 17.42.23.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/222222/89aaa1e5-7725-fc22-cb3d-e33e90940680.png)

設定し終わったら、右上のsaveを押して保存してください。


### 投稿する

次に、お知らせの投稿を1つしておきましょう。
Content modelタブの右横にあるContentタブから投稿を追加できます。
内容を書き終わったら、サイドバーにあるPublishボタンを押して、公開状態にしましょう！

![スクリーンショット 2019-12-15 23.02.58.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/222222/058a6104-3322-9bc2-1ede-6d714fcce839.png)


### APIキーを作成する

最後に、Gridsome側でコンテンツを取得する際に必要となる`APIキー`を作成します。
SettingsタブからAPI keysを選択し、次の画面でAdd API keyを選択します。

![スクリーンショット 2019-12-15 23.06.11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/222222/8de62e4a-081a-15b7-669d-c4abded2798b.png)

ここのNameは好きな名前をつけてください。
必要になるのは、`Space ID`と`Contentful Delivery API - access token`の2つです。
次にやるGridsomeの設定で使うので、画面をそのままにするか、どこかにコピーしておいてください。

![スクリーンショット 2019-12-15 23.08.18.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/222222/c4364a6c-f3f2-13dd-acaf-1de2086461e6.png)



## Gridsomeのセットアップ

### 必要なものをインストールする

```bash
$ npm i -g @gridsome/cli
$ gridsome create lp-site && cd $_
$ npm i @gridsome/source-contentful markdown-it
```


### Contentful pluginの設定

プロジェクトルートにある`gridsome.config.js`を編集します。
[公式に載っているものそのままですが](https://gridsome.org/plugins/@gridsome/source-contentful)、`YOUR_SPACE`の部分にさっき出した`Space ID`を、`YOUR_ACCESS_TOKEN`の部分に`access_token`を置きます。
しかし、そのまま置くと環境ごとに分けられなかったり、Githubのpublicリポジトリなんかにpushしたときには全世界の人に見られたりするので`env`ファイルに書いて読み込ませたりしたいと思います。

幸いなことに、Gridsomeでは[dotenvを使って対応しています](https://gridsome.org/docs/environment-variables/#environment-variables)。
また、Gridsomeでは`.env.production`または`.env.development`をルートディレクトリに配置することで、環境別の変数を用意できます。最高ですね！

```js:gridsome.config.js
module.exports = {
  plugins: [
    {
      use: '@gridsome/source-contentful',
      options: {
        space: process.env.CONTENTFUL_SPACE_ID,
        accessToken: process.env.CONTENTFUL_ACCESS_TOKEN,
        host: 'cdn.contentful.com',
        environment: 'master',
        typeName: 'Contentful'
      }
    }
  ]
}
```

```env:.env
CONTENTFUL_SPACE_ID=YOUR_SPACE
CONTENTFUL_ACCESS_TOKEN=YOUR_ACCESS_TOKEN
```

### GraphQL explorerでデータ構造をチェックする

Gridsomeをdevelopで起動すると、GraphQL explorerを開けるようになります。
これを使えば、Gridsomeを起動した時点でContentfulから情報を取得してきて、その構造を確認することができます。

表示方法は、`npm run develop`コマンドを打つと下のような表示が出てくるので、ブラウザで`http://localhost:8080/___explore`を開くだけです！

```bash
$ npm run develop

  Site running at:                                         
  - Local:                 http://localhost:8080/          
  - Network:               http://192.168.1.86:8080/       
                                                           
  Explore GraphQL data at: http://localhost:8080/___explore
```

するとこんな画面が開かれると思います

![スクリーンショット 2019-12-16 1.17.20.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/222222/3c5e10f5-95b1-adaa-8c24-7974ee8d27bd.png)

これは、GraphQLのクエリを試しに打ってみて実行して確認ができる画面になります。
使えるクエリを見るには、右側のDOCSタブをクリックしてみるといいです。

![スクリーンショット 2019-12-16 1.22.37.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/222222/451dc35d-5b18-ca44-7682-aaf921885976.png)


実行できるクエリたちが並んでいるので、それを左側の画面に入力して再生ボタンをクリックすれば動かすことができます！
このエディタ上だと入力補完が効くので、すごく便利です✨

試しに、一覧情報を取得するクエリを書いてみます。
コードは上画像の構造を元に書いていくので、分からなかったら照らし合わせてみてください！

```graphql
# queryには自由な名前を定義できます
#（主に再生ボタンを押したときに、どのクエリを実行するかに使用したりします）
query getAllTopics {
  allContentfulTopics {
    edges {
      node {
        id
        title
        article
        createdAt
        updatedAt
      }
    }
  }
}
```

書けたら、真ん中の再生ボタンを押して、実行してみてください。
Contentfulの設定のときに書いた内容が取得できていると思います！（こうなれば成功です）

![スクリーンショット 2019-12-16 1.25.48.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/222222/5af8e44e-7373-eed1-810f-132d7677f149.png)


### まずは一覧ページを作ってみる

Contentfulから情報を取得できていることが確認できたので、ページを作っていきましょう。
ページの作り方は、`Nuxt.js`を使っている人は親近感が湧く作り方になると思いますが、`pages`ディレクトリ内に作っていきます。

まずは、お知らせページの一覧画面を作ってみましょう！

```bash
$ touch src/pages/Topics.vue
```

```vue:src/pages/Topics.vue
<template>
  <Layout>
    <h1>Topics</h1>
    <ul>
      <li v-for="topics in $page.allTopics.edges" :key="topics.node.id">
        <g-link :to="`/topics/${topics.node.id}`">{{ topics.node.title }}</g-link>
      </li>
    </ul>
  </Layout>
</template>

<page-query>
query getAllTopics {
  allTopics: allContentfulTopics {
    edges {
      node {
        id
        title
      }
    }
  }
}
</page-query>
```

ちょっと独特な書き方をしていますが、分解すると

- `src/main.js`に書いてありますが、`Layout`コンポーネントがグローバルコンポーネントとして登録されています。これは全体的なレイアウトを統一するために存在しています
- GraphQLクエリは`<page-query>`タグで囲って記述します。この中に先程書いたクエリを転記します
- `allContentfulTopics`クエリの前に`allTopics`と書いてありますが、これはコンポーネント内でクエリの実行結果を使うときの名前です。つけない場合は`allContentfulTopics`の名前で取り扱います
- `page-query`での実行結果をコンポーネント内で使うときは`this.$page`を使います。ここに実行結果が入ってきます

という感じになります。


### 詳細ページを作ってみる

次に詳細ページを作っていきましょう。
詳細ページを作るときはカスタムルートを作る必要があるので、pagesディレクトリにファイルを置くだけでは作れません。
カスタムルートの作り方は

1. `gridsome.config.js`にカスタムルートを作成する
2. `src/templates`に、設定したカスタムルートに訪問した時表示するコンポーネントを作成する

という流れになります。

まずは`gridsome.config.js`に下を追記します。

```js:gridsome.config.js
module.exports = {
  plugins: [
    ...
  ],
  // ここから追記
  templates: {
    // ここは "Contentful + Content model名" になる
    ContentfulTopics: [
      {
        path: '/topics/:id',
        component: './src/templates/TheTopics.vue',
      },
    ],
  },
}
```

次に、`src/templates`に`TheTopics.vue`を作り、追記します。

```vue:TheTopics.vue
<template>
  <Layout>
    <h1>{{ $page.topics.title }}</h1>
    <main>
      <div v-html="article"></div>
    </main>
  </Layout>
</template>

<page-query>
  query topics($id: ID!) {
    topics: contentfulTopics(id: $id) {
      title
      article
    }
  }
</page-query>

<script>
import markdownIt from 'markdown-it'
export default {
  computed: {
    article() {
      const md = new markdownIt()
      return md.render(this.$page.topics.article)
    }
  }
}
</script>
```

ここでは以下の動きをしています

- queryの`$id`でカスタムルートに指定した`/:id`の値を取得し、ID型で定義します。それを使って`contentfulTopics(id: $id)`でコンテンツの指定を行います。
- articleはMarkdownを使っているので、Markdown parser（今回は`markdown-it`を使っています）を使ってHTMLに変換し、`v-html`に流しています

ここで`npm run develop`をして`/topics`からリンクを踏めば、詳細画面が開けることが確認できると思います！やったね！

このようにして、フロント側を作っていきます。
もっと詳しいことを学習されたい方は、ぜひ[公式ドキュメント](https://gridsome.org/docs/)を！
少し前と比べたらドキュメント量も増えてて、デザインも新しくなって見やすくなったので、一通りわかればサクサク進められると思います✨


前編はここまでです。最後まで見てくださり、ありがとうございます！


## 前編の終わりに

いかがでしたか？
ちょっと癖がありますが、慣れたら結構簡単に画面を作っていけるので、個人的にはGridsomeおすすめです！

後編はこちらです！よかったら見てください😆
[【後編】GridsomeとContentfulを使って、簡単に更新できる静的LPページを作ってみる](https://qiita.com/tk07Sky/items/528658213e62aad0a055)