0
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】paramsを使ってページネーションを実装する

0
Posted at

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()を使い、中の数字を切り上げます。今回は綺麗に割り切れてます。)

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つになります。

スクショ

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

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

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

スクショ2

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

動的ルーティングページ

次に実際にこのページネーションで遷移されるページを実装していきます。
/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()で取ります。

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やデータの数を増やしたりするともっとわかりやすいと思います。
ぜひ皆さんも試してみて下さい。

0
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
0
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?