はじめに
ブログサイトを作る過程で学んだことを、備忘録目的で投稿しています。
自身は駆け出しエンジニアであり、React自体がほぼ初学者のため、誤った認識・理解をしている可能性があります。
万が一参考にする場合は、上記の点を考慮した上でご一読ください。
また、スタイリングについては割愛しています。
この記事は、過去に投稿したNext.js × microCMSでブログサイト作成した際に学んだことを書き留めるのプロジェクトを元に説明しています。
目的
記事一覧に、カテゴリで絞り込みをしたカテゴリページを作成する。
作業環境
Windows10 Pro x64
Node.js: 20.14.0
npm: 10.3.0
Next.js: 14.2.3(App Router使用)
手順
カテゴリ一覧を取得
microCMSで登録したカテゴリを全て取得します、作りは記事一覧と同じなので説明は割愛します。
// カテゴリー一覧を取得
export const getCategoriesList = async (queries) => {
try {
const response = await client.getList({
endpoint: "categories",
queries,
});
return response;
} catch (error) {
console.error("getCategoriesListでエラーが発生しました", error);
notFound();
}
};
取得したカテゴリを表示します。
カテゴリページへのリンク先はhref={
/category/${category.id}}
とします。
それ以外は記事一覧のときとほぼ同じなので説明は割愛します。
import { notFound } from "next/navigation";
import styles from "./page.module.scss";
import { LIMIT } from "@/constants";
import { Cards } from "@/components/Cards";
import { Categories } from "@/components/Categories";
import { getArticlesList, getCategoriesList } from "@/libs/microcms";
export default async function Home() {
// ブログ一覧を取得
const articlesListQueries = { limit: LIMIT };
const articlesListResponse = await getArticlesList(articlesListQueries).catch(() => notFound());
const { contents: articles } = articlesListResponse;
// カテゴリ一覧を取得
const categoriesListQueries = { limit: 100 };
const categoriesListResponse = await getCategoriesList(categoriesListQueries).catch(() => notFound());
const { contents: categories } = categoriesListResponse;
return (
<main className={styles.main}>
<h1 className={styles.title}>全ての記事</h1>
<div className={styles.container}>
<div>
<p>記事一覧</p>
<ul className={styles.cards}>
<Cards articles={articles} />
</ul>
</div>
<div>
<p>カテゴリ一覧</p>
<ul className={styles.cards}>
<Categories categories={categories} />
</ul>
</div>
</div>
</main>
);
}
import styles from "./index.module.scss";
import { Category } from "@/components/Category";
export const Categories = async ({ categories }) => {
return (
<ul className={styles.categories}>
{categories.map((category) => (
<Category category={category} key={category.id} />
))}
</ul>
);
};
import Link from "next/link";
import styles from "./index.module.scss";
export const Category = async ({ category }) => {
return (
<li className={styles.category}>
<Link href={`/category/${category.id}`} className={styles.link}>
{category.name}
</Link>
</li>
);
};
カテゴリページの作成
カテゴリページを作成します。
記事一覧ページのファイルをベースとするため、記事一覧のファイルをコピーしたのち、
app
フォルダ配下にコピーしたpage.jsx
を/app/category/[categoryId]/page.jsx
として作成します。
page.jsx
に追記していきます。
まず、{ params }
でURLのカテゴリIDを取得し、それをクエリパラメータのfilters
を使用して絞り込みします。
これで、APIでデータ取得したさいに必要なデータのみを取得することができます。
細かい意味は、microCMSのリファレンスを参考にすると良いとおもいます。
import { notFound } from "next/navigation";
import styles from "./page.module.scss";
import { LIMIT } from "@/constants";
import { getArticlesList, getCategoriesList } from "@/libs/microcms";
import { Cards } from "@/components/Cards";
import { Categories } from "@/components/Categories";
export default async function Page({ params }) {
// URLからカテゴリIDを取得
const currentCategory = params.categoryId;
// ブログ一覧を取得
const filters = `category[equals]${currentCategory}`;
const articlesListQueries = { limit: LIMIT, filters: filters };
const articlesListResponse = await getArticlesList(articlesListQueries).catch(() => notFound());
const { contents: articles } = articlesListResponse;
// カテゴリ一覧を取得
const categoriesListQueries = { limit: 100 };
const categoriesListResponse = await getCategoriesList(categoriesListQueries).catch(() => notFound());
const { contents: categories } = categoriesListResponse;
return (
<main className={styles.main}>
<h1 className={styles.title}>「{currentCategory}」の記事</h1>
<div className={styles.container}>
<div>
<p>記事一覧</p>
<ul className={styles.cards}>
<Cards articles={articles} />
</ul>
</div>
<div>
<p>カテゴリ一覧</p>
<ul className={styles.cards}>
<Categories categories={categories} />
</ul>
</div>
</div>
</main>
);
}
SSGに対応する
SSG(Static Site Generation)の静的なファイルとして生成するようにします。
現時点でnpm run build
を実行すると、下記のようになっており、カテゴリページは動的レンダリングになっているのがわかります。
Route (app) Size First Load JS
┌ ○ / 427 B 99.3 kB
├ ○ /_not-found 871 B 87.8 kB
├ ● /articles/[slug] 285 B 92.4 kB
├ ├ /articles/3bqtpjwckpa6
├ ├ /articles/2la1jxzdhmy
├ └ /articles/yy3_bxzq2f-0
└ ƒ /category/[categoryId] 390 B 99.2 kB
+ First Load JS shared by all 87 kB
├ chunks/23-0627c91053ca9399.js 31.5 kB
├ chunks/fd9d1056-2821b0f0cabcd8bd.js 53.6 kB
└ other shared chunks (total) 1.89 kB
○ (Static) prerendered as static content
● (SSG) prerendered as static HTML (uses getStaticProps)
ƒ (Dynamic) server-rendered on demand
SSGに対応するために、generateStaticParams
を追記します。
考え方は記事詳細ページでのgenerateStaticParams
と同じですので説明は割愛します。
import { notFound } from "next/navigation";
import styles from "./page.module.scss";
import { LIMIT } from "@/constants";
import { getArticlesList, getCategoriesList } from "@/libs/microcms";
import { Cards } from "@/components/Cards";
import { Categories } from "@/components/Categories";
// カテゴリーページの静的パスを作成
export async function generateStaticParams() {
// カテゴリ一覧を取得
const queries = { limit: 100, fields: "id" };
const categoriesListResponse = await getCategoriesList(queries);
const { contents: categories } = categoriesListResponse;
const paths = categories.map((category) => {
return {
categoryId: category.id,
};
});
// 作成したパスの配列を返します。
return [...paths];
}
export default async function Page({ params }) {
// URLからカテゴリIDを取得
const currentCategory = params.categoryId;
// ブログ一覧を取得
const filters = `category[equals]${currentCategory}`;
const articlesListQueries = { limit: LIMIT, filters: filters };
const articlesListResponse = await getArticlesList(articlesListQueries).catch(
() => notFound()
);
const { contents: articles } = articlesListResponse;
// カテゴリ一覧を取得
const categoriesListQueries = { limit: 100 };
const categoriesListResponse = await getCategoriesList(
categoriesListQueries
).catch(() => notFound());
const { contents: categories } = categoriesListResponse;
return (
<main className={styles.main}>
<h1 className={styles.title}>「{currentCategory}」の記事</h1>
<div className={styles.container}>
<div>
<p>記事一覧</p>
<ul className={styles.cards}>
<Cards articles={articles} />
</ul>
</div>
<div>
<p>カテゴリ一覧</p>
<ul className={styles.cards}>
<Categories categories={categories} />
</ul>
</div>
</div>
</main>
);
}
この状態でnom run build
を実行してみると、カテゴリーページが静的生成に変わったのがわかります。
Route (app) Size First Load JS
┌ ○ / 427 B 99.3 kB
├ ○ /_not-found 871 B 87.8 kB
├ ● /articles/[slug] 285 B 92.4 kB
├ ├ /articles/3bqtpjwckpa6
├ ├ /articles/2la1jxzdhmy
├ └ /articles/yy3_bxzq2f-0
└ ● /category/[categoryId] 390 B 99.2 kB
├ /category/html
├ /category/css
└ /category/javascript
+ First Load JS shared by all 87 kB
├ chunks/23-0627c91053ca9399.js 31.5 kB
├ chunks/fd9d1056-2821b0f0cabcd8bd.js 53.6 kB
└ other shared chunks (total) 1.89 kB
○ (Static) prerendered as static content
● (SSG) prerendered as static HTML (uses getStaticProps)
参考