microCMSにカテゴリAPIを作成しカテゴリ一覧ページとカテゴリ一覧ボタンのサイドバーを表示する
新たにカテゴリページを追加したので、API作成からNext.jsへの反映までを記事にまとめました。
▼変更前・変更後
- カテゴリアーカイブ一覧の作成
- カテゴリのサイドバーを追加
microCMSのAPI設定
カテゴリ用のAPIを作成
作成したAPIスキーマのフィールドは以下の通り
- タイトル
- スラッグ
両フィールドとも必須設定。
スラッグは、カテゴリアーカイブURLにも使用するため重複しないようにユニーク設定を有効にしました。
重複を許可しない(ユニーク)
他のコンテンツで同じ値がある場合にはコンテンツを保存できなくなります。1つのAPIに対して最大で5つのフィールドに設定が可能です。
記事APIのAPIスキーマにカテゴリのフィールドを追加
種類 - 複数コンテンツ参照を選択。
このままでは記事作成画面でカテゴリを選択すると参照元のコンテンツIDが登録・表示され、何のカテゴリを選択したのかわからなくなります。
種類を選択したあとは、詳細で一覧画面に表示する項目を「タイトル」に変更することをおすすめします。
記事にカテゴリを紐づけてmicroCMSの設定は完了です。
Next.jsにカテゴリを表示
追加したフォルダ・ファイル
もともと作成していたファイルは除き、カテゴリ情報を追加・表示するために追加したフォルダ・ファイルの一覧です。
📁src
├ 📁api
│└ 📁cateogry
│ └ index.ts
├ 📁components
│ └ CategoryList.tsx
├ 📁pages
│└ 📁category
│ ├ 📁[slug]
│ │ └ index.tsx
│ ├ 📁page
│ │ └ [num].tsx
├ 📁types
│ └ category.ts
└ 📁utils
└ fetchCategory.ts
APIクライアントの作成
microCMS公式ブログでも紹介されているaspidaでカテゴリ用のAPIクライアントを作成します。
複数ページで読み込めるように使用するため以下のファイルで
api/category/index.tsx
// api/content/index.ts
import { MicroCMSListResponse, MicroCMSQueries } from 'microcms-js-sdk';
import { Category } from '@/types/category';
export type Methods = {
get: {
query?: MicroCMSQueries,
resBody: MicroCMSListResponse<Category>
}
}
カテゴリ一覧を作る
- カテゴリを全件取得(件数が多くないので一旦limit:300で取得)
- カテゴリのスラッグフィールドをページURLに使用するのでフォルダ名を[slug]とする
- 2ページ目以降はpageフォルダに[num].tsxを作成
src/utils/fetchCategory.ts
import { client } from "@/utils/client"
export const fetchCategories = () => {
const categories = client.category.$get({
query: {
offset: 0,
limit: 300,
},
});
return categories;
};
src/pages/category/[slug]/index.tsx
import { fetchCategories } from "@/utils/fetchCategory";
//略
export const getStaticPaths: GetStaticPaths = async () => {
const data = await fetchCategories();
const paths = data.contents.map((content) => `/category/${content.slug}`);
return { paths, fallback: false };
};
export const getStaticProps: GetStaticProps = async (context) => {
const slug = context.params?.slug;
const categories = await fetchCategories();
const filterCategories = await client.category.$get({
query: {
filters: `slug[equals]${slug}`,
},
});
const category = filterCategories.contents[0];
const data = await client.blog.$get({
query: {
limit: BLOG_POSTS_PER_PAGE,
filters: `category[contains]${category.id}`,
},
});
return {
props: {
category,
categories: categories.contents,
postsData: data.contents,
totalCount: data.totalCount,
offset: data.offset,
},
};
};
//略
カテゴリのスラッグがイコールの場合・・としてしまいそうだが、category[equals]${category.slug}
では取得できない。
filters: category[contains]${category.id}
記事対してカテゴリを「複数のコンテンツ参照」で紐づけているためフィルタリング機能で取得する
category(記事のカテゴリフィールド)[contains](絞り込む)${category.id}(カテゴリのコンテンツID)
2ページ目以降の一覧を作る
src/pages/category/[slug]/page/[num].tsx
export const getStaticPaths: GetStaticPaths = async () => {
const categories = await fetchCategories();
const resPaths = await Promise.all(
categories.contents.map((category) => {
const result = client.blog
.$get({
query: {
offset: BLOG_POSTS_PER_PAGE,
limit: 300,
filters: `category[contains]${category.id}`,
},
})
.then(({ totalCount }) => {
if (totalCount <= BLOG_POSTS_PER_PAGE) return [];
const range = (start: number, end: number) =>
[...Array(end - start + 1)].map((_, i) => start + i);
return range(2, Math.ceil(totalCount / BLOG_POSTS_PER_PAGE)).map(
(i) => `/category/${category.slug}/page/${i}`
);
});
return result;
})
);
const paths = resPaths.flat();
return {
paths,
fallback: false,
};
};
offset: BLOG_POSTS_PER_PAGE,
1ページ目は、src/pages/category/[slug]/index.tsx で表示しているので記事の開始位置を表示件数分ずらす
if (totalCount <= BLOG_POSTS_PER_PAGE) return [];
1ページあたりの表示件数より記事数が少ない場合は、空配列を返す
return range(2, Math.ceil(totalCount / BLOG_POSTS_PER_PAGE)).map(
1ページ目はpathsに含めないためrangeのstart=2とする
参考:Next.js×microCMSでタグ機能を作成する![ネストされた動的なルーティング][getStaticPaths]
pathsの中が知りたい・・
console.log(paths)
Google Chromeデベロッパーツール>コンソールでは確認できません。
getStaticPaths will only run during build in production, it will not be called during runtime.
公式ドキュメントで紹介されているツールかVScodeのターミナルで中身が確認できます。
npm run dev で実行し、該当ページにアクセスするとVSCodeのターミナルにpathsの値が表示されます。
各カテゴリ一覧へのリンクボタン(サイドバー)を作って各ページで読み込む
src/components/CategoryList.tsx
import { Content } from "@/api/types";
import Link from "next/link";
import styles from "./CategoryList.module.scss";
import clsx from "clsx";
type CategoryListProp = {
categories?: Content[];
currentSlug?: string;
};
export const CategoryList = ({ categories, currentSlug }: CategoryListProp) => {
return (
<div className={styles.wrap}>
<p className={styles.head}>Category</p>
<ul className={styles.list}>
{categories &&
categories.map((category, index) => {
return (
<li
key={index}
className={clsx(
styles.item,
currentSlug === category.slug && styles.current
)}
>
<Link
href={`/category/${category.slug}/`}
className={styles.link}
>
{category.title}
</Link>
</li>
);
})}
</ul>
</div>
);
};
作成したCategoryListコンポーネントを各ページに読み込む
<CategoryList categories={categories} />
//カテゴリ一覧にはカレントページのslug情報を渡す
<CategoryList categories={categories} currentSlug={category.slug} />