タグ一覧ページとタグ関連づく記事一覧を表示しよう【Nuxt.js×TypeScript】
  • 2022.02.08に公開
  • ブログ構築TS
  • 7. コンテンツページ構築
  • No.6 / 8

今回達成すること

  • タグ一覧ページ => tags/index.vueと、
  • タグに関連づく記事一覧を表示する => tags/_slug.vue(以下、タグコンテンツページ)

を作成します。

タグ一覧とタグコンテンツページを作成する

「pages」ディレクトリに「tags」ディレクトリを作成し、その直下にindex.vueと、_slug.vueを作成します。

% mkdir pages/tags && touch $_/{index.vue,_slug.vue}
  • index.vueにアクセスするパスは/tags
  • _slug.vueにアクセスするパスは/tags/<tag.fields.slug>となります。

タグ一覧ページの編集

タグ一覧ページはVuexに保管したtagsasyncData()内で取得し、Htmlに表示しています。

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

    <h2>
      タグ一覧
    </h2>

    <ul>
      <li
        v-for="(tag, i) in tags"
        :key="`tag-${i}`"
      >
        <nuxt-link
          :to="{ name: 'tags-slug', params: { slug: tag.fields.slug } }"
        >
          {{ tag.fields.title }}
        </nuxt-link>
      </li>
    </ul>
  </v-container>
</template>

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

type AsyncData = {
  tags: BlogTag[]
}

@Component
export default class TagsPage extends Vue {
  asyncData ({ app: { $accessor } }: Context): AsyncData {
    const tags = $accessor.tags

    return {
      tags
    }
  }
}
</script>

http://localhost:3000/tags にアクセスしてみましょう。

タグ一覧が表示されました。

2022-02-06 15-12-20

タグコンテンツページの編集

_slug.vueはこれまで編集してきたコンテンツページと同じ処理を行います。

pages/tags/_slug.vue
<template>
  <v-container>
    <nuxt-link
      to="/tags"
    >
      Tags
    </nuxt-link>

    <div>
      Tag title: {{ tag.fields.title }}
    </div>

    <h2>
      タグに関連づく記事
    </h2>

    <!-- TODO tagPostsのループ -->
  </v-container>
</template>

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

type AsyncData = void | {
  tag: BlogTag
}

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

  asyncData ({ app: { $accessor }, params, $my }: Context): AsyncData {
    const tag = $accessor.tags.find((tag: BlogTag) =>
      tag.fields.slug === params.slug
    )

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

    return {
      tag
    }
  }
}
</script>

/tagsからタグのリンクをクリックし、タグコンテンツページが表示されることを確認してください。

2022-02-06 19-25-30

リンクを生成する共通メソッドを作成する

<nuxt-link>に渡すリンクオブジェクトを簡素化するために共通メソッドを作成します。

<!-- 下記リンクオブジェクトを共通メソッド化 -->
<nuxt-link
  :to="{ name: 'tags-slug', params: { slug: tag.fields.slug } }"
>

my-plugin.tsに、linkTo()というメソッドを追加します。

plugins/my-plugin.ts
// 追加
import { BlogCategory, BlogPost, BlogTag } from '~/store/types'

// 以下、local types追加
/*
  local types
*/
// リンクオブジェクトのname値
type LinkName = 'categories-slug' | 'tags-slug' | ''

// LinkTo()のデフォルト戻り値
interface LinkToDefault {
  name: LinkName
  params: {
    slug: string
  }
}

// LinkTo()のblogPost戻り値
interface LinkToPost {
  name: 'posts-category-slug'
  params: {
    category: string
    slug: string
  }
}

/*
  class types
*/
export interface MyPluginInterface {
  errorHandler (statusCode: number): void
  errorMessage (statusCode: number): string
  // 追加
  linkTo (entry: BlogCategory | BlogPost | BlogTag): LinkToDefault | LinkToPost
}

