Nuxt.jsでカテゴリページを作る(WordPress REST API)

シェアする:

この記事をおすすめしたい人

  • WordPressしか知らないのにNuxt.jsを始めようとしてる人
  • Nuxt.jsでカテゴリページを作ろうとしている人
  • カテゴリページを作ったのにページの移動ができない人
  • つまりオレ

WordPress Nuxt化の一環です。

今回はWordPressのREST APIからデータを取得して、Nuxt.jsでカテゴリページを作っていきます。

タグページやただの記事一覧ページにも応用できると思います。

WordPressだとほぼ全自動生成と言ってもいいカテゴリページですが、Nuxt.jsで作るのはちょっと大変でした。

一応コピペでも動作するレベルまでは持っていきたいと思います。

WordPressの上でな!

※ このブログはまだWordPress製です

カテゴリページ作成までの流れ

WordPressの場合の話でいきます。 その他のCMSは構造が分からないので適宜読み替えてください。

  1. Nuxt.js側でカテゴリページを動的ルーティングで生成する
  2. アクセスされたURLからカテゴリIDやスラッグを取得
  3. アクセスされたURLからページ番号(初期値1)を取得
  4. 取得したIDまたはスラッグからカテゴリ情報を取得(REST API)
  5. カテゴリ情報とページ番号から該当記事(10件とか)を取得(REST API)
  6. カテゴリ情報から記事総数を取得
  7. 記事総数とページ番号からページネーションを生成

のような流れになります。

カテゴリページではなく、タグページの場合も一部読み替えるだけで流用できます。

ただの記事一覧の場合はちょっと手間がかかる部分があるので、説明を付け加えたいと思います。

カテゴリページを動的ルーティングで生成

カテゴリページを「/category/3/」や「/category/php/」のようなURLにする場合、まずはNuxt.jsのプロジェクトフォルダを開き、その中の「pages」フォルダに「category」というフォルダを作ります。 「/category/」の部分を変えたい場合はフォルダをその名前にします。


/pages/
└/category/

次に動的ルーティング用のファイルを作ります。

動的ルーティングというのは「about.vue」「contact.vue」のようにルートの名前(URLと思ってください)を指定するのではなく、IDやカテゴリのように、指定した情報に対して動的にルートとページ内容を生成する方法です。 詳しくはNuxt公式を見てください。

やり方は簡単、先ほどのcategoryフォルダの中に「_id.vue」や「_slug.vue」を作るだけです。


/pages/
└/category/
 └_id.vue

ファイル名は最初にアンダースコアをつけるのが動的ルーティングのサインで、後ろの「id」や「slug」は別になんでもかまいません。 「_id.vue」としておいてスラッグを受け取ることもできます。

無駄に分かりにくくすることもないので素直に付けておきます。

動的ルーティングのパラメータの受け取り方

パラメータはREST APIへのリクエストに使用するので以下、asyncData内での処理とします。

asyncDataの引数に「{params}」を指定します。


export default {
  async asyncData ({ params }) {
    const slug = params.slug
  },
}

asyncDataの引数はcontextオブジェクトで、中括弧でくくるとその中身を展開して受け取ることができます。 今回は動的ルーティングのパラメータ「params」を受け取っています。

paramsはシンプルな構造で、動的ルーティング用のファイル名「_XXXX.vue」の「XXXX」が入るだけです。


{
  slug: 'nuxt-js'
}

今回は「_slug.vue」の例ですが、「_id.vue」だとこの↓ような構造になります。


{
  id: '3'
}

ページ番号の受け取り

ページ番号(①② 3 ④⑤⑥みたいなやつ)はGETで「?page=2」のように指定するものとします。

受け取り方は先ほどの延長で、asyncDataの引数にqueryを追加し、その中のpageを取得するだけです。


export default {
  async asyncData ({ params, query }) {
    const slug = params.slug
    const page = query.page
  },
}

ですがちょっとやっかいな部分があるので、詳細は後回しにします。

REST APIからカテゴリ情報を取得

