microCMSとNext.jsでのカテゴリーごとのページネーション

komosyu
Next.js

目次

  1. はじめに
  2. カテゴリーページの作成
  3. 各ページでのコンテンツ取得
  4. ページネーションの実装
  5. 参考
  6. さいごに

はじめに

前回の記事で microCMS と Next.js によるカテゴリー絞り込みについて取り上げてみたのですが、カテゴリーページ内でのページネーションについてもつまったところがあったので解説していきたいと思います。

カテゴリーページの作成

まず、カテゴリーページを作成します。

今回は Next.js13 の app ディレクトリで実装しているので、こんな感じのディレクトリ構成でカテゴリーページを作成します。

/src/app/[category]/page/[number]/page.tsx

この[category]に対して、動的ルーティングをしてあげるとカテゴリーページの作成ができるようになります。
そして、[number]には各ページごとの番号をわりふっていきます。

app ディレクトリではgenerateStaticParamsで動的ルーティングを行います。

export async function generateStaticParams() {
  // コンテンツを取得
  const contentsData = await getMicroCMSDataList('contents')
  // コンテンツの合計数
  const { totalCount } = contentsData
  // ページネーションの範囲
  const range = (start: number, end: number) =>
    [...Array(end - start + 1)].map((_, i) => start + i)
  // カテゴリーを取得
  const categoriesData: MicroCMSCategoryData = await getMicroCMSData('category')
  // [category]と[number]に当てはめるための値を設定
  const categoryParams = categoriesData.contents.flatMap((content) =>
    range(1, Math.ceil(totalCount / PER_PAGE)).map((number) => ({
      category: content.english,
      number: number.toString(),
    }))
  )
  return categoryParams
}

こうすることで、例えばcontent.englishfruitとかが入っているのであれば、/fruitというカテゴリーページが作成されるようになりました!

ページネーションの範囲に関しても1~(コンテンツの合計数/1ページに表示する件数)として、[number]にそれぞれはめ込んでページを作成していきます。

各ページでのコンテンツ取得

カテゴリーページが作成できたところで、ページ内のコンテンツを取得していきましょう。

ここで気をつけるのは、カテゴリー内すべてのコンテンツを取得するのではなく、1 ページに表示する分だけのコンテンツ数を取得することです。

そのためには、以下の要素が必要になります。

  • コンテンツ名
  • 何件目から取得するか
  • 1 ページに表示するコンテンツ数
  • 表示したいカテゴリーの id
export default async function Category({ params }) {
  // generateStaticParamsのcategoryParamsから[category][number]の値を取り出す
  const { category, number } = params
  const currentNumber = Number(number)
  // カテゴリー取得
  const categoriesData = await getMicroCMSData('category')
  // 各ページのカテゴリー取得
  const categoryData = categoriesData.contents.filter(
    (content) => content.english === category
  )
  // カテゴリーのidを取得
  const categoryId = categoryData[0]?.id
  // 各ページ内で取得するコンテンツ
  const contentsData = await getMicroCMSDataList(
    'contents',
    (currentNumber - 1) * 10,
    10,
    categoryId
  )
  const { contents, totalCount } = contentsData
}

各ページ内で取得するコンテンツのためのgetMicroCMSDataListはこんな感じのものです。

export async function getMicroCMSDataList(
  contentId, // 取得したいコンテンツ種類
  offsetNumber = 0, // 何件目から取得するか
  limitNumber = 100, // 何件目まで取得するか
  categoryId? // 取得したいカテゴリーのid
) {
  const res = await client.getList({
    endpoint: contentId,
    queries: {
      offset: offsetNumber,
      limit: limitNumber,
      // ここでコンテンツの持つcategoryとcategoryIdが一致していれば
      filters: categoryId ? `category[equals]${categoryId}` : '',
    },
  })
  return res
}

といった感じでカテゴリーごとで/fruit/page/1,/fruit/page/2といった感じのページを実装することができるようになりました!

ページネーションの実装

