【自作ブログ】nuxt generateで動的ルーティングも含める方法
前回は記事の詳細画面を作成しました。
しかしNetlifyにデプロイしてみると記事の詳細ページが表示されないことがわかりました。
今回はこの原因と対策について説明していきます。
なぜダメだったのか?
なぜ表示されなかったのでしょうか。
それはNetlifyにデプロイする際に実行しているnpm run generate
の処理が深く関係しています。
nuxt generateは動的ルーティングを無視する
npm run generate
を実行した時pages/posts/_slug
のような動的に決定されるルーティングは全て無視されます。
考えてみれば当然のことで、_slug
の部分は「contentfulから取得した記事のslug
の値になります」なんてことをNuxt.jsが知っているわけがないのです。
どうするか?
解決策は単純です。「知らないのなら教えてあげればいい」のです。
nuxt generate
が実行される時に動的生成されるルートを全て教えてあげましょう。
generate時のルートを定義する
動的なルーティングを含ませる
解決策はわかったので早速修正していきましょう。
公式の方法を参考にしながらnuxt.config.js
を修正していきます。
generate
の中にroutes
関数を定義し、動的ルートの配列を返すように実装していきます。
nuxt.config.js
require('dotenv').config()
const sdkClient = require('./plugins/contentful').default // 追加
export default {
// 〜中略〜
generate: {
routes () {
return Promise.all([
sdkClient.getEntries({
content_type: 'blogPost'
})
]).then(([posts]) => {
return [
...posts.items.map((post) => {
return { route: `posts/${post.fields.slug}`, payload: post }
})
]
})
}
},
// 〜中略〜
}
routes
関数の中では、まずContentfulにリクエストを送って全ての記事のデータを取得しています。
そこから動的なルートの配列を生成して返すようになっています。
しかしroute
の他にpayload
も一緒に返しています。これはなんでしょうか?
payload
はgenerate
による生成時間を退縮するテクニックとしてAPIに記載されています。
APIに記載されている通りroutes
関数でルーティングを生成する時に、必要なデータ以外は破棄するようになっています。
つまり各記事の静的なページを生成する際に、もう一度Contentfulにリクエストを送ってデータを取得しなければならないのです。
pages/posts/_slug.vue
のコンテキストにpayload
として記事のデータを渡してあげる事によって、この二度手間を解決できるのです。
各ページでpayloadからデータを取得する
では各記事の詳細ページのコンテキストに渡されたpayload
から記事のデータを取得するように修正していきましょう。
pages/posts/_slug.vue
<template>
<!-- 〜中略〜 -->
</template>
<script>
import { mapState } from 'vuex'
export default {
computed: {
...mapState(['posts'])
// 削除〜ここから〜
// post () {
// return this.posts.find(
// post => post.fields.slug === this.$route.params.slug
// )
// }
// 削除〜ここまで〜
},
// 追加〜ここから〜
async asyncData ({ payload, store, params, error }) {
const post = payload || await store.state.posts.find(post => post.fields.slug === params.slug)
if (post) {
return { post }
} else {
return error({ statusCode: '404', message: 'お探しのページは見つかりませんでした' })
}
}
// 追加〜ここまで〜
}
</script>
<style>
// ...
</style>
これでpayload
にデータが入っている時、つまりgenerate時はpayload
から情報を取得するようになりました。
それ以外の時は今まで通りstate
から対象のpost
を探して表示します。
それでもmiddlewareは止まらない
context
のpayload
にデータを渡す事によって、npm run generate
実行時にはpayload
を参照すれば記事のデータを取得できるようになりました。
しかし、それだけでは不十分なのです。
実はnpm run generate
コマンドで静的ファイルをgenerateするとき、1ページ生成する毎にstoreの内容がリセットされるのです。
前回、私たちはmiddlewareを使ってstore.state.posts
が空のときは必ずレンダリング前にContentfulにリクエストを送ってデータを取得するように実装してしまいました。
これでは、いくらgenerate時にpayload
へデータを渡していてもmiddlewareが毎回働いてContentfulへのリクエストが送られてしまいます。
実際にgenerate時の動きを確かめてみましょう。
ログを仕込んで確認してみる
まずはmiddleware/getPosts.js
にログを仕込みます。
middleware/getPosts.js
export default async ({ store, payload }) => {
console.log('middleware is called.') // 常に出力
if (!store.state.posts.length) {
console.log('store.posts is empty...') // postsが空のときだけ出力
if (payload) {
console.log('payload is : ' + payload.fields.title) // payloadにデータが入っていれば出力
}
await store.dispatch('getPosts')
}
}
次にローカルでnpm run generate
を実行してコンソールログを確認してみましょう。
$ npm run generate
.
.
.
middleware is called. 13:41:08
store.posts is empty... 13:41:08
middleware is called. 13:41:08
store.posts is empty... 13:41:08
payload is : これはテスト2です 13:41:08
middleware is called. 13:41:08
store.posts is empty... 13:41:08
payload is : テストの記事 13:41:08
✔ Generated / 13:41:09
✔ Generated posts/test2 13:41:09
✔ Generated posts/test 13:41:09
ログの結果から分かる通り、store.posts
は毎回空っぽです。
そしてpayload
にデータが入っているにもかかわらずstore.dispatch('getPosts')
してしまっている事もわかりました。
payloadにデータがある時はdispatchしないようにする
では必要なとき以外はContentfulへのリクエストが発生しないように修正していきましょう。
middleware/getPosts.js
export default async ({ store, payload }) => {
if (!store.state.posts.length && !payload) {
await store.dispatch('getPosts')
}
}
これで必要なときだけContentfulへのリクエストが送信されるようになりました。
Netlifyにデプロイして確認する
最後にNetlifyにデプロイして確認してみましょう。
$ git add -A
$ git commit -m "記事詳細画面が正常に表示されるように修正"
$ git checkout master
$ git merge develop
$ git push origin master
Netlifyが発行してくれたURLを開いて詳細ページが表示できることが確認できれば完了です。
終わりに
お疲れ様でした。ようやく記事の詳細ページを正しく実装することができました。
ブログサイトの作成を通じて、Nuxt.jsの仕組みがどのようになっているかを少しだけ垣間見れたような気がしませんか?
次回は記事に関連付ける「カテゴリー」の機能を実装していこうと思います。
それではまた次回。