Nuxt + Contentful + Netlify で静的サイトを作る

# はじめに

Nuxt + Contentful + Netlify という構成で作っているブログが形になってきたので、その知見のメモです。

はじめに断っておきますが、この構成はよくみられますし、記事もたくさんあります。この記事でどれほど有益な知見を共有できるのかはわかりません。しかし、既存の記事では書かれていなかったところで躓いた部分も多かったため、まとめてみることにしました。

## 前提条件

Nuxt Contentful Netlify それぞれの基本的な部分には触れません。詳しい必要はありませんが、ある程度触ったことがある人を想定しています。

# はじめる

## Nuxt のセットアップ

nuxt のプロジェクトをセットアップします。今回は dotenv と (当然ですが) contentful を使うのでそれも入れます。それ以外はお好みでどうぞ。

```
npx create-nuxt-app
```

### dotenv のセットアップ

https://github.com/nuxt-community/dotenv-module

```sh
npm install --save-dev @nuxtjs/dotenv
```

```nuxt-config.js
export default {
  ...
  buildModules: [
    '@nuxtjs/dotenv'
  ],
  ...
}
```

### contentful のセットアップ

```
npm install --save contentful
```

トップレベルにディレクトリを作り、contentful 関連のコードを入れることにします。私は `contents` という名前にしましたが、このあたりはお好みで。JAM Stack の A に相当するという意味か api とする人も見かけます。

plugin にして context に inject する方法はおすすめしません。この場合 contentful の client をコンポーネント内で直接叩くことになり、密結合になりすぎます。最初は問題ないように見えますが、普通のブログ程度の規模でも後々破綻する可能性が高いです。

`.env` にアクセストークン等を書き、それを使って client を作ります。

```/contents/index.js
import * as contentful
export client = contentful.createClient({
  space: process.env.CONTENTFUL_SPACE_ID
  accessToken: process.env.CONTENTFUL_ACCESS_TOKEN
})

```


## モデルを取得して表示する

### contentful から取得する

Contentful に適当な Content model を作り、データを入れておきましょう。

まず、contentful を叩くコードを書きます。クラスを作っておくと扱いやすいです。

```/contents/posts.js
import { client } from '.' 

class Post {
  constructor(v) {
    this.id = v.id
    this.title = v.title
    this.body = v.body
  }
}

export async function fetchPost(id) {
  const entry = client.getEntry(id)
  return new Post({ ...entry.sys, ...entry.fields })
}
```

### ページをつくる

ページを作って asyncData でモデルを取得し、表示します。

```pages/posts/_id.vue
<template>
  <div>
    <h1>{{ post.title }}</h1>
    <div>{{ post.body }}</div>
  </div>
</template>
<script>
export default {
  async asyncData({ params }) {
    return await { post: fetchPost(params.id) }
  }
}
</script>
```

## 動的なルートを生成する

個々のページを作成するために、存在するページの URL 与える必要があります。この設定は nuxt.config.js に書くことになり、ハードコードする必要があります（Router をつかって url を生成する方法があれば教えて下さい）。

このコードは アプリケーションのコードとは実行のされ方が違う(webpack の設定等)ので、本体のコードとはある程度分離させるのがおすすめです。

nuxt.config.js に直接書いても良いですが、設定ファイルに書くにはやや複雑なので、別ファイルに書くことにします。

また、nuxt のアプリケーションコードではないため、dotenv は明示的に設定します。

```/config/generate.js
import * as contentful
import dotenv from 'dotenv'
dotenv.config()

export client = contentful.createClient({
  space: process.env.CONTENTFUL_SPACE_ID
  accessToken: process.env.CONTENTFUL_ACCESS_TOKEN
})



export async function generateRoutes() {
  return await client.getEntries().map((e) => `/posts/${e.sys.id}`)
}
```

```nuxt.config.js
import { generateRoutes } from './config/generate'

export default {
  generate: {
    routes: generateRoutes,
  }
}
```

## デプロイ

### Netlify の設定

まずは普通に nuxt の設定をします。

Settings -> Build & Deploy の Build hoooks から、Contentful で叩くための URL を生成します。

### Contentful の設定

Settings -> Webhooks から Netlify を選択し、上で生成した URL を入力します。

Contentful で新しい content を publish して、Netlify 側でビルドが走れば成功です。

## Tips

本質的にはあまり関係ないものの、躓いたポイントと解決策です。

### クエリを変更したときに内容が更新されない

#### 問題

カテゴリ機能を実装し、 `/posts?category=music` のようにクエリ文字列で指定できるようにしました。nuxt-link でカテゴリへのリンクを張っており、以下のようなコードで取得しているとします。

```js
export default {
  async asyncData({ query }) {
    await fetchPosts({ category: query.category })
  }
}
```

このとき、カテゴリへのリンクをクリックすると、URL は変わりますが表示内容が更新されません。

#### 解決策

asyncData はページがロードされたときに呼び出されるもので、 query が変更されただけでは呼び出されません。watchQuery を明示的に指定することで query の変更に対して呼び出すことが出来ます。

```js
export default {
  watchQuery: ['category'],
}
```