MENU

microCMSで関連記事を表示する

【過去に書いたブログ記事のリライト版です】

みなさん、おはようございます、こんにちは、こんばんは。

ふじです。

今回も、microCMSによるブログを作成する際に、関連記事を取り出す方法について書いていきます。

結論として、関連記事を取り出す方法は以下のとおりです。

①設定したrelated_blogsを呼び出す。
②呼び出したrelated_blogsに入っている関連記事は、Array形式のデータ構造になっている。
③再度related_blogsに対してmap関数を実行し、Array形式のデータから一つ一つの要素を呼び出す。

microCMSのAPIの扱い方を理解しているのであれば、簡単なことなのかもしれません。

ですが、独学者にとっては、理解するというか、気づくまで時間がかかってしまいました。

今回のつまづきポイントを忘れないように記事にまとめてみます。

目次

microCMSの設定

まずは、microCMS側のAPIスキーマの設定から行います。

ブログに関するAPIスキーマは、上記のような画像に示されるフィールドを設定します。

この中のrelated_blogsが今回の関連記事のフィールドになります。

このフィールドの設定項目について確認していきます。

①フィールドID
APIスキーマとして設定したフィールドの名称を表すもの
フィールドIDとされているが、related_blogsなどの名称設定をすることができ、実際にAPIから情報を取ってくる際にkeyとして使用する

②表示名
コンテンツの作成をする際に項目名として表示されるもの
コンテンツ管理にあたり、どういった入力項目なのかを示す

③種類
フィールドの入力方法・入力形態を設定するもの
related_blogsに関しては、「複数コンテンツ参照」を選択している

複数コンテンツ参照を選択すると、上記のような詳細設定が必要となります。

これらの項目についても見ていきます。

⑴ 必須項目
必須項目をONにすると、入力が必須となる項目となる
related_blogsは関連記事であり、関連記事は各ブログに必ず設定されるものではないため、必須項目にしていない

⑵ 説明文
説明文は入稿画面において表示させる説明文を指す
特段必要でなければ記入は不要だが、複数人でコンテンツ管理を行う場合は、誰が見てもわかりやすい説明などがあるとよい

⑶ 参照先コンテンツ
参照先コンテンツは、すでに作成している記事を関連記事とする
このため、「ブログ(blog)」を参照するように設定している
これにより、他の記事の情報を取得することが可能となる

⑷ 一覧画面に表示する項目
コンテンツIDまたはテキストフィールドの項目から選択することができる
コンテンツIDを選択したため、関連記事として入力する際、他のブログ記事を参照すると、コンテンツIDが表示される

⑸ 複数コンテンツ参照の数を制限する
参照コンテンツの数を制限したい場合は設定する
「関連記事は多くても3つまで」といったような制限を設けたい場合は、「最小値」と「最大値」を設定する
必要に応じて設定する項目

以上がmicroCMSのAPIの設定になります。

続いては、Next.js側の記述について見ていきます。

Next.jsでのデータ取り出し

ブログにおける、関連記事を表示する流れを紹介します。

①[id].tsx

記事の詳細については、component化しており、記事表示部分を一つのcomponentPostDetail.tsx)として作成しています。

関連記事はさらにその中のRelatedPost(RelatedPost.tsx)というcomponentを通して表示するようにしています。

この場合、RelatedPostというcomponent[id].tsx内で取得している記事データについて、relatedBlog={relatedBlog}という形でRelatedPostというcomponentに情報を渡しています。

このrelatedBlogに入っているデータの各項目を参照することで、関連記事の情報を取得することができます。

※1 以下は、私のPostDetail.tsxの一部抜粋です。
   必要な箇所のみを表示しております。

※2 classNameにtailwind cssを使用していますのでご了承ください。

import React from 'react';
import { GetStaticPropsContext, InferGetStaticPropsType, NextPage } from 'next'
import { client } from "../../libs/client"
import { PostDetail, RelatedPost } from "../../components"

type DetailPage = {
  blog:{
    title: string
    description: string
    id: string
    ogimage: {
        url: string
    };
    writer: {
        name: string
        image: {
            url: string
        }
    }
    createdAt: string
    tags: {
        map: any
    }
  }
  highlightedBody: string
  tags: {
    map: any
  }
  relatedBlog: {
    related_blogs: {
      length: number
      map: any
    }
  }
}

const DetailPage = ({ blog, highlightedBody, relatedBlog }: DetailPage) => {
  return (
    <>
      <div className="max-w-screen-xl mx-auto px-2 bg-white">
        <div className="grid grid-cols-1 lg:grid-cols-12 gap-6">
          <div className="col-span-1 lg:col-span-8">
            <PostDetail blog={blog} highlightedBody={highlightedBody} />
            <RelatedPost relatedBlog={relatedBlog} />
          </div>
        </div>
      </div>
    </>
  );
}

export default DetailPage;


export const getStaticProps = async(
  context: GetStaticPropsContext<{ id: string }>
) => {
  const id = context.params?.id;
  const data = await client.get({
    endpoint: "blog",
    contentId: id
  })
 const related_blogs = await client.get({
    endpoint: "blog",
    contentId: id,
    queries: {
      fields: 'related_blogs'
    }
  })
  const $ = cheerio.load(data.body);
  
  // コードハイライトの設定
  $('pre code').each((_, elm) => {
    const result = hljs.highlightAuto($(elm).text());
    $(elm).html(result.value);
    $(elm).addClass('hljs');
  })

  return {
    props: {
      blog: data || null,
      highlightedBody: $.html() || null,
      relatedBlog: related_blogs,
    },
    revalidate: 1,
  }
}