class MyPlugin implements MyPluginInterface {
  ...
  // 追加
  // 引数のリンクオブジェクトを返す
  linkTo (entry: BlogCategory | BlogPost | BlogTag) {
    const entryType: string = entry.sys.contentType.sys.id
    const slug: string = entry.fields.slug

    // blogPostPath: /posts/<category.fields.slug>/<post.fields.slug>
    if (entryType === 'blogPost') {
      const name: 'posts-category-slug' = 'posts-category-slug'
      const post = entry as BlogPost
      const category: string = post.fields.category.fields.slug

      return { name, params: { category, slug } }
    }

    const linkName = (entryType: string): LinkName => {
      switch (entryType) {
        case 'blogCategory': return 'categories-slug'
        case 'blogTag': return 'tags-slug'
        /* 動的ルートネームはここに追加する */
        default: return ''
      }
    }

    return { name: linkName(entryType), params: { slug } }
  }
}
  • interface LinkToPost ... blogPostのリンクオブジェクトには、categoryプロパティが必須なので専用の型を用意している。
  • linkTo (): LinkToDefault | LinkToPost ... linkTo()メソッドの戻り値は、LinkToDefault もしくはLinkToPostとなる。
  • const name: 'posts-category-slug' ... 'blogPost'の場合、nameプロパティは固定値なので、'posts-category-slug'の固定値を型として指定。これをしないとstring型となり、タイプエラーとなる。
  • const post = entry as BlogPost ... asBlogPost型として上書きしている。これによりfields.categoryプロパティが使用可能となる。BlogCategory, BlogTagの型には、fields.categoryプロパティが存在しないのでasで型を固定しないとタイプエラーとなる。

エディタで解析されたlinkTo()の型は以下のようになります。

2022-02-06 19-15-44

現在のリンクをlinkTo()に書き換える

それではタグ一覧ページのリンクをlinkTo()に書き換えましょう。

pages/tags/index.vue
<!-- :to="ここを書き換え" -->
<nuxt-link
  :to="$my.linkTo(tag)"
>
<!-- <nuxt-link
  :to="{ name: 'tags-slug', params: { slug: tag.fields.slug } }"
> -->
  {{ tag.fields.title }}
</nuxt-link>

ページ遷移が正しく行われているか確認してください。

Vuexにタグに関連づく記事一覧オブジェクトを作成する

ここからタグに紐づく記事一覧を表示する設定を行います。

VuexにTagIDに紐づいた記事一覧格納する、tagPostsオブジェクトを作成します。

tagPostsオブジェクトの完成イメージ
tagPosts: {
  'tag ID 1': [<'tag ID 1'に関連づく記事の配列>],
  'tag ID 2': [<'tag ID 2'に関連づく記事の配列>],
   ...
}

型の名前を書き換える

型はCategoryPostsと同じものを使用します。

そこで、CategoryPostsの型の名前をRelatedPostsに書き換え、共通の型として使用します。

types/index.tsCategoryPostsを書き変えましょう。

store/types/index.ts
// 書き換え
// カテゴリー関連 or タグ関連posts
export type RelatedPosts = {
  [key: string]: BlogPost[]
}
// export type CategoryPosts = {
//   [key: string]: BlogPost[]
// }

RelatedPostsに書き換えたことにより、store/index.tsの型も書き換えましょう。

store/index.ts
// { ..., RelatedPosts }に書き換え
import { BlogCategory, BlogPost, BlogTag, RelatedPosts } from './types'

export const state = () => ({
 ...
 // as RelatedPostsに書き換え
 categoryPosts: {} as RelatedPosts
})

export const mutations = mutationTree(state, {
 ...
 // (..., payload: RelatedPosts)に書き換え
 setCategoryPosts (state: RootState, payload: RelatedPosts) {
   state.categoryPosts = payload
 }
})

export const actions = actionTree({ state, getters, mutations }, {
 ...
 getCategoryPosts ({ state, commit }) {
   // RelatedPostsに書き換え
   const categoryPosts: RelatedPosts = {}
 }
})

tagPostsを処理するメソッド群を追加する

  • state => tagPosts: {}
  • mutations => setTagPosts()
  • actions => getTagPosts()

をそれぞれ追加します。

アクションメソッドのgetTagPosts()は、nuxtServerInit()内で呼び出してください。

store/index.ts
export const state = () => ({
  ...
  // 追加
  tagPosts: {} as RelatedPosts
})

...

export const mutations = mutationTree(state, {
  // 追加
  setTagPosts (state: RootState, payload: RelatedPosts) {
    state.tagPosts = payload
  }
})

