NuxtChildを使用してブログ記事ページを作成しよう【Nuxt.js×TypeScript】
  • 2022.02.03に公開
  • ブログ構築TS
  • 7. コンテンツページ構築
  • No.5 / 8

今回達成すること

ブログ記事を表示するコンテンツページを作成します。

その中で、NuxtChildコンポーネントについても解説します。

2022-02-03 18-48-21

ブログ記事コンテンツページのファイルを作成する

今回作成するブログ記事コンテンツページのパスは以下の通りです。

  • パス /posts/<category.fields.slug>/<post.fields.slug>

このパスに沿ったファイル構成は以下のようになります。

pages
└── posts
    └── _category
        └── _slug.vue

上記のディレクトリとページファイルを作成しましょう。

% mkdir -p pages/posts/_category && touch $_/_slug.vue

NuxtChildコンポーネントとは?

今回はNuxtChildコンポーネントを使用して、_slug.vueの共通レイアウトファイルを作成します。

NuxtChildコンポーネントとは、ネストされたルートの子コンポーネントを表示するために使用します。

pages
└── posts
    └── _category
        └── _slug.vue => ネストされたルートの子コンポーネント
  • ネストされたルートの、
  • 親ディレクトリと同じ名前のページファイルを作成することで、
  • 親コンポーネントとして扱うことができます。
pages
└── posts
    ├── _category
    │   └── _slug.vue => ネストされたルートの子コンポーネント
    └── _category.vue => ネストされたルートの親コンポーネント(親ディレクトリと同じ名前のページファイル)

子コンポーネントの呼び出しは、

  • 親の_category.vueから
  • NuxtChildコンポーネントを使用します。

NuxtChildの呼び出し場所には、_slug.vueのコンテンツが表示されます。

pages/posts/_category.vue
<template>
  <div>
    <!-- ここに_slug.vueのコンテンツが表示される -->
    <nuxt-child />
  </div>
</template>
pages/posts/_category/_slug.vue
<template>
  <div>
    hogehoge
  </div>
</template>
結果

2022-02-03 18-18-28

親コンポーネント_category.vueを作成する

今回、親コンポーネントの_category.vueは、共通レイアウトファイルとして使用します。

_category.vueを作成しましょう。

% touch pages/posts/_category.vue

pagesディレクトリはこのようになりました。

pages
├── categories
│   └── _slug.vue
├── index.vue
└── posts
    ├── _category
    │   └── _slug.vue
    └── _category.vue

_category.vueのコードは、categories/_slug.vueとほぼほぼ同じです。

ただ、カテゴリーを取得するパラメーターはparams.categoryを参照します。

pages/posts/_category.vue
<template>
  <v-container>
    <nuxt-link
      to="/"
    >
      Home
    </nuxt-link>

    <div>
      Category title: {{ category.fields.title }}
    </div>

    <!-- ここのブログ記事が表示される -->
    <nuxt-child
      :category="category"
    />

    <h2>
      関連記事
    </h2>

    <ul>
      <li
        v-for="(post, i) in categoryPosts"
        :key="`post-${i}`"
      >
        [{{ post.fields.category.fields.title }}]
        {{ post.fields.title }}
      </li>
    </ul>
  </v-container>
</template>

<script lang="ts">
import { Context } from '@nuxt/types'
import { Component, Vue } from 'nuxt-property-decorator'
import { BlogCategory, BlogPost } from '~/store/types'

type AsyncData = void | {
  category: BlogCategory
  categoryPosts: BlogPost[]
}

@Component
export default class PostsCategoryPage extends Vue {
  validate ({ params }: Context): boolean {
    return !!params.category
  }

  asyncData ({ app: { $accessor }, params, $my }: Context): AsyncData {
    const category: BlogCategory | undefined =
      $accessor.categories.find((category: BlogCategory) =>
        /* params.categoryから検索する */
        category.fields.slug === params.category
      )

    if (!category) {
      return $my.errorHandler(404)
    }

    const categoryPosts: BlogPost[] = $accessor.categoryPosts[category.sys.id]

    return {
      category,
      categoryPosts
    }
  }
}
</script>

カテゴリー検索メソッドをVuexに用意する

$accessor.categories.find()の処理は、他のページでも使用されています。

