1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Next.js × microCMSで作ったブログにカテゴリページを実装

Last updated at Posted at 2024-06-08

はじめに

ブログサイトを作る過程で学んだことを、備忘録目的で投稿しています。
自身は駆け出しエンジニアであり、React自体がほぼ初学者のため、誤った認識・理解をしている可能性があります。
万が一参考にする場合は、上記の点を考慮した上でご一読ください。
また、スタイリングについては割愛しています。

この記事は、過去に投稿したNext.js × microCMSでブログサイト作成した際に学んだことを書き留めるのプロジェクトを元に説明しています。

目的

記事一覧に、カテゴリで絞り込みをしたカテゴリページを作成する。

作業環境

Windows10 Pro x64
Node.js: 20.14.0
npm: 10.3.0
Next.js: 14.2.3(App Router使用)

手順

カテゴリ一覧を取得

microCMSで登録したカテゴリを全て取得します、作りは記事一覧と同じなので説明は割愛します。

src/libs/microcms.js
// カテゴリー一覧を取得
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}}とします。
それ以外は記事一覧のときとほぼ同じなので説明は割愛します。

/src/app/page.jsx
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>
  );
}

src/components/Categories/index.jsx
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>
  );
};

src/components/Category/index.jsx
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>
  );
};

image.png

カテゴリページの作成

カテゴリページを作成します。
記事一覧ページのファイルをベースとするため、記事一覧のファイルをコピーしたのち、
appフォルダ配下にコピーしたpage.jsx/app/category/[categoryId]/page.jsxとして作成します。

page.jsxに追記していきます。
まず、{ params }でURLのカテゴリIDを取得し、それをクエリパラメータのfiltersを使用して絞り込みします。
これで、APIでデータ取得したさいに必要なデータのみを取得することができます。
細かい意味は、microCMSのリファレンスを参考にすると良いとおもいます。

/app/category/[categoryId]/page.jsx
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>
  );
}

image.png

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と同じですので説明は割愛します。

/src/app/category/[categoryId]/page.jsx
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)

参考

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?