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

Next.jsを使用して、`params`を使った動的ルーティングとページネーションを組み合わせた実装を紹介します。
記事のデータを用意して、それぞれのページにうまく表示させていきます。
また、トップページはページ番号で言うと1になり、ルーティングは必要ないので固定するイメージで行なっていきます。
## トップページ(1ページ目)
こちらが今回表示したいデータです。
```tsx
// 仮データ
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' },
];
```
トップページから実装していきます。
```tsx
// 1ページあたりのアイテムの数
export const POSTS_PER_PAGE = 5;

// 合計何ページ必要か計算
export const totalPages = Math.ceil(mockPosts.length / POSTS_PER_PAGE);
```
1ページあたり5つデータを表示するとしましょう。また、必要なページ数は全部で2ページになります。(`Math.ceil()`を使い、中の数字を切り上げます。今回は綺麗に割り切れてます。)

```src/app/page.tsx
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つになります。

![スクショ](https://i.imgur.com/ob2mCPc.jpeg)


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

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

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

![スクショ2](https://i.imgur.com/yn1Ixiv.jpeg)

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

## 動的ルーティングページ

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

```src/app/post/[page]/page.tsx
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()`で取ります。
```tsx
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）や、現在のページ番号が変わったとしても使える汎用的な式です。

次に先ほどと同じように配列を作ってページネーションを実装していきます。
```tsx
const isActive = currentPage - 1 === i;
```
ページネーションの数字が現在のページ番号に等しい場合、ルーティングを行いたくないので後にこの式で分岐して`span`要素を使います。

```tsx
if (i === 0) {
  return (
    <Link key={i} href="/">
      {i + 1}
    </Link>
  );
}
```
`i === 0`の時つまりページ番号が1の時は、トップページに遷移させたいのでこのように条件分岐します。

次に2つ目の条件分岐です。
現在表示されているページ番号には、ルーティングをさせたくないのでこのように分岐させます。
```tsx
return isActive ? (
  <span key={i}>{i + 1}</span>
) : (
  <Link key={i} href={`/post/${i + 1}`}>
    {i + 1}
  </Link>
);
```

2ページ目はこのように表示されてます。
![スクショ3](https://i.imgur.com/YsXozLz.jpeg)


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