paramsで受け取ったカテゴリIDなりスラッグなりを元に、WordPressのREST APIからカテゴリ情報を取得します。

axiosをインポート

まずはaxiosというモジュールをインポートします。


import axios from 'axios'

axiosって何ですか?

と聞かれると困ってしまうのですが、僕もよく理解していません。 APIと通信するときはaxiosを使う!と覚えておけばしばらくは十分だと思います。

axiosはNuxt.jsのインストールオプションでも選べるので既にインストールされているかもしれません。

まだの場合はターミナルから以下のコマンドでインストール。


$ npm i @nuxtjs/axios

nuxt.config.jsに設定追加。


export default {
  modules: [
    '@nuxtjs/axios',
  ],
}

axiosの基本的な使い方

axiosの基本的な使い方はこんな↓感じ。


export default {
  async asyncData ({ params, query }) {
    const res = await axios.get(url)
  },
}

「url」の部分にリクエストするAPIのURLを放り込みます。

戻り値はこんな↓感じ。


{
  status: 200,
  statusText: 'OK',
  headers: { (略) },
  config: { (略) },
  request: <ref *1> ClientRequest { (略) },
  data: [
    (略)
  ]
}

「data」の中にリクエストした結果が入っています。 基本的に用があるのは「data」だけで、ちょっとだけ「headers」も使います。

ID指定でカテゴリAPIを取得

axiosの使い方が分かったので、あとはリクエストするURLを生成するだけです。

今回はカテゴリAPIなのでREST API公式に習います。 カテゴリではなくタグの場合はこちらを。