そこで、Vuexのgetterslugから現在のカテゴリーを返す関数を追加します。

store/index.ts
export const getters = getterTree(state, {
  // 引数のslugから現在のパスと一致するカテゴリーを返す
  currentCategoryFor: (state: RootState) => (slug: string | undefined) => {
    if (!slug) { return undefined }

    return state.categories.find(category =>
      category.fields.slug === slug
    )
  }
})

検索処理をVuexのメソッドに書き換える

  1. pages/posts/_category.vue
  2. pages/categories/_slug.vue

$accessor.categories.find()の処理を書き換えましょう。

pages/posts/_category.vue
asyncData ({ app: { $accessor }, params, $my }: Context): AsyncData {
  const category: BlogCategory | undefined =
    // 書き換え
    $accessor.currentCategoryFor(params.category)
    // $accessor.categories.find((category: BlogCategory) =>
    //   category.fields.slug === params.category
    // )
  ...
}
pages/categories/_slug.vue
asyncData ({ app: { $accessor }, params, $my }: Context): AsyncData {
  const category: BlogCategory | undefined =
    // 書き換え
    $accessor.currentCategoryFor(params.slug)
    // $accessor.categories.find((category: BlogCategory) =>
    //   category.fields.slug === params.slug
    // )
  ...
}

ブログ記事コンテンツページを作成する

ブログ記事を表示する_slug.vueを作成します。

propscategoryを受け取り、asyncData()ではパラメーターと一致するブログ記事を取得します。

pages/posts/_category/_slug.vue
<template>
  <div>
    Post title: {{ post.fields.title }}
  </div>
</template>

<script lang="ts">
import { Context } from '@nuxt/types'
import { Component, Vue, Prop } from 'nuxt-property-decorator'
import { BlogCategory, BlogPost } from '~/store/types'

type AsyncData = void | {
  post: BlogPost
}

@Component
export default class PostsCategorySlugPage extends Vue {
  @Prop({
    type: Object,
    required: true
  })
    category!: BlogCategory

  validate ({ params }: Context): boolean {
    return !!params.slug
  }

  asyncData ({ app: { $accessor }, params, $my }: Context): AsyncData {
    const post: BlogPost | undefined =
      $accessor.posts.find((post: BlogPost) =>
        post.fields.slug === params.slug
      )

    if (!post) {
      return $my.errorHandler(404)
    }

    return {
      post
    }
  }
}
</script>
  • @Prop() ... nuxt-property-decoratorを使用した場合のpropsの書き方。
  • @Prop({ required: true }) ... required: trueの場合は、必ずpropsの値を渡す必要がある。これは、propsの値が親コンポーネントで確定している場合に使用する。
  • category! ... 初期値を代入する必要がない場合は、!をつけて変数を宣言する。

リンク元をオブジェクトに書き換える

index.vueの該当のリンク元をオブジェクトに書き換えましょう。

<nuxt-link to="">を使用している場合も、渡すオブジェクトは同じように書き換えてください。