export const getStaticPaths = async () => {
    const data = await client.get({
    endpoint: "blog"
  })
  const paths = data.contents.map((content: { id: any; }) => `/blog/${content.id}`);
  return {
    paths,
    fallback: false
  }
};

APIの構成

microCMSからデータを取り出す際に提供されるAPIのレスポンスは以下のような構成となります。

{
    "id": "コンテンツID",
    "createdAt": "作成日時",
    "updatedAt": "更新日時",
    "publishedAt": "公開日時",
    "revisedAt": "改訂日時",
    "title": "ブログタイトル",
    "tags": [
        {
            "id": "タグID①",
            "createdAt": "タグ作成日時①",
            "updatedAt": "タグ更新日時①",
            "publishedAt": "タグ公開日時①",
            "revisedAt": "タグ改訂日時①",
            "name": "タグ名①"
        },
        {
            "id": "タグID②",
            "createdAt": "タグ作成日時②",
            "updatedAt": "タグ更新日時②",
            "publishedAt": "タグ公開日時②",
            "revisedAt": "タグ改訂日時②",
            "name": "タグ名②"
        }
    ],
    "toc_visible": false,
    "body": "コンテンツ内容",
    "description": "概要文",
    "ogimage": {
        "url": "サムネイル画像URL",
        "height": 1080,
        "width": 1920
    },
    "writer": {
        "id": "著者ID",
        "createdAt": "作成日時",
        "updatedAt": "更新日時",
        "publishedAt": "公開日時",
        "revisedAt": "改訂日時",
        "name": "著者氏名",
        "text": "著者紹介文",
        "image": {
            "url": "著者画像URL",
            "height": 512,
            "width": 512
        }
    },
    "partner": null,
    "related_blogs": [
        {
            "id": "関連記事ID①",
            "createdAt": "関連記事作成日時①",
          "updatedAt": "関連記事更新日時①",
          "publishedAt": "関連記事公開日時①",
          "revisedAt": "関連記事改訂日時①",
            "title": "関連記事タイトル①",
            "tags": [
                {
                    "id": "関連記事に設定されたタグ①"
                },
                {
                    "id": "関連記事に設定されたタグ②"
                },
                {
                    "id": "関連記事に設定されたタグ③"
                }
            ],
            "toc_visible": true,
            "body": "関連記事コンテンツ①",
            "description": "関連記事概要文",
            "ogimage": {
                "url": "関連記事サムネイルURL",
                "height": 309,
                "width": 512
            },
            "writer": {
                "id": "関連記事著者"
            },
            "partner": null,
            "related_blogs": [関連記事の関連記事があればここに入ります],
            "featured_post": false
        }
    ],
    "featured_post": false
}

注目すべきは、以下のAPIです。

"related_blogs": [
        {
            "id": "関連記事ID①",
            "createdAt": "関連記事作成日時①",
          "updatedAt": "関連記事更新日時①",
          "publishedAt": "関連記事公開日時①",
          "revisedAt": "関連記事改訂日時①",
            "title": "関連記事タイトル①",
            "tags": [
                {
                    "id": "関連記事に設定されたタグ①"
                },
                {
                    "id": "関連記事に設定されたタグ②"
                },
                {
                    "id": "関連記事に設定されたタグ③"
                }
            ],
            "toc_visible": true,
            "body": "関連記事コンテンツ①",
            "description": "関連記事概要文",
            "ogimage": {
                "url": "関連記事サムネイルURL",
                "height": 309,
                "width": 512
            },
            "writer": {
                "id": "関連記事著者"
            },
            "partner": null,
            "related_blogs": [関連記事の関連記事があればここに入ります],
            "featured_post": false
        }
    ],

【!ポイント!】
1 related_blogsは配列として設定されています。
2 related_blogsの中は、「連想配列」により構成されています。
3 keyvalueが1セットになった連想配列からデータを取り出す。
4 relatedblogに入っているrelatedblogsに対しmap関数を実行
5 getStaticPropsで取得したデータ配列(上記のrelated_blogs)から1つずつ取り出し、related_blogという変数に入れる
6 related_blogには、idcreatedAtなどのkeyに紐づくvalueが格納されているため、「related_blog.○○」でデータを取り出すことが可能となる

といった流れです。

最後に

配列(と連想配列)、APIの構成を理解していなかったため、なかなか読み解くまで時間がかかってしまいました。

基礎はひととおり勉強したつもりでしたが、何かを作ることにシフトしていたため、少し疎かになってしまっていました。

今回の内容は、データの構造とデータの取り出し方を理解するためには避けては通れないものだと思いました。

データの構成を理解することで、何をどう取り出し、表示させるかを理解することができるとわかりました。

プログラミングを独学していると、わからないことが多く、戸惑いますが、同じ独学者の方と情報共有したり、この記事を見てくれた初学者の方にとって、参考になればよいと思います。

今回はここまでです。

最後まで読んでいただきありがとうございました。

【P.S】

もし、誤りを発見した場合は、お手数ですがTwitterよりご連絡をいただけますと幸いです。

この記事が気に入ったら
フォローしてね!

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

この記事を書いた人

はじめまして、ふじです。
Python、Django、FastAPI、React.js、Next.jsを学習している、ずっと文系のプログラミング独学者です。

コメント

コメントする

目次