export const actions = actionTree({ state, getters, mutations }, {
  async nuxtServerInit ({ dispatch }, _nuxtContext: Context) {
    await dispatch('getContentfulApi')
    dispatch('getCategoryPosts')
    // 追加
    dispatch('getTagPosts')
  },
	...
  // 追加
  // tagId別のBlogPost配列を作成する
  getTagPosts ({ state, commit }) {
    const tagPosts: RelatedPosts = {}

    for (const tag of state.tags) {
      const tagId: string = tag.sys.id

      tagPosts[tagId] = []

      for (const post of state.posts) {
        // postにtagsが存在しない場合は次のループへ
        if (!post.fields.tags) {
          continue
        }

        // post.fields.tagsのtagID配列を作成
        const postTagIds: string[] =
          post.fields.tags.map((tag: BlogTag) => tag.sys.id)

        // tagID配列にtagのIDが存在する場合配列にpush
        if (postTagIds.includes(tag.sys.id)) {
          tagPosts[tagId].push(post)
        }
      }
    }

    commit('setTagPosts', tagPosts)
  }
})

以上でタグに関連づく記事一覧のオブジェクトが作成できました。

tagPostsの実態は以下のようになります。

2022-02-06 20-21-03

タグコンテンツページにtagPostsを表示しよう

tags/_slug.vueasyncData()から、tagPostsを取得し、Html内でループします。

pages/tags/_slug.vue
<template>
  <v-container>
    ...

    <h2>
      タグに関連づく記事
    </h2>

    <!-- 以下、追加 -->
    <ul>
      <li
        v-for="(post, postI) in tagPosts"
        :key="`post-${postI}`"
      >
        {{ post.fields.title }}
        <ul
          class="d-flex"
          style="list-style: none"
        >
          <li
            v-for="(tag, tagI) in post.fields.tags"
            :key="`tag-${tagI}`"
            class="mr-2"
          >
            <nuxt-link
              :to="$my.linkTo(tag)"
            >
              {{ tag.fields.title }}
            </nuxt-link>
          </li>
        </ul>
      </li>
    </ul>
  </v-container>
</template>

<script lang="ts">
...
// BlogPost 追加
import { BlogTag, BlogPost } from '~/store/types'

type AsyncData = void | {
  tag: BlogTag
  // 追加
  tagPosts: BlogPost[]
}

@Component
export default class TagsSlugPage extends Vue {
  ...
  asyncData ({ app: { $accessor }, params, $my }: Context): AsyncData {
    const tag = $accessor.tags.find((tag: BlogTag) =>
      tag.fields.slug === params.slug
    )

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

    // 追加
    const tagPosts: BlogPost[] = $accessor.tagPosts[tag.sys.id]

    return {
      tag,
      // 追加
      tagPosts
    }
  }
}
</script>

ブラウザで確認しよう

タグのコンテンツページにアクセスしてください。(筆者の場合: http://localhost:3000/tags/nuxtjs)

タグが関連づいた記事一覧が表示されました。

記事に紐づいたのタグの配列にも、同じタグが表示されているか確認してください。

2022-02-06 20-30-53

以上で全てのコンテンツページが作成できました。

Netlifyにデプロイする

ここまでの編集を本番環境にデプロイしましょう。

% yarn netlify:deploy

generateコマンドが走ると全てのルートが生成されるのが分かります。

2022-02-06 20-40-27

今回の作業は以上です。

% git add -A
% git commit -m "Add tag index and content page"

まとめと次回

今回はタグ一覧ページとタグのコンテンツページを作成しました。

次回はプライバシーポリシーページを作成します。

あなたの力になれること
私自身が独学でプログラミングを勉強してきたので、一人で学び続ける苦しみは痛いほど分かります。そこで、当時の私がこんなのあったら良いのにな、と思っていたサービスを立ち上げました。周りに質問できる人がいない、答えの調べ方が分からない、ここを聞きたいだけなのにスクールは高額すぎる。そんな方に向けた単発・短期間メンターサービスを行っています。
独学プログラマのサービス
ブログ構築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制作のご依頼は下記メールアドレスまでお送りください。