<!-- 
  記事コンテンツページへのリンクを下記に書き換え
	{ name: 'posts-category-slug', params: { category: post.fields.category.fields.slug, slug: post.fields.slug }
-->
<v-list-item
  v-for="(post, i) in posts"
  :key="`post-${i}`"
  @click="$router.push({ name: 'posts-category-slug', params: { category: post.fields.category.fields.slug, slug: post.fields.slug }})"
>
</v-list-item>

ブログ記事コンテンツページを表示しよう

ブラウザで記事のコンテンツページへアクセスしてみましょう。

  • 赤枠で囲んだ部分が<nuxt-child />タグが表示しているコンテンツ、
  • 黄色で囲んだ部分が_category.vueに用意したコンテンツが表示されています。

2022-02-03 18-48-21

404が返ってきているか確認しよう

下記4つのパターンも確認し、404エラーが返ってきているか確認してください。

  1. 存在しないparams.slugの場合
  2. params.slugのパスがない場合
  3. 存在しないparams.categoryの場合
  4. params.categoryのパスがない場合

コンテンツがスクロールされている問題に対応する

一度ホームに戻って、別のコンテンツも表示してみます。

するとスクロール位置が記憶されていて、ページ末尾のコンテンツが表示されました。

2022-02-03 19-02-26

Nxutの子ルートはスクロール位置を記憶する

これはNuxtのデフォルトの仕様で、子ルートを使用するとスクロール位置を記憶します。

これを回避し、子ルートのスクロール位置を一番上にする場合はscrollToTopプロパティにtrueを渡します。

export default {
  scrollToTop: true
}

scrollToTop()を宣言して対応する

nuxt-property-decoratorでは、プロパティを直接クラス内に宣言すると、データの宣言とみなされるため、今回は関数で宣言します。

_category.vueの一番下にscrollToTop()を追加します。

pages/posts/_category.vue

@Component
export default class PostsCategoryPage extends Vue {
  ...
  // 追加
  // Doc: https://nuxtjs.org/docs/components-glossary/scrolltotop
  scrollToTop (): boolean {
    return true
  }
}

これで子ルートでもスクロール位置を記憶しなくなりました。

今回の作業は以上です。

% git add -A
% git commit -m "Add post content page"

まとめと次回

今回はNuxtChildコンポーネントを使って、ブログ記事のコンテンツページを作成しました。

次回はタグの一覧ページを作成します。

あなたの力になれること
私自身が独学でプログラミングを勉強してきたので、一人で学び続ける苦しみは痛いほど分かります。そこで、当時の私がこんなのあったら良いのにな、と思っていたサービスを立ち上げました。周りに質問できる人がいない、答えの調べ方が分からない、ここを聞きたいだけなのにスクールは高額すぎる。そんな方に向けた単発・短期間メンターサービスを行っています。
独学プログラマのサービス
ブログ構築TSの投稿
1
  • Nuxt.js×TypeScript開発環境構築
  • /
  • #01
Nuxt.jsをローカルPCに立ち上げよう
2
  • Nuxt.js×TypeScript開発環境構築
  • /
  • #02
Nuxt.jsプロジェクトをGitHubにPushしよう
3
  • Nuxt.js×TypeScript開発環境構築
  • /
  • #03
nuxt-property-decoratorのインストールとTypeScriptのセットアップ
1
  • Vuetifyセットアップ
  • /
  • #01
TypeScript環境のNuxt.jsにVuetifyを導入しよう
2
  • Vuetifyセットアップ
  • /
  • #02
VuetifyにカスタムCSSを追加してSASS変数を理解しよう
3
  • Vuetifyセットアップ
  • /
  • #03
VuetifyにカスタムSVGアイコンを追加しよう
1
  • NetlifyCLIを使ったNuxtデプロイ
  • /
  • #01
Netlify CLIをインストールして本番環境のサイトを作成しよう
2
  • NetlifyCLIを使ったNuxtデプロイ
  • /
  • #02
netlify.tomlを使ってNuxt.jsをNetlifyに手動デプロイしよう
1
  • Contentfulモデル構築
  • /
  • #01
Contentfulの料金とCommunityプランの無料枠を理解する
2
  • Contentfulモデル構築
  • /
  • #02
Contentfulへ新規会員登録、ロケールの変更、API Keyの発行を行う
3
  • Contentfulモデル構築
  • /
  • #03
Contentful ブログカテゴリーモデルを作成しよう
4
  • Contentfulモデル構築
  • /
  • #04
Contentful カテゴリーモデルに1対1で関連づくblogPostモデルを作成しよう
5
  • Contentfulモデル構築
  • /
  • #05
Contentful ブログ記事に1対多で関連づくplogTagモデルを作成しよう
6
  • Contentfulモデル構築
  • /
  • #06
Contentful カテゴリー・ブログ記事・タグコンテンツを作成しよう
1
  • Nuxt.js×Contentfulセットアップ
  • /
  • #01
Nuxt.js×Contentfulセットアップ。モジュールのインストールからAPI Keyの登録まで
2
  • Nuxt.js×Contentfulセットアップ
  • /
  • #02
Contentful APIリクエストの実行 Nuxt.jsにブログコンテンツを表示しよう
3
  • Nuxt.js×Contentfulセットアップ
  • /
  • #03
ContentfulAPIをNetlifyにデプロイしよう【Nuxt FullStaticのasyncDataとfetch】
1
  • Vuex×TypeScriptセットアップ
  • /
  • #01
Vuexの型付け vuex-module-decoratorsとnuxt-typed-vuexどちらを使用するか
2
  • Vuex×TypeScriptセットアップ
  • /
  • #02
nuxt-typed-vuexのインストールとセットアップ。Vuexの型定義と呼び出し方
3
  • Vuex×TypeScriptセットアップ
  • /
  • #03
VuexにContentfulの型定義ファイルとnuxtServerInitを追加しよう
4
  • Vuex×TypeScriptセットアップ
  • /
  • #04
VuexにContentfulAPIレスポンスを保存してVueファイルに表示しよう
1
  • コンテンツページ構築
  • /
  • #01
ブログアプリのページ設計とNuxt.jsの動的ルーティングについて理解しよう
2
  • コンテンツページ構築
  • /
  • #02
カテゴリーのコンテンツページを作成しよう【Nuxt.js×Contentful】
3
  • コンテンツページ構築
  • /
  • #03
カテゴリーに関連付くブログ記事一覧を表示しよう【Nuxt.js×Contentful】
4
  • コンテンツページ構築
  • /
  • #04
injectを使用して共通エラー処理メソッドを作成しよう【Nuxt×TypeScript】
5
  • コンテンツページ構築
  • /
  • #05
NuxtChildを使用してブログ記事ページを作成しよう【Nuxt.js×TypeScript】
6
  • コンテンツページ構築
  • /
  • #06
タグ一覧ページとタグ関連づく記事一覧を表示しよう【Nuxt.js×TypeScript】
7
  • コンテンツページ構築
  • /
  • #07
プライバシーポリシーページを作成しよう【Nuxt.js×TypeScript】
8
  • コンテンツページ構築
  • /
  • #08
@nuxtjs/i18nのインストールとセットアップ。ページタイトルの翻訳化【TypeScript】
1
  • NetlifyFunctionsを使った検索機能
  • /
  • #01
Netlify Functionsを使ってクエリを返す関数を作成しよう【Nuxt.js×TypeScript】
2
  • NetlifyFunctionsを使った検索機能
  • /
  • #02
Netlify Functionsプロジェクトをデプロイしよう【Nuxt.js×TypeScript】
3
  • NetlifyFunctionsを使った検索機能
  • /
  • #03
Nuxt.js × axiosセットアップ Netlify Functionsにリクエストを行う準備をしよう
4
  • NetlifyFunctionsを使った検索機能
  • /
  • #04
オリジン•CORS•プリフライトリクエストを理解する【Nuxt.js×Netlify Functions】
5
  • NetlifyFunctionsを使った検索機能
  • /
  • #05
Netlify Functionsを使ってフォームバリデーション機能を構築しよう【Nuxt.js】
6
  • NetlifyFunctionsを使った検索機能
  • /
  • #06
ツールバーに表示する検索フォームを作成しよう【Nuxt.js×TypeScript】
7
  • NetlifyFunctionsを使った検索機能
  • /
  • #07
検索ページを作成しよう【Vue propsとTypeScriptの書き方 解説】
8
  • NetlifyFunctionsを使った検索機能
  • /
  • #08
Netlify FunctionsからContentfulAPIリクエストを送ろう【Nuxt.js】
9
  • NetlifyFunctionsを使った検索機能
  • /
  • #09
検索ページに「もっと見る」ボタンを実装しよう【Nuxt.js×TypeScript】
1
  • ブログMarkdown対応
  • /
  • #01
@nuxtjs/markdownitのインストールとセットアップ【Nuxt.js×TypeScript】
2
  • ブログMarkdown対応
  • /
  • #02
Nuxt.js×markdown-it 外部リンクを別タブで開くプラグインを追加しよう
3
  • ブログMarkdown対応
  • /
  • #03
Nuxt.js×markdown-it 内部リンクをVueRouterで高速にページ遷移しよう
4
  • ブログMarkdown対応
  • /
  • #04
Nuxt.js×markdown-it アンカーリンクとブログ目次を自動生成しよう
独学プログラマ
独学でも、ここまでできるってよ。
CONTACT
Nuxt.js制作のご依頼は下記メールアドレスまでお送りください。