【Next.js】paramsを使ってページネーションを実装する

2025年6月24日

【Next.js】paramsを使ってページネーションを実装する

Next.jsを使用して、paramsを使った動的ルーティングとページネーションを組み合わせた実装を紹介します。
記事のデータを用意して、それぞれのページにうまく表示させていきます。
また、トップページはページ番号で言うと1になり、ルーティングは必要ないので固定するイメージで行なっていきます。

トップページ(1ページ目)

こちらが今回表示したいデータです。

// 仮データ
export const mockPosts = [
  { id: 1, title: 'Getting Started with Next.js' },
  { id: 2, title: 'Understanding App Router in Next.js 15' },
  { id: 3, title: 'Using Prisma with PostgreSQL' },
  { id: 4, title: 'Building a Blog with Markdown Support' },
  { id: 5, title: 'Adding Image Upload to Your Forms' },
  { id: 6, title: 'Implementing Tag Filtering' },
  { id: 7, title: 'Responsive Design with Tailwind CSS' },
  { id: 8, title: 'Authentication with next-auth' },
  { id: 9, title: 'Previewing Markdown in Real Time' },
  { id: 10, title: 'Deploying to Vercel' },
];

トップページから実装していきます。

// 1ページあたりのアイテムの数
export const POSTS_PER_PAGE = 5;

// 合計何ページ必要か計算
export const totalPages = Math.ceil(mockPosts.length / POSTS_PER_PAGE);

1ページあたり5つデータを表示するとしましょう。また、必要なページ数は全部で2ページになります。(Math.ceil()を使い、中の数字を切り上げます。今回は綺麗に割り切れてます。)

export default function Page() {
  // 1ページあたりに必要なアイテムの数だけsliceする
  const paginatedPosts = mockPosts.slice(0, POSTS_PER_PAGE);
  
  return (
    <div className="mx-auto">
      <div className="grid grid-cols-5 gap-4">
        {paginatedPosts.map(post => (
          <div key={post.id}>
            {post.title}
          </div>
        ))}
      </div>
      <div className="flex gap-4">
        {Array.from({ length: totalPages }).map((_, i) =>
          i === 0 ? (
            <span key={i} className="bg-blue-300 px-3 py-1 rounded">
              {i + 1}
            </span>
          ) : (
            <Link
              key={i}
              href={`/post/${i + 1}`}
              className="bg-blue-300 px-3 py-1 rounded"
            >
              {i + 1}
            </Link>
          )
        )}
      </div>
    </div>
  );
}

1ページ目なので、slice()で0番目からスタートすれば必要な数だけデータを取れます。そして実際に表示されるデータは、mockPosts[0]mockPosts[4]の5つになります。

スクショ

次にルーティングの部分です。

ページ番号を表示するためtotalPagesを用いて、ページ数の合計を長さとする配列をArray.from()で作ります。今回は全部で2ページなので、長さが2の配列です。
<Link>コンポーネントの/post/${i + 1}で、ページ番号をパラメータとしてparamsを使えるようにルーティングします。

また、i === 0の時、つまり1ページ目の時はルーティングをさせたくないので(トップページのままでいいから)span要素を使って表示しています。

スクショ2

トップページの実装はとりあえずこのような感じです。

動的ルーティングページ

次に実際にこのページネーションで遷移されるページを実装していきます。
/post/[page]/page.tsxにコードを書いていきます。先に書いた/post/${i + 1}で表示されるページになります。

const Page = async ({ params }: Params) => {
  const { page: currentPage } = await params;

  // 1ページあたりに必要なデータだけ取得
  const paginatedPosts = mockPosts.slice(
    POSTS_PER_PAGE * (currentPage - 1),
    POSTS_PER_PAGE * currentPage
  );

  return (
    <div>
      <div>
        {paginatedPosts.map(post => (
          <div key={post.id}>
            <p>{post.title}</p>
          </div>
        ))}
      </div>
      <div>
        {Array.from({ length: totalPages }).map((_, i) => {
          const isActive = currentPage - 1 === i;
          if (i === 0) {
            return (
              <Link key={i} href="/">
                {i + 1}
              </Link>
            );
          }
          return isActive ? (
            <span key={i}>
              {i + 1}
            </span>
          ) : (
            <Link
              key={i}
              href={`/post/${i + 1}`}
            >
              {i + 1}
            </Link>
          );
        })}
      </div>
    </div>
  );
};

まずparamsから現在のページ番号(currentPage)を取得します。
そして1ページあたりに必要なデータだけslice()で取ります。

const paginatedPosts = mockPosts.slice(
  POSTS_PER_PAGE * (currentPage - 1),
  POSTS_PER_PAGE * currentPage
);

この式は少し複雑ですが、一般化された形になっています:

  • 1ページ目(currentPage = 1): slice(0, 5) → インデックス0〜4の記事
  • 2ページ目(currentPage = 2): slice(5, 10) → インデックス5〜9の記事

1ページあたりに表示したい記事数(POSTS_PER_PAGE)や、現在のページ番号が変わったとしても使える汎用的な式です。

次に先ほどと同じように配列を作ってページネーションを実装していきます。

const isActive = currentPage - 1 === i;

ページネーションの数字が現在のページ番号に等しい場合、ルーティングを行いたくないので後にこの式で分岐してspan要素を使います。

if (i === 0) {
  return (
    <Link key={i} href="/">
      {i + 1}
    </Link>
  );
}

i === 0の時つまりページ番号が1の時は、トップページに遷移させたいのでこのように条件分岐します。

次に2つ目の条件分岐です。
現在表示されているページ番号には、ルーティングをさせたくないのでこのように分岐させます。

return isActive ? (
  <span key={i}>{i + 1}</span>
) : (
  <Link key={i} href={`/post/${i + 1}`}>
    {i + 1}
  </Link>
);

2ページ目はこのように表示されてます。
スクショ3

まとめ

以上の方法で基本的なページネーションを動的ルーティングと組み合わせて実装できます。 
POSTS_PER_PAGEやデータの数を増やしたりするともっとわかりやすいと思います。
ぜひ皆さんも試してみて下さい。

タグ

コメント

シェア:

関連記事