MicroCMSで取得したデータをGridSomeのGraphQLへインポートして表示する

この記事では、MicroCMSで取得した投稿データをGridSomeのGraphQLへインポートして、表示する方法を解説します。


1.MicroCMSの投稿データを取得

まずはgridsome.server.jsに以下のように投稿データを取得します。
[gridsome.server.js]

// MicroCMSのAPIキー
const API_KEY = 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx'
const axios = require('axios');

// リスト形式の投稿タイプ用に付与するイテレーターの関数
const equipIterator = function(target) {
  target[Symbol.iterator] = function() {
    let i = 0;
    return {
      next() {
        return target.length <= i ? { done: true } : { value: target[i++] };
      }
    }
  }
  return target;
}

// axisoで投稿データを取得する関数、何度も使うので関数化
const getMicroCMSPosts = (URL, API_KEY) => {
  return axios.get(URL, { headers: { 'X-API-KEY': API_KEY } })
  .then(res => { 
  equipIterator(res.data.contents) : false; // 投稿がリストタイプの時はイテレーターを付与して戻す
      if(res.data.contents) {
        return equipIterator(res.data.contents);
      } else {
        return res.data // 投稿がJSON形式の場合はそのまま戻す
    }
  })
  .catch(err => { console.log(err) })
}

const homePosts = getMicroCMSPosts('https://your.microcms.io/api/v1/home', YOUR_API_KEY);
const worksPosts = getMicroCMSPosts('https://your.microcms.io/api/v1/blog', YOUR_API_KEY);
const aboutPosts = getMicroCMSPosts('https://your.microcms.io/api/v1/company', YOUR_API_KEY);
// URLにはあなたのMicroCMSのURLを入力してください



2.取得した投稿データをGraphQLで使用できるようにする

次に、GridSomeのLoadSourceAPIを利用して、投稿データをGraphQLへインポートします。
[gridsome.server.js]


module.exports = function(api) {

// homeポストをGraphQLへインポート(JSON形式)
  api.loadSource(async actions => {
      let post = await homePosts;
      const  collection = actions.addCollection('Home');
        collection.addNode({
          id: post.id,
          title: post.page_title,
          date: post.updatedAt,
          content: post.content,
          introduce: post.introduce,
          hero: post.hero,
          carousel: post.carousel,
          feature: post.feature
        })
    });

// WorksポストをGraphQLへインポート(リスト形式)
  api.loadSource(async actions  => {
      let post = await worksPosts;
      const collection = actions.addCollection('Works');
      for (const item of post) {
        collection.addNode({
          id: item.id,
          title: item.page_title,
          slug: item.permalink,
          date: item.updatedAt,
          content: item.content,
          path: item.permalink // 後のSetup templatesで使用するスキーマ
        })
      }
    })
 }

collection.addNodeの中のitem.idのidの部分はMicroCMSのスキーマの部分です。
これでGraphQLが利用できるようになりました。


3.リスト形式のそれぞれの投稿データの個別ページを静的に書き出す設定を追加

リスト形式は、WordPressでいうPost type(投稿タイプ)のようなものなので、投稿記事それぞれの個別ページを用意する必要があります。記事数が10件程度であれば、手作業でページテンプレートを作成してもいいかもしれませんが、投稿ページをそういう使い方するケースはほとんどないでしょう。
なので、投稿記事が増えても減ってもメンテナンスフリーにするため、リスト形式の投稿データの数だけ、個別ページを静的に書き出す設定を追加します。
GridSomeのSetup Templates機能を利用して、gridsome.config.jsへ設定を追加します。

// リスト形式のWorksでの例
module.exports = {
  siteName: 'サイトの名',
  templates: {
    Works: [
      {
        path: '/works/:path', //Worksの個別ページのそれぞれのURL。パラメータのpathはGraphQLのpathスキーマから
        component: './src/templates/WorksPost.vue' // templateディレクトリのWorksPost.vueを使用(あとで作成)
      }
    ]
  }
}



4.リスト形式の投稿を一覧表示するページを用意する。

ここではGraphQLへインポートしたリスト形式の投稿タイプを一覧表示するためのページを用意します。
この例ではworksという投稿タイプなので、pagesディレクトリ直下にWorks.vueというページを作成します。
[pages/Works.vue]

<template>
  <Layout>
          <article v-for="(item, key) in blog" :key="key">
            <h2>{{ item.node.title }}</h2>
            <time>{{ item.node.date }}</time>
            <div>{{ rawcontent[key] }}</div>
            <div><g-link :to="item.node.path">READ MORE</g-link></div>
    </article>
  </Layout>
</template>

<page-query>
query {
  allWorks {
    edges {
      node {
        id
        title
        slug
        date (format: "YYYY年MM月DD日 HH:mm:ss")
        content
        path
      }
    }
  }
}
</page-query>

<script>
import axios from 'axios'
export default {
  data() {
    return {
      blog: null, // 投稿データをvue側で使用するため
      rawcontent: []
    }
  },
  metaInfo: {
    title: '記事のタイトル',
  },
  methods: {
      putData() {
        this.blog = this.$page.allWorks.edges;
        for(let item in this.blog) { // 記事の概要を表示させるため、投稿のHTMLタグを削除して、rawcontentへ格納
          this.rawcontent.push(this.blog[item].node.content.replace(/<("[^"]*"|'[^']*'|[^'">])*>/g,''));
        }
      }
  },
  mounted() {
    this.putData(); // GraphQLの投稿データをvueへマウント
  }
}
</script>



5.リスト形式の投稿の個別表示するためのテンプレートを作成する

3の手順でやった「リスト形式のそれぞれの投稿データの個別ページを静的に書き出す設定を追加」の表示側テンプレートを作成します。
以下の例ではtemplateディレクトリ直下にWorksPost.vueを作成しています。
[template/WorksPost.vue]

<template>
    <Layout>
        <article>
            <h1>{{ $page.works.title }}</h1>
            <time>{{ $page.works.date }}</time>
            <div v-html="$page.works.content"></div>
        </article>
        <nav>
              <!-- Pager Links -->
              <ul>
                  <li v-for="(item, i) in allPagelinks" :key="i">
                    <g-link :to="item.path">
                      <span v-if="pagercondition.prev & i == 0 | !pagercondition.next & i == 0">Prev</span>
                      {{item.title}}
                      <span v-if="pagercondition.next & i == 1 | !pagercondition.prev & i == 0">Next</span>
                    </g-link>
                  </li>
              </ul>
        </nav>
    </Layout>
</template>

<!-- IDが入ってるQueryが個別記事の現在表示中の投稿データ -->
<page-query>
    query($id: ID!) {
      works(id: $id) {
        id
        title
        slug
        date(format: "YYYY年MM月DD日 HH:mm:ss")
        content
        path
      }
      allWorks {
        edges {
          node {
            id
            title
            slug
            date(format: "YYYY年MM月DD日 HH:mm:ss")
            path
          }
        }
      }
    }
</page-query>
<script>
export default {
  data() {
    return {
      currentpage: null, 
      allpages: null,
      otherpagelink: [],
      pagercondition: {
        prev: false,
        next: false
      },
      rawcontent: null
    }
  },
  metaInfo() {
    return {
      title: this.$page.works.title
    }
  },
  computed: {
    getNowPage() { // 現在のページを取得
      return this.$page.works.path;
    },
    allPagelinks() { // Worksの全ての記事のリンクを取得し、現在表示中の記事以外のリンクを取得する。
      this.otherpagelink = [];
      let self = this;
      let allpage = self.allpages;
      let indexNum = [];

      for (let items in allpage) {
        indexNum.push(allpage[items].node.path)

        // 表示中の記事が最初の記事の場合のリンクを取得
        if(indexNum.indexOf(this.getNowPage) === 0) {
          this.otherpagelink.push(allpage[1].node)
          this.pagercondition.prev = false;
          this.pagercondition.next = true;
          return this.otherpagelink;
        }

        // 表示中の記事が最後の記事の場合のリンクを取得
        if(indexNum.indexOf(this.getNowPage) === allpage.length -1) {
          this.otherpagelink.push(allpage[items -1].node)
          this.pagercondition.prev = true;
          this.pagercondition.next = false;
          return this.otherpagelink;
        }



        // 表示中の記事が最初でも最後でもない記事のリンクを取得
        if(indexNum.indexOf(this.getNowPage) !== 0 && indexNum.indexOf(this.getNowPage) !== allpage.length -1 && indexNum.indexOf(this.getNowPage) !== -1) {
            this.otherpagelink.push(allpage[indexNum.indexOf(this.getNowPage) -1].node);
            this.otherpagelink.push(allpage[indexNum.indexOf(this.getNowPage) +1].node);
            this.pagercondition.prev = true;
            this.pagercondition.next = true;
            // console.log("Middle link condition : ",this.otherpagelink)
            return this.otherpagelink;
        }
      }
    }
  },
  mounted() {
    this.currentpage = '/works/' + this.$route.params.path + '/';
    this.allpages = this.$page.allWorks.edges;
  }
}
</script>



