owned mediaウェブ制作に役立つコンテンツを発信中!

ヘッドレスCMSのWordPressとNuxt.jsのSSGでJamstackなサイトを作成してみる #3:動的ページ(投稿一覧)の実装

最終更新日: Update!!
前回記事「ヘッドレスCMSのWordPressとNuxt.jsのSSGでJamstackなサイトを作成してみる #2:フロントエンドでのサイト制作」では、Nuxt.jsでサイト制作にあたっての設定や基本的な処理、また静的ページの作成などを行いました。静的ページのみであれば、このままビルドして公開という流れになりますが、今回はヘッドレスCMSのWordPressを使って、記事ページの一覧と詳細という動的なページも作成していきます。それでは、今回はまず投稿一覧のアーカイブページを作成していく流れをまとめてみたいと思います。   ヘッドレスCMSの構築、またNuxt.jsでの静的ページ作成については過去記事にもまとめていますのでご参考に。 (過去記事) ・ヘッドレスCMSのWordPressとNuxt.jsのSSGでJamstackなサイトを作成してみる #2:フロントエンドでのサイト制作 ・ヘッドレスCMSのWordPressとNuxt.jsのSSGでJamstackなサイトを作成してみる #1:ヘッドレスCMSの構築   まず、前回のおさらいにもなりますが、ここでは下記のような構成を前提に進めています。今回は投稿一覧ページを作成していきますので、pagesディレクトリ配下にルーティングに対応させるディレクトリ(今回はblogという名前にしています)を作成し、その直下にindex.vueを作成します。これが投稿一覧ページになります。また、アーカイブのリストやページネーションなど、他のページでも投稿の抜粋を使えるようにコンポーネント化しておきます。そのため、componentsディレクトリに必要なコンポーネントを用意しておきます。
/SRC_DIR
  ┣ ......
  ┣ components
    ┣ ......
    ┣ PostArchive.vue
    ┗ Pagenation.vue
  ┣ pages
    ┣ index.vue
    ......
    ┗ blog
      ┣ index.vue
      ┗ _id.vue
  ┗ store
    ┣ index.js
    ┗ blog.js
  投稿一覧ページの中身を見ていきます。今回はサンプルのため、必要最小限の内容になっていますので、デザインや仕様に合わせて適宜調整してください。ページテンプレート内には、記事抜粋のアーカイブリストとページネーションが含まれるコンポーネントを配置しています。そしてスクリプト側で各コンポーネントを呼び出します。 【pages/blog/index.vue】
<template>
  <div>
    <post-archive />
  </div>
</template>

<script>
  import PostArchive from '~/components/PostArchive.vue'
  export default {
    components: {
      PostArchive
    },
  ......
}
</script>
  ページコンポーネントはこんな感じで先に進めていきます。もし記事のリストを独立したコンポーネントではなく、ページコンポーネントに直接設置する場合には次に紹介していくロジックをページコンポーネントに書いていく形になります。  
投稿一覧用の抜粋をアーカイブ表示
続いて投稿一覧用のアーカイブリストを作成していきます。ここでの流れとしては、まずAPI経由でヘッドレスCMSから投稿データを取得→現在ページの目次とトータルのページ数を算出→現在ページの目次に合わせた部分の投稿を抜粋→ループ処理でページ内のリスト上に展開という形になります。今回は取得した投稿データについて、コンポーネント内のdataオプションで扱うのではなく、他の部分でも流用できるようにstoreの値に格納しておきます。そのため、store内にstateやmutationを定義していきます。 【store/blog.js】
export const state = () => ({
  currentPosts: []
})

export const mutations = {
  setCurrentPosts: (state, posts: []) => state.currentPosts = posts
}

export const actions = {
  fetchCurrentPosts: ({ commit }, data: []) => commit('setCurrentPosts', data)
}

export const getters = {
  getCurrentPosts: state => state.currentPosts
}
  上記ではシンプルに取得した投稿データを全てstateの値に格納していく仕様になっています。投稿データの取得は、コンポーネント側でfetchメソッドを使って取得するので、そstore内では、その値をstateへ受け渡していくだけになっています。格納したデータを取得する場合にはgettersを使うので、フィルタリングなどをする場合にはgettersの中で処理する形でもいいですね。   続いて投稿一覧ページのメインとなるコンポーネントであるアーカイブリストを作成していきます。このコンポーネントでは、投稿データの取得と、現在ページの目次や全ページ数の算出、またページネーションの呼び出しなどを行なっていきます。テンプレート側では、記事抜粋を表示させるリストとコンポーネント化したページネーションを配置します。そしてスクリプト側では、投稿データの取得やページ情報の算出などを行います。まず、投稿データの取得ですがAxiosを使いますのでモジュールを読み込んでいきます。そしてfetchメソッドでAPIのエンドポイントに対してGETメソッドで実行していきます。(参考記事「Nuxt.jsでasyncDataやfetchを使って外部からのデータを非同期で取得してコンポーネントに出力する」)そうすると投稿データのオブジェクトが変数内に格納されます。 【components/PostArchive.vue】
<template>
  <div>
    <ul>
      <li v-for="(post, index) in posts" :key="index">
        <span>投稿日:{{ post.date }}</span>
        <h2>タイトル:{{ post.title }}</h2>
        <nuxt-link :to="`/blog/${post.ID}`">この記事を見る</nuxt-link>
      </li>
    </ul>
    <pagenation :page-data="{ postsPerPage, currentPage, totalPages }" />
  </div>
</template>

<script>
  import { $axios } from 'axios';
  import Pagenation from '~/components/Pagenation.vue'
  export default {
    name: 'PostArchive',
    components: {
      Pagenation
    },
    async fetch() {
      const response = await this.$axios.get('https://example.com/wp-json/wp/api/blog/')
      this.foundPosts = response.data.length || 0
      await this.$store.dispatch('blog/fetchCurrentPosts', response.data.slice(this.postOffset, this.postOffset + this.postsPerPage))
      this.posts = this.$store.getters['blog/getCurrentPosts']
      if((this.foundPosts) && (this.postsPerPage < this.foundPosts)) {
        this.totalPages = Math.floor(this.foundPosts / this.postsPerPage) + 1
      } else {
        this.totalPages = 1
      }
    },
    data() {
      return {
        postsPerPage: 24,
        postOffset: 0,
        currentPage: this.getCurrentPage || 1,
        totalPages: 1,
        foundPosts: 0,
        posts: []
      }
    },
    computed: {
      getCurrentPage() {
        return this.$route.query.page ? Number(this.$route.query.page) : 1
      }
    },
    created() {
      this.setPageOffset()
    },
    methods: {
      setPageOffset() {
        if(this.currentPage > 1) {
          this.postOffset = (this.currentPage - 1) * this.postsPerPage
        } else {
          this.postOffset = 0
        }
      }
    }
  }
</script>
  投稿データを取得したら、まずは全投稿データ数を算出します。そして現在ページの目次数と、ページあたりの表示件数から、ページに表示させる投稿記事をフィルタリングして、dispatchメソッドでstoreの値に投稿データを格納していきます。そこからgettersで投稿データを取得し、ページ上に出力するため配列の形で保持しておきます。そして先ほど算出した全投稿データ数とページあたりの表示件数から、全ページ数を算出します。これらの情報は、dataオプション内に値として置いておき、ページネーションのコンポーネントに受け渡していきます。   あとは、ページ送りのための処理を用意していきます。1つめは、現在ページの目次数を取得する処理になります。これは現在ページの目次数をクエリパラメーターで管理することで$routeのqueryキーを参照することで目次数を取得することができます。もし、クエリがない場合には1ページ目になるようにしておきます。2つめは、表示させる投稿リストでの表示させる開始位置を計算する処理です。この処理をすることで、現在ページの目次数に対応した投稿データを表示させることができます。オフセット位置の計算には、現在ページの目次(つまりクエリパラメーターの値)と、ページあたりの表示件数から計算することができます。   これでページ繰りに対応した投稿データを表示させるアーカイブリストができました。実際にgettersで取得した値を確認すると、オブジェクトの形で投稿データを取得できているのが確認できますね。  
ページネーションの実装
先ほどの状態ではまだページを移動する手段がないため、URLを直接変更しないと機能しない状況です。そのためページネーションを実装していきます。こちらもコンポーネント化しておくことで、いろんなページで流用することができますので、処理などもできるだけ汎用性が高いような形にしておきます。まずはテンプレート側から見ていきます。今回はシンプルにリスト要素でマークアップし、最初のページ、前のページ、次のページ、最後のページへの移動と、直近で2ページ先へのページリンクを入れている仕様になります。注意する点としては、リンク形式では正しく動かない場合があるので、クリックイベントでrouteの値を更新するようにしてあげることです。これで確実に現在ページの目次数を扱うことができます。それぞれ現在ページの数に合わせて必要なリンクが表示されるように条件分岐をかけておきます。 【components/Pagenation.vue】
<template>
  <ul>
    <li class="first" v-if="pageData.currentPage > 1">
      <span @click="pagenation(1)">最初のページ</span>
    </li>
    <li class="prev" v-if="pageData.currentPage > 1">
      <span @click="pagenation(pageData.currentPage - 1)">前のページ</span>
    </li>
    <li class="current">
      <span>{{ pageData.currentPage }}</span>
    </li>
    <li class="pages" v-if="pageData.currentPage <= pageData.totalPages - 1">
      <span @click="pagenation(pageData.currentPage + 1)>{{ pageData.currentPage + 1 }}</span>
    </li>
    <li class="pages" v-if="pageData.currentPage <= pageData.totalPages - 2">
      <span @click="pagenation(pageData.currentPage + 2)>{{ pageData.currentPage + 2 }}</span>
    </li>
    <li class="next" v-if="pageData.currentPage <= pageData.totalPages - 1">
      <span @click="pagenation(pageData.currentPage + 1)">次のページ</span>
    </li>
    <li class="last" v-if="pageData.currentPage <= pageData.totalPages - 1">
      <span @click="pagenation(pageData.totalPages)">最後のページ</span>
    </li>
  </ul>
</template>

<script>
  export default {
    name: 'Pagenation',
    props: [
      pageData: {
        type: Object,
        default: () => {}
      }
    ],
    methods: {
      pagenation(index) {
        this.$router.push({
          query: {
            page: index
          }
        })
      }
    }
  }
</script>
  スクリプト側では、クリックでページ目次数の値をクエリパラメーターへ更新する処理になります。先ほどの親コンポーネントから、ページ繰りに関する情報をPropsとして受け取っているので、そのデータを利用しながら、現在ページの目次数を更新していく処理を作成していきます。これで実際にテストしてみると、ルーティングに関する情報も更新されているのが確認できます。  
  今回はヘッドレスCMSのWordPressからの投稿データを、動的ページの中に表示させる方法についてまとめてみました。当サイトもそうですが、実際にはカテゴリやキーワード検索など複数のクエリパラメーターの情報が掛け合わされることもあります。今回の記事では基本的な部分のみでしたが、このような場合には、上記のサンプルをベースにクエリの更新などのカスタマイズが必要になりますね。次回も引き続き、ヘッドレスCMSを使ったJamstackなサイトの制作についてまとめていきたいと思います。   (過去記事) ・ヘッドレスCMSのWordPressとNuxt.jsのSSGでJamstackなサイトを作成してみる #2:フロントエンドでのサイト制作 ・ヘッドレスCMSのWordPressとNuxt.jsのSSGでJamstackなサイトを作成してみる #1:ヘッドレスCMSの構築
  • はてなブックマーク
  • Pocket
  • Linkedin
  • Feedly

この記事を書いた人

Twitter

sponserd

    keyword search

    recent posts

    • Twitter
    • Github
    contact usscroll to top
      • Facebook
      • Twitter
      • Github
      • Instagram