今回達成すること
さて、今回はmiddlewareを使って、Contentfulからブログ記事を取得していきます。
middlewareを使うことで、Contentfulへリクエストを送信する頻度を減らし、サイトのパフォーマンスを最適にします。
現状の問題点
現在、記事一覧を
現在の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へリクエストを送信しなければなりません。
これではパフォーマンスが悪すぎます。
middlewareを使った解決策
そこで、APIリクエストを最初の一回だけにする方法として、 middleware を活用します。
middlewareとは、特定のページ又は全てのページにおいて、ページがレタリングされる前に実行する関数を定義する、Nuxt.jsの機能の一つです。
ここに関数を仕込んでおくと、どのページから来たとしても、必ず記事一覧を取得することができます。
流れとしては、こんな感じです。
- middlewareからContentfulへリクエスト
- Vuexに保管
- 必要な記事をJavascriptで取得
コードの編集に移りましょう。まずは、index.vueから
まずは、
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>
そして、
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)
}
}
-
Vuexストアではモジュールモードの書き方を採用しています。
ほかに「クラシックモード」の書き方がありますが、Nuxtjs v3 以降で廃止予定のため推奨しません。
-
actions
経由でContentful APIを取得しています。mutations
内でContentful APIを取得することも可能ですが、非同期処理で実行するために、actions
を経由しています。非同期処理とは、「ある関数が呼び出されたとき、戻り値として本来渡したい結果を返すのではなく、一度関数としては終了し(=呼び出し元に戻る)、後で『本来渡したかった値』を返せる状態になったときに、呼び出し元にその値を通知する」処理方法です。
引用(ここ分かりやすいよ!後で読んでみてください)
-
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内に
$ 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
export default {
// 追記
router: {
middleware: [
'getContentful'
]
},
// 追記終了
axios: {},
}
-
ruoter
プロパティのmiddlewareに配列を渡します。ここには、middlewareディレクトリ内のファイル名を配列で渡します。
以上で、middlewareの登録は完了です。
index.vueから呼び出してみる
さて、Vuexに保管した記事一覧を
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に記事一覧が表示されていますか?
表示されていれば成功です。
さあ、次へ進みましょう。
単一記事の取得方法を変更する
次は、
今現在の単一記事の取得方法は、
- 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>
-
const currentPost = payload || ...
本番環境では、
payload
に記事のオブジェクトが入っているので、currentPostへ代入しています。payloadについては、この記事で解説しています。
-
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を使って記事一覧を取得しました。
最後にもう一度流れを整理しておきますね。
- middlewareディレクトリに getContentful.js という名のファイルを作成し、
- nuxt.config.js の
router
プロパティに、middlewareを登録する。 - getContentful.jsでは、index.js の
actions
内でContentfulへリクエストを投げ、 - 帰ってきた記事一覧を、index.js の
posts
で保管する。 - index.vue では、index.jsの
posts
を参照し、記事一覧を表示する。 - posts/_slug.vue では、パラメーターが一致している記事を取り出す。
今回説明したことは、以上となります。
お疲れ様でした。
さて、次回は?
これで、ブログ記事周りの構築は完了です。
次は、カテゴリーモデルを新たに作成して、カテゴリー別の記事表示を実現していきましょう。
またね!