ブログ構築 5. ブログ記事周りの構築 #04
2019年10月12日に更新

【Nuxt.js】middlewareを活用しブログ記事取得のパフォーマンスを改善する

今回達成すること

さて、今回はmiddlewareを使って、Contentfulからブログ記事を取得していきます。

middlewareを使うことで、Contentfulへリクエストを送信する頻度を減らし、サイトのパフォーマンスを最適にします。

現状の問題点

現在、記事一覧をpages/index.vueで取得しています。

現在のpages/index.vue
<script>

export default {

  async asyncData({ env }) {
    let posts = []
    await client.getEntries({
      content_type: env.CTF_BLOG_POST_TYPE_ID,
      order: '-fields.publishDate'
    }).then(res => (posts = res.items)).catch(console.error)
    return { posts }
  }
}
</script>

このままだと、index.vueを経由しないと記事一覧を取得できません。

例えば、記事ページで「関連記事」や「人気の記事」を表示したいとなった場合、ページ毎にContentfulへリクエストを送信しなければなりません。

images orignal.001

これではパフォーマンスが悪すぎます。

middlewareを使った解決策

そこで、APIリクエストを最初の一回だけにする方法として、 middleware を活用します。

middlewareとは、特定のページ又は全てのページにおいて、ページがレタリングされる前に実行する関数を定義する、Nuxt.jsの機能の一つです。

ここに関数を仕込んでおくと、どのページから来たとしても、必ず記事一覧を取得することができます。

流れとしては、こんな感じです。

  1. middlewareからContentfulへリクエスト
  2. Vuexに保管
  3. 必要な記事をJavascriptで取得

images orignal.002

コードの編集に移りましょう。まずは、index.vueから

まずは、pages/index.vueのscript内のコードを整理します。

linkTo算出プロパティと、asyncData自体をまるっと削除してください。

pages/index.vue
<script>

// import client from '~/plugins/contentful'  削除

export default {

  computed: {

 // 削除
 // linkTo: () => (obj) => {
 //   return { name: 'posts-slug', params: { slug: obj.fields.slug } }
 // }
    
  },
 // 削除
 //  async asyncData({ env }) {
 //  let posts = []
 //  await client.getEntries({
 //    content_type: env.CTF_BLOG_POST_TYPE_ID,
 //    order: '-fields.publishDate'
 //  }).then(res => (posts = res.items)).catch(console.error)
 //  return { posts }
 // }
}
</script>

そして、index.jsへ削除したコードを移動します。

store/index.js
import client from '~/plugins/contentful'	// 追記

// 追記
export const state = () => ({
  posts: []
})

export const getters = {

	// 追記
  linkTo: () => (name, obj) => {
    return { name: `${name}-slug`, params: { slug: obj.fields.slug } }
  }
}

// 追記
export const mutations = {
  setPosts(state, payload) {
    state.posts = payload
  }

}

// 追記
export const actions = {
  async getPosts({ commit }) {
    await client.getEntries({
      content_type: process.env.CTF_BLOG_POST_TYPE_ID,
      order: '-fields.publishDate' // desc
    }).then(res =>
      commit('setPosts', res.items)
    ).catch(console.error)
  }

}

  • actions経由でContentful APIを取得しています。

    mutations内でContentful APIを取得することも可能ですが、非同期処理で実行するために、actionsを経由しています。

    非同期処理とは、「ある関数が呼び出されたとき、戻り値として本来渡したい結果を返すのではなく、一度関数としては終了し(=呼び出し元に戻る)、後で『本来渡したかった値』を返せる状態になったときに、呼び出し元にその値を通知する」処理方法です。

    引用(ここ分かりやすいよ!後で読んでみてください)

    非同期処理ってどういうこと?JavaScriptで一から学ぶ - Qiita

  • order: '-fields.publishDate'

    Contentful APIを呼び出す時、並び順を指定することができます。

    そのプロパティがorderです。

    今回は、公開日(publishDate)が新しい順に記事を取得しています。

    「-」を外すと、公開日が古い順に記事を取得することができます。

    • 「-」あり … 降順
    • 「-」なし … 昇順
  • linkTo: () => (name, obj) => ...

    linkTo算出プロパティは、他のオブジェクトでも使えるようにnameの引数を追加しました。

    これにより、カテゴリーページやタグページに使い回しすることができます。

算出プロパティ 生成されるPath
記事の場合 linkTo(‘posts’, post) “posts/slug”
カテゴリーの場合 linkTo(‘categories’, category) “categories/slug”
タグの場合 linkTo(‘tags’, tag) “tags/slug”

middlewareからindex.jsを実行する

それではmiddlewareにファイルを作成し、index.js内のコードを実行します。

middleware内にgetContentful.jsを作成してください。

$ touch middleware/getContentful.js

そして以下のように編集します。

middleware/getContentful.js
export default async ({ store }) => {
  if (!store.state.posts.length) await store.dispatch('getPosts')
}

middlewareは全てのページで実行される

基本的に、middlewareは全てのページを表示する前に実行されます。

そこで、記事が存在しないときだけリクエストを投げるように、if (!store.state.posts.length)という条件分岐をしています。

これにより、Vuex内のposts配列が空っぽの場合のみ、リクエストを投げることになります。

nuxt.config.jsにmiddlewareを登録する

今のままでは、middleware内の関数は実行されません。

nuxt.config.jsに登録する必要があります。

nuxt.config.js
export default {

  // 追記
  router: {
    middleware: [
      'getContentful'
    ]
  },
  // 追記終了
  
  axios: {},
}
  • ruoterプロパティのmiddlewareに配列を渡します。

    ここには、middlewareディレクトリ内のファイル名を配列で渡します。

以上で、middlewareの登録は完了です。

index.vueから呼び出してみる

さて、Vuexに保管した記事一覧をindex.vueで呼び出しましょう。

pages/index.vue
<script>
// import { mapGetters } from 'vuex'        // 削除
import { mapState, mapGetters } from 'vuex' // 追記

export default {

  computed: {
    ...mapState(['posts']),                               // 追記
//    ...mapGetters(['setEyeCatch', 'draftChip'])         // 削除
    ...mapGetters(['setEyeCatch', 'draftChip', 'linkTo']) // 追記
  }
}
</script>

index.vueに記事一覧が表示されていますか?

表示されていれば成功です。

さあ、次へ進みましょう。

単一記事の取得方法を変更する

次は、pages/posts/_slug.vueを編集します。

今現在の単一記事の取得方法は、

  • URLに紐付けたパラメーターのslugを、
  • Contentfulへ渡して検索し、
  • 単一記事を返しています。

これを、Vuexのpostsから取得するように変更します。

毎回APIを投げるよりは、パフォーマンスが良くなります。

pages/posts/_slug.vue
<script>
// import client from '~/plugins/contentful'  // 削除

export default {

  computed: {
    ...mapGetters(['setEyeCatch'])
  },
// 削除
//  async asyncData({ env, params }) {
//   let currentPost = null
//   await client.getEntries({
//     content_type: env.CTF_BLOG_POST_TYPE_ID,
//     'fields.slug': params.slug
//   }).then(res => (currentPost = res.items[0])).catch(console.error)
//
//   return { currentPost }
// }
  
  // 追記
  async asyncData({ payload, store, params, error }) {
    const currentPost = payload || await store.state.posts.find(post => post.fields.slug === params.slug)

    if (currentPost) {
      return { currentPost }
    } else {
      return error({ statusCode: 400 })
    }
  }
}
</script>
  • await store.state.posts.find(post => post.fields.slug === params.slug)

    payloadの値がない場合に、Vuexのpostsからfindメソッドで記事を検索します。

    渡されたslugが一致している記事を取り出しています。

  • return error({ statusCode: 400 })

    asyncDataの引数errorは、エラーコードを指定することができます。

    記事(currentPost)がない場合に、error 400を返します。

これで全ての設定が完了しました。

pushして本番環境でも確認しよう

だいぶコードがスッキリしましたね。

それでは、今までの変更をpushして本番環境でも確認してみましょう。

今回は、新規ファイルを追加したのでget add -Aを実行します。

$ git add -A
$ git commit -m "add_middleware_by_getContentful.js"
$ git push

本番環境でも表示されましたか?

OK!ナイスですね!

まとめ

今回は、middlewareを使って記事一覧を取得しました。

最後にもう一度流れを整理しておきますね。

  1. middlewareディレクトリに getContentful.js という名のファイルを作成し、
  2. nuxt.config.jsrouterプロパティに、middlewareを登録する。
  3. getContentful.jsでは、index.jsactions内でContentfulへリクエストを投げ、
  4. 帰ってきた記事一覧を、index.jspostsで保管する。
  5. index.vue では、index.jsのpostsを参照し、記事一覧を表示する。
  6. posts/_slug.vue では、パラメーターが一致している記事を取り出す。

今回説明したことは、以上となります。

お疲れ様でした。

さて、次回は?

これで、ブログ記事周りの構築は完了です。

次は、カテゴリーモデルを新たに作成して、カテゴリー別の記事表示を実現していきましょう。

またね!

現在、カテゴリー「Rails apiとNuxt.jsでSPA開発」のデモアプリを構築中です。記事になるまでもう少々のお時間が必要です。ブログの更新が止まって申し訳ありません。デモアプリの進捗状況は こちらの記事 で随時お伝えしてまいります。
スポンサー広告
次の記事はこちらです
ブログ構築
1. 今回作るアプリケーション
#01
Nuxt.jsとContentfulで作るマイブログ
今日のTweet
スポンサー広告