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

Nuxt.jsにContentfulのブログ記事を表示する

今回達成すること

今回は、

  1. ブログ記事ページを用意して、
  2. アイキャッチ画像が無い場合にデフォルトの画像を表示

していきます。

アイキャッチ画像がセットされていないと、エラーを吐き出して記事の確認が取れなくなるので、このエラー回避にも繋がります。

記事ページの表示

2019-10-11 14-21-20

アイキャッチが無い場合の対応

2019-09-01 19-46-36

Gitでブランチを作成する

毎回のことですが、作業前に新しいブランチに移動しましょう。

$ git checkout -b post_slug

確認しておきましょう。

$ git branch

  master
* post_slug
  setup_contentful

よし、次!

ブログ記事の個別ページを用意する

前回までに、ブログトップページに記事一覧を表示する設定を行っていきました。

Contentfulからブログ記事を取得してNuxt.jsで表示するための手順書

この記事の内容を表示する個別ページ用意していきましょう。

pagesディレクトリに「posts」ディレクトリを作成して、_slug.vueファイルを作成します。

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からブログ記事を取得する

さあ、_slug.vueを編集していきましょう。

このページでは、記事のタイトル、アイキャッチ画像、公開日、記事の内容を表示します。

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を埋め込む

それでは記事一覧を表示しているindex.vueを編集しましょう。

「この記事をみる」ボタンに算出プロパティを追記します。

<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に算出プロパティとして計算しています。

もっと詳しく知りたい方はこの記事がおすすめです。

https://dev83.com/vue-computed-methods/

ブログ記事の個別ページを確認してみよう

以上の編集が終わったら、Nuxtを起動してhttp://localhost:3000/にアクセスしてみましょう。

$ yarn dev

「この記事をみる」をクリックすると下記のような画面が表示されているはずです。

2019-08-31 14-51-07

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ディレクトリに保存します。

コマンドを実行してimagesディレクトリを作成しましょう。

$ mkdir -p assets/images

imagesディレクトリが作成されます。

app
 L assets
   L images // 作成

デフォルトのアイキャッチを用意する

アイキャッチがない場合に表示する画像を用意しましょう。

その画像をassets/imagesディレクトリに保存します。

今回はdefaultEyeCatch.pngという画像を用意しました。

サイズは900×500、おおよそ16対9の比率に合わせています。

app
 L assets
   L images
     L defaultEyeCatch.png // 保存

共通メソッドをVuexに追加する

NuxtでVuexを扱う場合は、storeディレクトリにjsファイルを作成します。

ここで事前知識を得ておいてください。

Vuex ストア - Nuxt.js

storeディレクトリにindex.jsファイルを作成しましょう。

ターミナルから下のコマンドを実行してください。

$ touch store/index.js

作成した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内のメソッドは、ファイル名を指定せず、直接メソッド名を指定して呼び出すことができます。

    1. index.jsのGetterを呼びすとき => ...mapGetters(['setEyeCatch'])

    2. 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をセットしています。

    ここを参考にしています。

    https://github.com/nuxt/nuxt.js/issues/2103

Getterメソッドを利用する

作成したGetterを呼び出しましょう。

まずはindex.vueを編集します。

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>

次に_slug.vueを編集します。

<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」で保存してください。

2019-09-01 19-39-59

ほら、うまくできましたね!

2019-09-01 19-46-36

ここまでの編集をBitbucketにpushしておきましょう

毎度の作業です。masterブランチにマージしてpushしましょう。

$ git add -A
$ git commit -m "add_post_slug_page"
$ git checkout master
$ git merge post_slug
$ git push

さて次回は?

今回は、ブログ記事の個別ページを用意してアイキャッチが無い場合のエラー対応をしていきました。

ただ今のままでは、Countentfulのコンテンツを公開状態にしないと開発環境に表示されません。

ブログを運用していくには、公開前の確認作業が必要ですよね。

そこで次回は、

  • 開発環境には下書きを含む全てのコンテンツを
  • 本番環境には公開コンテンツのみを

表示するようにカスタマイズしていきます。

それではまた!

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