ID指定でカテゴリを取得する場合は、REST APIのルート(https://xxxx.com/wp-json/とか)に対して次のように追加します。


/wp/v2/categories/(id)

IDが2ならこう↓。


/wp/v2/categories/2

カテゴリIDはパラメータとして受け取るので、ここまでのパラメータの受け取り方、axiosの使い方と合わせてこんな↓風に取得します。


export default {
  async asyncData ({ params }) {
    const id = params.id
    const query = 'https://xxxx.com/wp-json/wp/v2/categories/' + id
    const res = await axios.get(query)
    
    return {
      category: res.data
    }
  },
}

asyncDataはページのインスタンス作成前に実行されるのでthisへはアクセスできません。 なのでreturnで必要な情報を返してやります。

返した値はdata()と統合されます。
(同じキーの値はasyncDataが上書き)

上記コードを実行すると「category」に次のような内容が入ります。


{
  "id": 11,
  "count": 5,
  "description": "",
  "link": "略",
  "name": "AMP",
  "slug": "amp",
  "taxonomy": "category",
  "parent": 0,
  "meta": [],
  "_links": { (略) }
},

無駄なデータを省きたい場合はカテゴリAPI軽量化の記事を参考にしてください。 省略してますが_linksの中身がめっちゃ多いです。

スラッグ指定でカテゴリAPIを取得

スラッグの場合はちょっとだけ手順が異なります。

というのもスラッグは一意(ユニーク)ではないので、IDのように指定するものではなく、該当するものを検索する形になります。

具体的にどう変わるかというと、APIが返すデータが配列になります(要素はひとつ)。

リクエストURLもちょっと変わってこんな↓感じ。


/wp/v2/categories?slug=XXXX

カテゴリ情報取得までのコードはこの↓ようになります。


export default {
  async asyncData ({ params }) {
    const slug = params.slug
    const query = 'https://xxxx.com/wp-json/wp/v2/categories?slug=' + slug
    const res = await axios.get(query)
    
    return {
      category: res.data[0]
    }
  },
}

上記コードは「APIが確実にレスポンスを返す前提」で成立するので、実際はレスポンスがちゃんと返ったかチェックした方がいいと思います。

カテゴリAPIが取得できたら

まずページタイトルが付けられるようになります。


<h1>【{{category.name}}】の記事一覧</h1>

タイトルの他に、descriptionやOGPに使える情報も入手できました。

カテゴリの記事一覧を取得するためのカテゴリIDも手に入りました。

REST APIからカテゴリの記事一覧を取得

カテゴリページの記事一覧を取得するには次の3点が必要です。

  • カテゴリのID
  • 1ページあたりの表示数
  • ページ番号(2ページ目なら11~20件目の記事、みたいな)

IDはもう取得しました。

1ページあたりの表示数は今回は10件とします。
(デフォルトで10件ですが明示しています)

ページ番号はちょっとややこしいので、とりあえず最初の10件を取得します。

axiosで記事一覧を取得する

axiosの使い方はカテゴリAPIの時と同様で、リクエストするURLが変わります。


/wp/v2/posts?categories=2

上記コードだとカテゴリIDが2の、最新の記事10件を取得できます。 詳細は投稿API検索のオプションを見てください。

実際のコードはこんな↓感じ。


export default {
  async asyncData ({ params }) {
    const per_page = 10
    
    const id = params.id
    const query = 'https://xxxx.com/wp-json/wp/v2/posts' + 
                  '?categories=' + id +
                  '&per_page=' + per_page + 
                  '&context=embed'
    
    const res = await axios.get(query)
    
    return {
      posts: res.data
    }
  },
}

コード簡略化のためにIDは既知のものとしました。 スラッグで取得した場合は、先にカテゴリを取得してそのIDを指定します。。

「per_page」は1ページあたりの表示数です。 変更があった場合に対応できるよう明示しておきます。 asyncData内で使用するので、宣言はdata()ではなくasyncData内。

「context=embed」というのが付いていますが、これは一覧表示用に軽量化するオプションです。

上記コードを実行すると「posts」に投稿データの配列が入ります。

記事ひとつあたりの構造はこんな↓感じになります。


{
  "id": 102,
  "date": "2020-08-14T15:05:03",
  "slug": "wp-rest-embed",
  "type": "post",
  "link": "略",
  "title": {
    "rendered": "略"
  },
  "excerpt": {
    "rendered": "略",
    "protected": false
  },
  "author": 1,
  "featured_media": 0,
  "_links": { (略) }
}

けっこう無駄なデータが多いので、軽量化をおすすめします。

取得した記事一覧の出力

出力の仕方(HTMLタグの構造)は人によってかなり違うと思うので、一番シンプルな例を示します。


<ul>
<li v-for="post in posts">
<nuxt-link :to="'/post/' + post.slug + '/'">{{ post.title.rendered }}</nuxt-link>
</li>
</ul>

これは記事ページのパーマリンクが「/post/%postname%/」の場合のコードなので、異なる場合はそれに合わせて書き換えます。

Nuxt.jsがほぼ初の場合は色々と分からない表記があると思いますが、まず<nuxt-link>はNuxt.js用の<a>タグだと思ってください。 コードの違いは「href」が「to」に変わっていることくらいです。

その他、「v-bind」「v-for」というものが使われていて、それはこのページで解説で解説しています。

ページネーションを作成

ここまででページタイトル、記事一覧とあらかた見た目は整いました。

あとは次の10件を表示するためのページネーション(« ①② 3 ④⑤⑥ »)を作るだけです。

実装方法はいろいろあると思いますが、今回は「?page=2」のようにGETでページ番号を渡して実装します。

ページネーションの作り方

前回の記事で詳しくやりました。

詳しくはそちらを見てもらうとして、<script>部分のコード↓。


export default {
  props: [
    'current',
    'last',
  ],
  data () {
    return {
      num_pages: 5,
      start_num: 1,
      last_num: 5,
    }
  },
  computed: {
    pages() {
      const value = (this.num_pages-1)/2
      
      let start = this.current - Math.floor(value)
      let last = this.current + Math.ceil(value)
      
      if ( this.last <= this.num_pages ) {
        start = 1
        last = this.last
      } else if ( this.current <= 1 + Math.floor(value) ) {
        start = 1
        last = this.num_pages
      } else if ( this.current >= Math.ceil(value) ) {
        start = this.last - this.num_pages + 1
        last = this.last
      }
      
      let pages = []
      
      for ( let i = start; i <= last; i++ ) {
        pages.push(i)
      }
      
      this.start_num = start
      this.last_num = last
      
      return pages
    },
    prevPage() {
      if ( this.current > 1 ) {
        return this.current-1
      }
      return 0
    },
    nextPage() {
      if ( this.current < this.last ) {
        return this.current+1
      }
      return 0
    },
    prevDot() {
      return this.start_num > 1
    },
    nextDot() {
      return this.last_num < this.last
    },
  },
}

<template>部分のコード↓。


<div>
<nuxt-link v-if="prevPage" :to="'?page='+prevPage">«</nuxt-link>
<ul>
<li v-if="prevDot">…</li>
<li v-for="i in pages">
<nuxt-link v-if="i != current" :to="'?page=' + i">{{i}}</nuxt-link>
<span v-else class="current">{{i}}</span>
</li>
<li v-if="nextDot">…</li>
</ul>
<nuxt-link v-if="nextPage" :to="'?page='+nextPage">»</nuxt-link>
</div>

これを「<Pagination />」というコンポーネントにします。

使い方は、現在のページ番号(current)、最終ページ番号(last)を引数としてこんな↓感じ。


<Pagination :current="current" :last="last" />

watchQueryを設定しないとページ移動ができない

もうこれで完成しててもおかしくなかったんですが、ページネーションのリンクを踏んでもページ移動が起きませんでした。

これはデフォルトではasyncDataやfetchなどはGETのパラメータを監視していないことが原因だったようで、watchQueryというのを設定してやらないといけません。


export default {
  watchQuery: ['page'],
}

これでasyncDataがpageの変更を検出するようになります。

さらにkeyも設定しないとページの再レンダリングが起きませんでした。 これは説明読んでもあまり理解できていません…。


export default {
  watchQuery: ['page'],
  key(route) {
    return route.fullPath
  },
}

これで無事にページ移動もできるようになりました。

今回のコードまとめ

Nuxt.jsでWordPressのREST APIを利用してカテゴリページを作るコードです。

まずは<script>部分のコード。


//axiosをインポート
import axios from 'axios'

export default {
  //asyncDataでもGETパラメータの変更を監視
  watchQuery: ['page'],
  //謎。ないと動かない
  key(route) {
    return route.fullPath
  },
  async asyncData ({ params, query }) {
    //1ページあたりの表示数
    const per_page = 10
    
    //動的ルーティングのパラメータからカテゴリIDを取得
    const id = params.id
    
    //ページ番号を初期化
    let page = 1
    
    //GETでページ番号の指定があった場合は上書き
    if ( query.page ) {
        page = query.page
    }
    
    //カテゴリAPIのURL
    const query1 = 'https://xxxx.com/wp-json/wp/v2/categories/' + id
    //カテゴリAPIを取得
    const res1 = await axios.get(query1)
    
    //記事一覧APIのURL
    const query2 = 'https://xxxx.com/wp-json/wp/v2/posts' + 
                  '?categories=' + id +
                  '&per_page=' + per_page + 
                  '&page=' + page + 
                  '&context=embed'
    //記事一覧を取得
    const res2 = await axios.get(query2)
    
    //記事APIのレスポンスから記事総数を取得
    const last = Math.ceil(parseInt(res2.headers['x-wp-total']) / per_page)
    
    return {
      category: res1.data,
      posts: res2.data,
      current: page,
      last: last,
    }
  },
}

これはカテゴリIDをベースとした動的ルーティングのコードです。 スラッグの場合のコードは記事内で紹介しています。

次に<template>部分のコード。


<div>
<h2>【{{category.name}}】の記事一覧</h2>
<ul>
<li v-for="post in posts">
<nuxt-link :to="'/post/' + post.slug + '/'">{{ post.title.rendered }}</nuxt-link>
</li>
<Pagination :current="current" :last="last" />
</div>

<Pagination />というコンポーネントは前回の記事の流用です。 このページにも完成版のコードは書いています。

以上、WordPressからお届けしました!

シェアする: