Nuxt.js + Jamstack 構成において、動的コンテンツを処理する方法

# はじめに

とあるホームページを現在、作成中です

WordPress といった CMS を利用すると楽に作れますが、ホームページに動的コンテンツがほとんどないので、サーバーレスで静的な HTML をビルドする方法で開発中です
いわゆる Jamstack な構成です

以下のような技術を使っています

- Nuxt.js（`target: 'static'`）
  - 静的 HTML をエクスポート
- Vercel
  - ホスティングサービス
- microCMS
  - ヘッドレス CMS

今作成中のホームページでは、一部のページで定期的に更新が発生するので、その動的コンテンツをアプリケーションのビルド時に処理して、コンポーネントで利用できる所までを実装してみます

# 構成

簡単な全体構成です
以下の「ビルド＆デプロイ」で microCMS の API からコンテンツを取得する部分をビルド時に処理して、静的な HTML を生成します

<img width="637" alt=" 2020-08-07 9.59.26.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/168197/f84d7947-591f-1a42-b250-09f63c2ded44.png">

# ビルド時に動的コンテンツを処理

`nuxt build`をフックして、ビルド前に API からデータを取得し、JSON ファイルに書き出しています

他の方法として、`nuxt export`をフックして、コンテキストの payload にデータをセットする方法もあります（後述）
しかし、普段の開発は HMR が使える`npm run dev`で行いたいので、export をフックするやり方はやめました

あまりスマートなやり方とは言えませんが、API から取得したデータの中身を確認したい時にビルドだけすれば確認できるので、まあこれでもいいかなって思ってます

```js:nuxt.config.js
import axios from 'axios'
import fse from 'fs-extra'

const nuxtConfig = {
  hooks: {
    build: {
      async before() {
        const example = await axios
          .get(
            'https://[microCMSのプロジェクト名].microcms.io/api/v1/example',
            { headers: { 'X-API-KEY': process.env.MICROCMS_API_KEY } }
          )
          .then(res => res.data.contents)
          .catch(err => console.error(`microCMS get error: ${err.message}`))

        fse
          .outputJson('./microcms/example.json', { example })
          .catch(err => console.error(`json write error: ${err.message}`))
      }
    }
  }
}
```

microCMS では、`X-API-KEY`をリクエストヘッダーにセットして送信します
よって、アプリケーションのインタラクションの中で API リクエストを行うと、ユーザーにキーが見えてしまいます
これは GET リクエストのみで有効な認証キーなのですが、できれば隠蔽したほうがよいでしょう

サーバーがあれば、サーバーでリクエストするようにすればよいですが、サーバーレスだとビルド時に API リクエストを行うことで隠蔽できます

## payload を使う方法

上記では API で取得したデータを JSON に書き出すというやり方を取りましたが、他にも良さそうな方法はあります
`asyncData`が使えるページコンポーネントでデータを使いたいのであれば、以下のやり方で良いと思います

今回は Composition API で実装しており、`asyncData`が上手く動くか分からなかったので、試していません

### setPayload を使って、payload をセットする

hooks.export.before が持つ`setPayload`関数でデータをコンテキストに保存し、ページコンポーネントの`asyncData`で取得する
https://github.com/nuxt/nuxt.js/pull/7422

Nuxt の v2.13.0 から使える機能です

### 動的ルーティングで、payload をセットする

generate.routes で payload に値をセットして返す
https://ja.nuxtjs.org/api/configuration-generate/#payload-%E3%81%AB%E3%82%88%E3%82%8B%E5%8B%95%E7%9A%84%E3%83%AB%E3%83%BC%E3%83%86%E3%82%A3%E3%83%B3%E3%82%B0%E7%94%9F%E6%88%90%E3%81%AE%E9%AB%98%E9%80%9F%E5%8C%96

# Nuxt のコンテキストに API データをセット

以下の実装では JSON を動的に import して、コンテキストに inject しています
ここは、Vuex で Store に保存したり、普通にページコンポーネントで取得して管理するでも良いです

```ts:plugins/example.ts
import { Plugin } from '@nuxt/types'

declare module '@nuxt/types' {
  interface Context {
    $example: {}[]
  }
}

const ExamplePlugin: Plugin = async (_, inject) => {
  const example = await import('@/microcms/example.json')
    .then(res => res.example)
    .catch((err: Error) => {
      console.error(`json import error: ${err.message}`)
      return []
    })

  inject('example', example)
}

export default ExamplePlugin
```

ちなみに JSON を扱う場合は、tsconfig に`"resolveJsonModule": true`が必須ですね

# API データをコンテキストから取得して使う

`useContext`から取得すれば、前述の`plugins/example.ts`で定義した型推論が効きます

```ts:pages/index.vue
import { defineComponent, computed, useContext } from 'nuxt-composition-api'

export default defineComponent({
  setup() {
    const example = computed(() => useContext().$example)

    return { example }
  }
})
```