Noob Front End Engineer Blog

CONTACT

Nuxt.jsでAPIで取得したデータごとにページを作成する

2019/11/14

概要

API で取得してきたデータ毎にページを作成したい!と思い色々試行錯誤した内容をまとめました。 API 作成に使用したのツールはMicro CMSです。別記事で使用方法を書いてあります。 Nuxt で最近話題の MicroCMS を使用して Firebase で公開する

※コードの説明は基本コメントで行ってます。

まずは公式リファレンスを読もう

Nuxt.js 動的ルーティングについて公式リファレンスで記載があるので、まずはコチラを読みましょう。

なるほどわからん:thinking:

もう少し調べると素敵な記事を見つけました。

どうやら nuxt.config.js をいじるっぽいぞ:thinking:

という事で再度公式リファレンスを確認。 ちょっとずつ分かってきたぞ。ではやってみよう。

まずは API の作成

MicroCMS を使用する場合であればNuxt で最近話題の MicroCMS を使用して Firebase で公開する の記事の方を参考に。 実際に送られてくるデータの例としては下記の様に作成しました。 作成するサイトはお友達紹介ページです。

{
  "contents": [
    {
      "id": "自動登録",
      "createdAt": "自動登録",
      "updatedAt": "自動登録",
      "dir": "friends01",
      "title": "お友達01",
      "mainv": {
        "url": "画像URL"
      },
      "body": "<p>仲良しこよしのお友達<br>お友達登録No.001</p>"
    },
    {
      "id": "自動登録",
      "createdAt": "自動登録",
      "updatedAt": "自動登録",
      "dir": "friends02",
      "title": "お友達02",
      "mainv": {
        "url": "画像URL"
      },
      "body": "<p>仲良しこよしのお友達<br>お友達登録No.002</p>"
    }
  ]
}

使用するのはdir,title,mainv,bodyの 4 つです。

nuxt.config.js の編集

// axiosをimport (※importせず$を使用した方法でも可
import axios from "axios"

export default {
  mode: "spa",
  // ~
  // 中略
  // ~
  generate: {
    routes() {
      // 使用するAPIから情報を取得
      return axios
        .get("リクエストURL", {
          headers: { "X-API-KEY": "APIキー" },
        })
        .then(res => {
          return res.data.contents.map(friends => {
            return {
              route: friends.dir, // ページのurlになる部分。今回はAPI上で設定した情報を使用。
              payload: friends, // ページ毎に疎通を行わずに済むようpayloadに情報を渡す
            }
          })
        })
    },
  },
}

Store の作成

API で取得してきたデータをstateとして管理します。

export const state = () => ({
  ApiFlag: false, //APIデータ取得を行ったか否か
  friend: "", //APIで取得してきたデータを格納
})

export const mutations = {
  // API疎通に成功した際にFlagを立てる
  FlagChange(state) {
    if (state.ApiFlag === false) {
      state.ApiFlag = true
    }
  },

  // APIで取得してきたデータを格納する
  getFriends(state, res) {
    state.friend = res
  },
}

ページ毎に API 疎通を行わない様に、ApiFlagを設置しました。

これについてはpayloadで解決しそうなものなのですが、 うまく動作しなかったのでこの仕様にしました :cry:

テンプレート用の vue ファイルの作成

動的に作成されるページのテンプレートファイルを作成します。 アンダースコアのプレフィックスを付けた vue ファイルを作成する事で定義できます。 私の場合は_friends.vueという名前でテンプレートを作成しました。

JS 部分

<script>
import axios from 'axios'

export default {
  // store
  computed: {
    // stateの情報を取得
    ApiFlag () { return this.$store.state.friends.ApiFlag },
    friends () { return this.$store.state.friends.friend }
  },
  data () {
    return {
      // APIで取得してきたデータ群
      dir: '',    // ページURL
      title: '',  // お友達の名前
      mainv: '',  // お友達のイメージ画像
      body: ''    // お友達の紹介文
    }
  },
  async asyncData ({ store, params, error, payload }) {
    // payloadでデータを受け取った場合
    if (payload) {
      return {
        dir: payload.dir,
        title: payload.title,
        mainv: payload.mainv,
        body: payload.body
      }
    } else {
      // payloadでデータを取得できなかった場合
      let Dir
      let Ftitle
      let Fmainv
      let Fbody

      // API通信を行っていなかった場合
      if (store.state.friends.ApiFlag === false) {
        await axios.get('リクエストURL', {
          headers: { 'X-API-KEY': 'APIキー' }
        })
          .then((res) => {
            // Flagを立てて、情報をstateに格納
            this.$store.commit('friends/FlagChange')
            this.$store.commit('friends/getFriends', res.data.contents)
            // APIで取得してきたデータを格納    
            res.data.contents.map((friend) => {
              if (friend.dir === params.friends) {
                Dir = friend.dir
                Ftitle = friend.title
                Fmainv = friend.mainv
                Fbody = friend.body
              }
            })
          })

      // API通信済みの場合
      } else {
        // storeのデータを回して、現在のページのお友達情報を格納
        store.state.friends.friend.map((friend) => {
          if (friend.dir === params.friends) {
            Dir = friend.dir
            Ftitle = friend.title
            Fmainv = friend.mainv
            Fbody = friend.body
          }
        })
      }

      return {
        dir: Dir,
        title: Ftitle,
        mainv: Fmainv,
        body: Fbody
      }
    }
  },
  methods: {
    // ページ遷移関数
    jump (e) {
      location.href = e.target.value
    }
  }
}
</script>

HTML 部分

<template>
  <div class="container">
    <!-- 選択でページ遷移 -->
    <select class="links" @change="jump">
      <option hidden>おともだち を えらぼう!</option>
      <option v-for="friend of friends" :key="friend.id" :value="`./${friend.dir}`">{{ friend.title }}</option>
    </select>

    <div class="card">
      <div class="contents">
        <h1 class="title">
          <!-- dataからお友達の名前を取得 -->
          {{ title }}
        </h1>
        <div class="mainv">
          <img :src="mainv.url" alt="メイン画像">
        </div>
        <div class="contents">
          <h2>プロフィール</h2>
          <span v-html="body" />
        </div>
      </div>
    </div>
  </div>
</template>

select 部分

API で取得してきたデータを v-for で回してoptionを作成しています。 :valueの書き方が特殊なので注意してください。

card 部分

titile ⇒ 通常の{{ }}を使用して<h1>に適用 mainvv-bindを使用したsrc<img>に適用 body ⇒ 取得してきた html タグをそのまま使用するため、v-htmlで適用

これで API で取得してきたデータ分だけページが作成されるはずです!

まとめ

動的ルーティングは HeadlessCMS を使ったブログページ作成などで重宝しそうです。 ただ、API を使用したブログページが SEO や表示速度の面でどうなのか気になる所です。 HeadlessCMS の使用箇所としてはブログなどの情報量が多いものではなく、ページの一部のみ 簡単に変更したい場合などに使うようにするくらいが良いのだろうか…難しいところです。


Written by daichi iwamoto