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


はじめに

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

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

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

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

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

構成

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

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

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

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

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

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 に保存したり、普通にページコンポーネントで取得して管理するでも良いです

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で定義した型推論が効きます

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

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

    return { example }
  }
})