それでは、最後にページネーションをつくっていきましょう!

ページネーションの実装にはこれらの要素が必要になってくるかと思います。

  • カテゴリー内のコンテンツの合計数
  • カテゴリー名
  • 現在のページ番号

なので、Paginationコンポーネントに渡す props はこんな感じになります。

<Pagination
  totalCount={totalCount} // カテゴリー内のコンテンツの合計数
  pageName={categoryName} // カテゴリー名
  currentNumber={currentNumber} // 現在のページ番号
/>

それでは、本体のPaginationコンポーネントについて見てましょう!

'use client'

import Link from 'next/link'
import { usePathname } from 'next/navigation'
import React from 'react'
import style from './Pagination.module.scss'
import { PER_PAGE } from '@/constants'
import textStyle from '@/styles/Text.module.scss'

export const Pagination = ({ totalCount, pageName, currentNumber }) => {
  // urlを取得
  const router = usePathname()
  // 何ページまで作成するか(1,2,3...)
  const allPageNumber = Math.ceil(totalCount / PER_PAGE)
  // 現在のページ(カテゴリートップページの場合は1を返すようにする)
  let pageNumber: number
  if (!router?.includes('page')) {
    pageNumber = 1
  } else {
    pageNumber = currentNumber
  }

  const paginationGenerator = (
    pageNumber,
    allPageNumber,
    width = 2 // 左右にいくつ番号を表示するか
  ) => {
    const left = pageNumber - width // 現在ページの左側に表示するページ番号の最大値
    const right = pageNumber + width + 1 // 現在ページの右側に表示するページ番号の最大値
    const ranges = [] //  表示するページ番号の候補を格納する配列
    const rangeWithDots = [] // ページ番号とドットを含めた、最終的に表示するページ番号を格納する配列
    let length //  前のページ番号を格納する変数
    // 1ページ目と最後のページ、および現在のページを中心としたページ番号の範囲をranges配列に格納
    for (let i = 1; i <= allPageNumber; i += 1) {
      if (i === 1 || i === allPageNumber || (i >= left && i <= right)) {
        ranges.push(i)
      } else if (i < left) {
        i = left - 1
      } else if (i > right) {
        ranges.push(allPageNumber)
        break
      }
    }

    // rangesに格納されたページ番号をそれぞれ返す
    ranges.forEach((range) => {
      if (length) {
        if (range - length === 3) {
          rangeWithDots.push(length + 1)
        } else if (range - length !== 1) {
          rangeWithDots.push('...')
        }
      }
      rangeWithDots.push(range)
      length = range
    })
    return rangeWithDots
  }

  return (
    <>
      {allPageNumber > 1 && (
        <nav className={style.container}>
          <Link
            href={`/${pageName}/page/${pageNumber - 1}`}
            className={`${style.prev} ${style.itemLink} ${pageNumber === 1 && style.current}`}
          >
            前のページに戻る
          </Link>
          <ul className={style.list}>
            {paginationGenerator(pageNumber, allPageNumber).map((page, index) => (
              <li key={index} className={style.item}>
                {typeof page === 'number' ? (
                  <Link
                    href={`/${pageName}/page/${page}`}
                    className={`${textStyle.enMd} ${style.itemLink} ${
                      page === pageNumber && style.current
                    }`}
                  >
                    {page}
                  </Link>
                ) : (
                  <span className={`${textStyle.enMd} ${style.itemDott}`}>{page}</span>
                )}
              </li>
            ))}
          </ul>
          <Link
            href={`/${pageName}/page/${pageNumber + 1}`}
            className={`${style.next} ${style.itemLink} ${
              pageNumber === allPageNumber && style.current
            }`}
          >
            次のページに戻る
          </Link>
        </nav>
      )}
    </>

参考

さいごに

前回に引き続き、microCMS について書いてみました。

私だけかもしれませんが、カテゴリー絞り込みの際のページネーションでつまるところがあったので、同じような方の参考になればと思います!