Nuxt.js + Nuxt Compostion API + TypeScriptでmicroCMSを利用してSSGをする

November 1, 2020,
tags: nuxt vue microcms typescript


このエントリーをはてなブックマークに追加

jamstackが流行り始めている最近ですが、流行りに乗っかるためにNuxt.js + Nuxt CompostionAPI + TypeScriptとう環境下でmicroCMSを利用してSSGしてみた。

特に頑張るところはないんですが、TypeScriptが初心者でちょっと苦労した・・・。

サンプル

今回のサンプルはgithub上にあります。
https://github.com/polidog/nuxt-microcms/tree/compostion

まずはNuxtのプロジェクトを用意する

npx create-nuxt-app nuxt-microcms

必要となるnpmパッケージをいれておく

追加で必要になるものは以下の通り

  • @nuxt/http
  • @nuxtjs/dotenv
  • @nuxtjs/composition-api
$ yarn add --dev @nuxtjs/dotenv
$ yarn add @nuxt/http @nuxtjs/composition-api

microCMS用のpluginを作成する

APIと通信する方法はいくつかあるが、今回はnuxt/httpを利用する形でmicrocms用のプラグインを用意した。

// plugins/microcms.ts

import { defineNuxtPlugin } from '@nuxtjs/composition-api'

export default defineNuxtPlugin(({ $http, env }, inject) => {
  const $microcms = $http.create({
    headers: { 'X-API-KEY': env.MICROCMS_API_KEY },
  })
  $microcms.setBaseURL(env.MICROCMS_API_URL)
  inject('microcms', $microcms)
})

こんなな感じで$httpから新しいインスタンスを作り、APIをコールするのに必要な情報をセットする。
nuxt/composition-apiの defineNuxtPlugin を使ってpluginを定義している。
ただ、これは使わなくてもいい気がしていて、下記の書き方もできる

const mircoCMS: Plugin = ({ $http, env }, inject) => {
  const $microcms = $http.create({
    headers: { 'X-API-KEY': env.MICROCMS_API_KEY },
  })
  $microcms.setBaseURL(env.MICROCMS_API_URL)

  inject('microcms', $microcms)
}

export default mircoCMS

defineNuxtPluginを使う必要性がわからないんだけど、なにか良いことがあるのか?若干疑問…。

あとはnuxt.config.jsでpluginを登録。

// nuxt.config.js

...
plugins: ['@/plugins/microcms'],
...

microCMS pluginを使う

先程作成したpluginをpageコンポーネントで使ってみる。
今回はmicrocmsでページ専用のobject形式のAPIを用意

{
    "createdAt": "2020-10-31T06:36:42.770Z",
    "updatedAt": "2020-10-31T16:11:02.887Z",
    "publishedAt": "2020-10-31T06:36:42.770Z",
    "name": "パーティー野郎株式会社",
    "message": "<p>ここにテキストが入ります。<br>ここにテキストが入ります。ここにテキストが入ります。ここにテキストが入ります。ここにテキストが入ります。ここにテキストが入ります。<br>ここにテキストが入ります。ここにテキストが入ります。ここにテキストが入ります。ここにテキストが入ります。<br>ここにテキストが入ります。ここにテキストが入ります。ここにテキストが入ります。ここにテキストが入ります。<br>ここにテキストが入ります。ここにテキストが入ります。ここにテキストが入ります。ここにテキストが入ります。</p>",
    "news": [
        {
            "id": "3s-cjdi9s",
            "createdAt": "2020-10-31T06:38:29.486Z",
            "updatedAt": "2020-10-31T16:01:22.376Z",
            "publishedAt": "2020-10-31T06:38:29.486Z",
            "title": "お知らせA",
            "image": {
                "url": "https://images.microcms-assets.io/protected/ap-northeast-1:76b0f3c9-98ad-479c-b4d6-a4eabb4a5307/service/ssr-ssg/media/fff.png"
            },
            "content": "<p>お知らせAお知らせAお知らせAお知らせAお知らせAお知らせAお知らせAお知らせAお知らせAお知らせAお知らせAお知らせAお知らせAお知らせAお知らせAお知らせAお知らせAお知らせAお知らせAお知らせAお知らせA</p>",
            "date": "2020-10-30T15:00:00.000Z"
        },
        {
            "id": "k_xtrtam0",
            "createdAt": "2020-10-31T06:37:32.722Z",
            "updatedAt": "2020-10-31T16:01:31.536Z",
            "publishedAt": "2020-10-31T06:37:32.722Z",
            "title": "お知らせB",
            "image": {
                "url": "https://images.microcms-assets.io/protected/ap-northeast-1:76b0f3c9-98ad-479c-b4d6-a4eabb4a5307/service/ssr-ssg/media/fff.png"
            },
            "content": "<p>お知らせBお知らせBお知らせBお知らせBお知らせBお知らせBお知らせBお知らせBお知らせBお知らせBお知らせBお知らせBお知らせBお知らせBお知らせBお知らせBお知らせBお知らせBお知らせBお知らせBお知らせBお知らせBお知らせB<br><br><br>お知らせBお知らせBお知らせBお知らせBお知らせBお知らせBお知らせBお知らせB</p>",
            "date": "2020-10-30T15:00:00.000Z"
        }
    ]
}

