今回達成すること
今回は、
- ブログ記事ページを用意して、
- アイキャッチ画像が無い場合にデフォルトの画像を表示
していきます。
アイキャッチ画像がセットされていないと、エラーを吐き出して記事の確認が取れなくなるので、このエラー回避にも繋がります。
記事ページの表示
アイキャッチが無い場合の対応
Gitでブランチを作成する
毎回のことですが、作業前に新しいブランチに移動しましょう。
$ git checkout -b post_slug
確認しておきましょう。
$ git branch
master
* post_slug
setup_contentful
よし、次!
ブログ記事の個別ページを用意する
前回までに、ブログトップページに記事一覧を表示する設定を行っていきました。
この記事の内容を表示する個別ページ用意していきましょう。
pagesディレクトリに「posts」ディレクトリを作成して、
Nuxtプロジェクトのトップディレクトリ上で、下記コマンドを実行してください。
$ mkdir -p pages/posts && touch pages/posts/_slug.vue
-
mkdir -p pages/posts
-p
オプションでpages下の階層に新規ディレクトリを作成しています。 -
touch
新規ファイルを作成するコマンドです。
このような階層でファイルが作成されます。
app
...
L pages
L posts // 作成
L _slug.vue // 作成
Contentfulからブログ記事を取得する
さあ、
このページでは、記事のタイトル、アイキャッチ画像、公開日、記事の内容を表示します。
pages/posts/_slug.vue
<template>
<v-container fluid>
<template v-if="currentPost">
{{ currentPost.fields.title }}
<v-img
:src="currentPost.fields.image.fields.file.url"
:alt="currentPost.fields.image.fields.title"
:aspect-ratio="16/9"
width="700"
height="400"
class="mx-auto"
/>
{{ currentPost.fields.publishDate }}<br>
{{ currentPost.fields.body }}
</template>
<template v-else>
お探しの記事は見つかりませんでした。
</template>
<div>
<v-btn
outlined
color="primary"
to="/"
>
<v-icon size="16">
fas fa-angle-double-left
</v-icon>
<span class="ml-1">ホームへ戻る</span>
</v-btn>
</div>
</v-container>
</template>
<script>
import client from '~/plugins/contentful'
export default {
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 }
}
}
</script>
-
'fields.slug': params.slug
Contentfulの「blogPost」モデルから、
params.slug
に一致する記事を検索して取得します。この
params.slug
プロパティは、リンク元のURLに埋め込む必要があります。
ブログ記事のリンクにparamsを埋め込む
それでは記事一覧を表示している
「この記事をみる」ボタンに算出プロパティを追記します。
<template>
...
<v-card-actions>
<v-spacer />
<v-btn
text
color="primary"
:to="linkTo(post)" <!-- 追記 -->
>
この記事をみる
</v-btn>
</v-card-actions>
...
</template>
<script>
import client from '~/plugins/contentful'
export default {
// 追記
computed: {
linkTo: () => (obj) => {
return { name: 'posts-slug', params: { slug: obj.fields.slug } }
}
},
// 追記終了
...
}
</script>
-
{ name: 'posts-slug',
nameでURL指定をしています。
これは、
{ path: posts/${obj.fields.slug}...
と書き換えることができます。ここのnameは、
.nuxt/router.js より確認することができます。 -
params: { slug: obj.fields.slug } }
URLに「slug」というパラメーターを埋め込んでいます。
この
obj.fields.slug
には、Contentfulで指定した「slug」の値が入ります。_slug.vue では、URLパラメータからブログ記事を検索し取得します。
computedとmethodsの違い
大きな違いは計算結果がキャッシュされるかされないかです。
- computed ... 計算結果がキャッシュされ、依存データに変更がない限りキャッシュを返す
- methods ... 呼び出し毎に計算する
ブログ記事へのURLは不変的な値です。
何かを変更すると、連動して変化するような値ではないですよね。
呼び出し時に毎回計算するより、計算結果のキャッシュを返した方が早く動きます。
そこでURLは、computed
に算出プロパティとして計算しています。
もっと詳しく知りたい方はこの記事がおすすめです。
ブログ記事の個別ページを確認してみよう
以上の編集が終わったら、Nuxtを起動してhttp://localhost:3000/にアクセスしてみましょう。
$ yarn dev
「この記事をみる」をクリックすると下記のような画面が表示されているはずです。
URLに注目してください。
各記事それぞれ違うURLで表示されていますね。
これは先ほどのname: 'posts-slug'
が有効に動いている証拠です。
アイキャッチ画像がない場合の対応をする
もしContentfulでアイキャッチ画像をセットしていない場合、「TypeError Cannot read property 'fields' of undefined」というエラーが出ます。
これは、imageオブジェクトのプロパティが存在しないことにより発生するエラーです。
<v-img
:src="currentPost.fields.image.fields.file.url" << ここのプロパティがないためにエラー発生
:alt="currentPost.fields.image.fields.title" << ここのプロパティがないためにエラー発生
:aspect-ratio="16/9"
width="700"
height="400"
class="mx-auto"
/>
画像保管ディレクトリを作成する
デフォルトのアイキャッチは、assets/imagesディレクトリに保存します。
コマンドを実行して
$ mkdir -p assets/images
imagesディレクトリが作成されます。
app
L assets
L images // 作成
デフォルトのアイキャッチを用意する
アイキャッチがない場合に表示する画像を用意しましょう。
その画像をassets/imagesディレクトリに保存します。
今回は
サイズは900×500、おおよそ16対9の比率に合わせています。
app
L assets
L images
L defaultEyeCatch.png // 保存
共通メソッドをVuexに追加する
NuxtでVuexを扱う場合は、storeディレクトリにjsファイルを作成します。
ここで事前知識を得ておいてください。
storeディレクトリに
ターミナルから下のコマンドを実行してください。
$ touch store/index.js
作成した
store/index.js
import defaultEyeCatch from '~/assets/images/defaultEyeCatch.png'
export const getters = {
setEyeCatch: () => (post) => {
if (!!post.fields.image && !!post.fields.image.fields) return { url: `https:${post.fields.image.fields.file.url}`, title: post.fields.image.fields.title }
else return { url: defaultEyeCatch, title: 'defaultImage' }
}
}
-
index.jsファイルについて
storeディレクトリ内のindex.jsはルートモジュールとして扱われます。
index.js内のメソッドは、ファイル名を指定せず、直接メソッド名を指定して呼び出すことができます。
-
index.jsのGetterを呼びすとき =>
...mapGetters(['setEyeCatch'])
-
posts.jsというファイルのGetterを呼び出すとき =>
...mapGetters('posts', ['setEyeCatch'])
-
-
if (!!post.fields.image && !!post.fields.image.fields)
!!post.fields.image
は、記事にimageがセットされている時にtrue
を返します。!!post.fields.image.fields
は、記事にセットされたimageが公開の場合にtrue
を返します。要約すると、「記事にimageがセットされていて、かつそのimageが公開されている場合」に
true
となります。 -
else return { url: defaultEyeCatch, title: 'defaultImage' }
imageが無い、もしくは非公開の場合に
defaultEyeCatch
を返します。ここに画像のURLを直接指定するとうまく動きませんでした。
ですのでimportした
defaultEyeCatch
をセットしています。ここを参考にしています。
Getterメソッドを利用する
作成したGetterを呼び出しましょう。
まずは
pages/index.vue
<template>
...
<v-img
:src="setEyeCatch(post).url" <!-- 変更する -->
:alt="setEyeCatch(post).title" <!-- 変更する -->
:aspect-ratio="16/9"
max-height="200"
class="white--text"
>
...
</template>
<script>
import { mapGetters } from 'vuex' // 追記
export default {
computed: {
...mapGetters(['setEyeCatch']), // 追記
...
}
</script>
次に
<template>
...
<v-img
:src="setEyeCatch(currentPost).url" // 変更する
:alt="setEyeCatch(currentPost).title" // 変更する
:aspect-ratio="16/9"
width="700"
height="400"
class="mx-auto"
/>
...
</template>
<script>
import { mapGetters } from 'vuex' // 追記
export default {
// 追記
computed: {
...mapGetters(['setEyeCatch'])
},
// 追記終了
...
}
</script>
Contentfulでアイキャッチ画像を外してみる
ここまでできたら、Contentfulにログインしてブログ記事のアイキャッチを外してみましょう。
Imageの「•••」ボタンから「Remove」を選択し、「Publish changes」で保存してください。
ほら、うまくできましたね!
ここまでの編集をBitbucketにpushしておきましょう
毎度の作業です。masterブランチにマージしてpushしましょう。
$ git add -A
$ git commit -m "add_post_slug_page"
$ git checkout master
$ git merge post_slug
$ git push
さて次回は?
今回は、ブログ記事の個別ページを用意してアイキャッチが無い場合のエラー対応をしていきました。
ただ今のままでは、Countentfulのコンテンツを公開状態にしないと開発環境に表示されません。
ブログを運用していくには、公開前の確認作業が必要ですよね。
そこで次回は、
- 開発環境には下書きを含む全てのコンテンツを
- 本番環境には公開コンテンツのみを
表示するようにカスタマイズしていきます。
それではまた!