6.GraphQLにインポートしたJSON形式の投稿データを表示するページを作成する

ここでは、2の手順の「取得した投稿データをGraphQLで使用できるようにする」のJSON形式の投稿データを表示するためのページを作成します。
例ではhomeポストの投稿データを表示するため、pagesディレクトリ直下にIndex.vueを作成しています。
[pages/Index.vue]

<template>
  <Layout>
    <div class="hero">
      <!-- Vue Carouselプラグインを使用しています -->
      <ClientOnly>
        <carousel :per-page="1" :mouse-drag="false" :autoplay="true">
          <slide v-for="(item, key) in carousel" :key="key">
            <img :src="item.carousel.url" />
          </slide>
        </carousel>
        </ClientOnly>
        <h1>{{ title }}</h1>
    </div>
    <section>
        <ul>
          <li v-for="(item, key) in feature" :key="key">
            <div>
              <img :src="item.feature_image.url" :alt="item.feature_title">
            </div>
            <div>
              <h3>{{ item.feature_title }}</h3>
              <p>{{ item.feature_content }}</p>
            </div>
          </li>
        </ul>
    </section>
  </Layout>
</template>

<!-- carouselスキーマはMicroCMS側で複数コンテンツ参照を利用。配列形式で取得される点にご注意 -->
<page-query>
query {
  allHome {
    edges {
      node {
        id
        title
        date
        introduce {
          intro_title
          intro_content
          intro_image {
            url
          }
        }
        hero {
          url
        }
        carousel {
          id
          carousel {
            url
          }
        }
        feature {
          feature_title
          feature_image {
            url
          }
          feature_content
        }
      }
    }
  }
}


</page-query>
<script>
export default {
  data() { // Vue側でGraphQLの投稿データを利用する
    return {
      title: null,
      date: null,
      introduce: {
        intro_title: null,
        intro_content: null,
        intro_image: {
          url: null
        }
      },
      hero: {
        url: null
      },
      carousel: [{
        id: null,
        carousel: {
          url: null
        }
      }],
      feature: [{
        feature_title: null,
        feature_image: {
          url: null
        },
        feature_content: null
      }]
    }
  },
  components: {
    // CarouselはNetlifyでサーバーサイドレンダリングできないため、その対策
    Carousel: () =>
      import('vue-carousel')
      .then(m => m.Carousel)
      .catch(),
    Slide: () =>
      import('vue-carousel')
      .then(m => m.Slide)
      .catch()
  },
  metaInfo: {
    title: 'microCMSをGridSomeで構築してNetlifyで公開するための検証サイト'
  },
  methods: {
    putData() {
      this.title = this.$page.allHome.edges[0].node.title;
      this.date = this.$page.allHome.edges[0].node.date
      this.hero.url = this.$page.allHome.edges[0].node.hero.url
      this.carousel = this.$page.allHome.edges[0].node.carousel
      this.feature = this.$page.allHome.edges[0].node.feature
      this.introduce = this.$page.allHome.edges[0].node.introduce
    }
  },
  mounted() {
    this.putData();
  }
}
</script>

※この手順で使用している「カルーセルスライダーは」vue-carouselをインストールする必要があります。

$ npm i --save-dev vue-carousel


終わりに

以上になります。
何か抜けている部分があるかもしれません。
その場合はGithubのソースコードをご覧いただくか、
コチラまでおしらせください。