こんにちは。
私が個人開発で作成しているmicroCMS × Next.jsのブログで、カテゴリ毎の一覧ページを作成する実装が発生しました。
案外資料が少なくて実装に苦労したので備忘録代わりにやったことを記載したいと思います。
前提1:microCMS側でのカテゴリの設定方法
今回の記事では、カテゴリを
- ブログ記事に紐付ける
- 複数のカテゴリを記事に紐付け可能
- 記事一覧にバッジ型UIとして表示させ、カテゴリ名を押下するとカテゴリ一覧に遷移する
と設定するものとします。
その場合、カテゴリ専用のコンテンツAPIを作成する必要があります。
今回はカテゴリのエンドポイントはcategoryと命名するものとします。
作成したコンテンツAPIはブログのAPIスキーマで複数コンテンツ参照を選択することでブログ記事と紐づけることができます。
詳細はmicroCMSの公式ドキュメントにも記載がございます。
前提2:カテゴリを含んだCMSのデータの型
前提1で作成・紐付けしたカテゴリを含むデータは以下のような形でレスポンスが返ります。
(データのサンプルは公式ドキュメントからの引用です)
// データの引用元:https://help.microcms.io/ja/knowledge/categories-and-tabs
{
"id": "q1hkeq065",
"createdAt": "2023-02-27T04:32:10.629Z",
"updatedAt": "2023-02-27T04:42:43.015Z",
"publishedAt": "2023-02-27T04:32:10.629Z",
"revisedAt": "2023-02-27T04:42:43.015Z",
"title": "(サンプル)まずはこの記事を開きましょう",
"content": "<h2 id=\"h63ad3943f9\">テスト</h2>",
"eyecatch": {
"url": "https://images.microcms-assets.io/assets/c97ebf4eab9d4a648fa189177cb1f4d5/24a141cafcf74ec0b5b54eddf3fea579/blog-template.png",
"height": 630,
"width": 1200
},
"category": {
"id": "fwiovjn2ggf",
"createdAt": "2023-02-27T04:32:09.660Z",
"updatedAt": "2023-02-27T04:32:09.660Z",
"publishedAt": "2023-02-27T04:32:09.660Z",
"revisedAt": "2023-02-27T04:32:09.660Z",
"name": "チュートリアル"
}
}
Next.jsにおけるカテゴリ一覧ページの構成
カテゴリ一覧ページをNext.jsで作成する場合の構成例としては以下のようなものが考えられます。
app
- page.tsx
- layout.tsx
- categories
- [category]
- page.tsx
- layout.tsx
以下、カテゴリ一覧ページはcategories/[category]配下に一覧ページを作成する想定で記載を進めます。
app直下のpage.tsxの実装例
appフォルダ直下のpage.tsxではカテゴリ一覧ページに遷移するLinkコンポーネントへhref属性でpropsを渡せばOKです。
カテゴリを1つの記事に複数紐付ける実装の場合、カテゴリのデータは配列で渡されるためmapを用いて1つずつ処理させる必要があります。
今回のケースではmicrocms-js-sdkのcreateClientを用いて実装するものとします。
// 本来はエラーハンドリングも必要だが今回は省略
import { createClient } from 'microcms-js-sdk';
export const client = createClient({
serviceDomain: process.env.MICROCMS_SERVICE_DOMAIN,
apiKey: process.env.MICROCMS_API_KEY,
});
import Link from 'next/link';
import { client } from '../libs/client'
import Image from 'next/image'
// TypeScriptの型定義は今回は省略
// microCMSのAPIから記事を取得
async function getBlogPosts() {
const data = await client.get({
endpoint: 'blogs',
queries: {
limit: 10,
}
});
return data.contents;
}
export default async function Home() {
const dataContents = await getBlogPosts();
return (
<>
{dataContents.map((item) => {
return (
<div key={item.id}>
<Link href={`/posts/${item.id}/`}>
{item.thumbnail ?
<Image src={item.thumbnail.url} width={item.thumbnail.width} height={item.thumbnail.width} alt=""></Image>
: <Image src="/images/noimage.jpg" width="960" height="540" alt=""></Image>}
<p>{item.title}</p>
</Link>
// この部分でカテゴリを1つずつ出力し、idを遷移先URLとしている
{item.category.map((cat) => {
return (
<Link href={`/categories/${cat.id}`} key={cat.id}>
<p>{cat.title}</p>
</Link>
)
})}
</div>
)
})}
</>
);
}
カテゴリ別ページのpage.tsxの実装方法
記事データの取得方法
microCMSのAPIを用いて特定のカテゴリに一致するデータの一覧を取得する場合は、filtersを使います。
参考:公式ドキュメント
今回のケース(複数カテゴリ参照)では、記事の持つカテゴリのうち1つが当てはまっていれば良いのでcontainsでコンテンツを取得します。
なお、単にawait client.getで記事APIを参照するだけだと、ページIDとカテゴリIDが一致しない(いわゆる404エラー)場合に500エラーとなってしまいます。
try...catch...でエラーハンドリングをする必要があります。
import { client } from "@/libs/client";
import { notFound } from "next/navigation";
export default async function Page({ params }) {
const { category } = await params;
async function getBlogCategoryArchive() {
try {
const data = await client.get({
endpoint: 'blogs',
queries: {
filters: `category[contains]${category}`,
},
});
return data.contents;
} catch {
notFound();
}
}
const categories = await getBlogCategoryArchive();
// 続きは取得したデータに従ってUIを出力する部分のため、今回は省略
注意点として
const { category } = await params;
で分割代入をする定数名は必ずエンドポイントで設定した名称と同じもの(今回はcategory)としてください。別の名称で指定するとエラーが発生します。
カテゴリ別ページのlayout.tsxの実装方法
カテゴリ別一覧ページを作成する場合、page.tsxだけでなくlayout.tsxの実装も必要になるケースがあります。
例えば「見出しに【(カテゴリ名)の一覧】というようにカテゴリ名を紐付けたい」といったケースでlayout.tsxの実装が必要になってきます。
layout.tsxの実装例としては下記のようになります。
export default async function CategoryLayout({ params, children }) {
const { category } = await params;
async function getCategoryName() {
try {
const data = await client.get({
endpoint: 'category',
queries: {
filters: `id[contains]${category}`,
},
});
return data.contents;
} catch {
notFound();
}
}
const categories = await getCategoryName();
return (
<div>
<p>{categories[0].title}の記事一覧</p>
{children}
<Link href="/">トップに戻る</Link>
</div>
);
}
APIからのデータの取得
第一に、今回のようにカテゴリ名だけ取得したいケースでは記事の取得は不要なため、カテゴリの一覧をAPI経由で取得します。
props経由で渡されたカテゴリ情報のIDとカテゴリIDが一致するものを対象とするため、filtersは
id[contains]${category}で設定します。
先ほどと同様にカテゴリのIDと一致しないpropsが渡された際に404ページを表示させるため、エラーハンドリングのtry...catch...も設定します。
export default async function CategoryLayout({ params, children }) {
const { category } = await params;
async function getCategoryName() {
try {
const data = await client.get({
// カテゴリを参照するためendpointは'category'とする
endpoint: 'category',
queries: {
filters: `id[contains]${category}`,
},
});
return data.contents;
} catch {
notFound();
}
}
layout.tsxではpage.tsxを反映させるためにchildrenもpropsとします。
export default async function CategoryLayout({ params, children })
今回のように複数コンテンツ参照のカテゴリからデータを参照する場合、idを参照して返ってくるデータは配列形式なので、先頭のデータを参照するために[0]を対象にタイトルの文字列を取得しました。
return (
<div>
<p>{categories[0].title}の記事一覧</p>
{children}
<Link href="/">トップに戻る</Link>
</div>
);
最後に
ここまで読んでいただきありがとうございます。
CMSにおけるカテゴリ一覧ページは重要度が高い機能のため、この記事が参考になれば幸いです。
参考資料