あとは適当にvueファイルを用意する

// pages/index.vue
<template>
  <section v-if="contents" class="top">
    <b-container>
      <h2>{{ contents.name }}</h2>
      <div class="jumbotron jumbotron-fluid">
        <div class="container">
          <div class="lead" v-html="contents.message" />
        </div>
      </div>

      <div class="news">
        <h2>お知らせ</h2>
        <b-row>
          <b-col
            v-for="item in contents.news"
            :key="item.id"
            sm="auto"
            xs="auto"
          >
            <b-card
              :title="item.title"
              :img-src="item.image.url"
              :img-alt="item.title"
              img-top
              tag="article"
              style="max-width: 20rem;"
              class="mb-2"
            >
              <div v-html="item.content" />
              <b-button href="#" variant="primary">詳細</b-button>
            </b-card>
          </b-col>
        </b-row>
      </div>
    </b-container>
  </section>
</template>

<script lang="ts">
import { defineComponent, useContext, useAsync } from '@nuxtjs/composition-api'

export default defineComponent({
  setup() {
    const { $microcms } = useContext()
    const contents = useAsync(() => $microcms.$get('/pages-top'))
    return {
      contents,
    }
  },
})
</script>

<style scoped>
.top {
  margin-top: 30px;
}
</style>

このまま yarn dev して動かすと、APIから情報を取得できる

defineComponentとかuseContextとか

nuxt/compostion-apiを使っているので、defineComponent使ってsetupメソッド定義して、そのなかでAPIからデータを取得する処理をしている。

Property ‘$microcms’ does not exist on type ‘UseContextReturn’の問題

ブラウザで実行するとうまく表示されているが、エラーが発生してた

TS2339: Property '$microcms' does not exist on type 'UseContextReturn'.
    42 | export default defineComponent({
    43 |   setup() {
  > 44 |     const { $microcms } = useContext()
       |             ^^^^^^^^^
    45 |     const contents = useAsync(() => $microcms.$get('/pages-top'))
    46 |     return {
    47 |       contents,

UseContextReturnでは$microcms の定義がないのでエラーになっているみたい。
UseContextReturnとはなんぞや?ということで型定義をみてみる

interface UseContextReturn extends Omit<Context, 'route' | 'query' | 'from' | 'params'> {
    route: Ref<Route>;
    query: Ref<Route['query']>;
    from: Ref<Route['redirectedFrom']>;
    params: Ref<Route['params']>;
}

てか、$http とかどうなっているんだろうとnuxt/httpのコードを読んでたら以下のようなコードをみつけた

declare module '@nuxt/types' {
  interface Context {
    $http: NuxtHTTPInstance
  }
}

https://github.com/nuxt/http/blob/5b3de263eb772c54d79362010060eb87212388e3/types/index.d.ts#L162-L166

とりあえず @/plugins/micorcmsに記述してみた

import { defineNuxtPlugin } from '@nuxtjs/composition-api'
import { NuxtHTTPInstance } from '@nuxt/http'

export default defineNuxtPlugin(({ $http, env }, inject) => {
  const $microcms = $http.create({
    headers: { 'X-API-KEY': env.MICROCMS_API_KEY },
  })
  $microcms.setBaseURL(env.MICROCMS_API_URL)
  inject('microcms', $microcms)
})

declare module '@nuxt/types' {
  interface Context {
    $microcms: NuxtHTTPInstance
  }
}

これで解決した。

asyncDataのような挙動を期待してたのに・・・

setupメソッドの中でuseAsync使うと非同期処理になるので、CSRしたときにレンダリング時にAPIからデータ取得が終わっているわけじゃないので、nullの可能性がある。。。
なので v-ifで判定して上げる必要があるよね。。。asyncDataのほうが使いやすかった感が・・・

SSGする

基本的には yarn generateすればコンテンツが生成されるはず。
あとは確認するために yarn startして localhost:3000にブラウザにアクセスして確認すればいい。

ssr: false問題

なぜかうまく行かずCSRになってしまうことがあった。 nuxtでSSGしたい場合はnuxt.conf.jpでssr:false にしてはいけない。 これをするとgenerateしてもCSRとして動いてしまう。

まとめ

  • plugins + nuxt/httpを使ってmicroCMSのpluginを用意した
  • useContextを使ってPage componentから$microcms を使ってapiにリクエストを送った
  • asyncDataと違うのでv-if書いておいたほうがいい。

最後に

次はSSG + SSR + Firebaseの組み合わせてみた話を書こうと思います。

comments powered by Disqus