ページネーションの仕組み
投稿日: 2025年03月04日
ページネーションの実装は、オリジナルアプリで挑戦する方も多いかと思います。
私は実務課題で初めて実装しました。
ページネーションのロジックがよくわからなくて、娘の習い事の待ち時間にスマホで調べた記憶があります。
これからページネーション実装する方もいらっしゃると思うので、この記事では処理の流れを中心にバックエンドとフロントエンド両方について触れて解説してみたいと思います。
バックエンドで1ページあたり30件などと固定する場合もあれば、フロントエンドから取得件数を指定する場合もあります。
取得する場合はクエリパラメータで受け取ります。
こちらもクエリパラメータで受け取り、データを取得します。
フロントエンドの仕様にもよりますが、全体件数(totalCount)を取得することで、現在の表示件数や全ページ数を計算できるようになります。
export const GET = async (request: NextRequest) => {
const prisma = await buildPrisma();
try {
const url = new URL(request.url);
//クエリパラメータのpageを取得、なければ0ページ目とする
const page = parseInt(url.searchParams.get("page") || "0");
//1ページ辺り15データ
const pageSize = 15;
//何件スキップするか定義
const skip = page * pageSize;
//何件取得するか定義(フロント側から取得する場合はpageSize不要で取得した値を代入)
const take = pageSize;
//検索機能もある場合は検索ワード取得して変数に格納
//ここでは省略するが、whereConditionにwhere句の内容いれている
const [recipeArticles, totalCount] = await Promise.all([
prisma.recipeArticle.findMany({
where: whereCondition//絞込みがある場合のみ
skip,
take,
orderBy: { createdAt: "desc" },
include: {
maltArticle: true,
user: true,
},
}),
//全件数を取得する
prisma.recipeArticle.count({
where: whereCondition,//絞込みがある場合のみ
}),
]);
//何ぺージあるかを計算する
const totalPages = Math.ceil(totalCount / take);
//返却する
こんな感じです。
次にフロント側です。
現在のクエリパラメータのpageをステートで管理します。
要件によっては、URL操作でページを切り替えられるようにすることもあります。
「次へ」「前へ」ボタンを押すたびに page の値を変更し、リクエストを送ってデータを取得・表示する流れになります。
下記のようにしてリクエストします。
/api/recipes?page=${currentPage}
これでcurrentPageの値をバックエンドに渡せて現在のページに応じたデータを受け取ることが出来ます。
//useSearchParamsState.ts
"use client";
import { useSearchParams } from "next/navigation";
import { useState, useEffect } from "react";
export const useSearchParamsState = () => {
//URLのpageを取得、なければ0にして文字列→数値に変える
const searchParams = useSearchParams();
const page = parseInt(searchParams?.get("page") || "0", 10);
//URLのpageをステートにセットする
const [currentPage, setCurrentPage] = useState(page);
//ボタン操作等で現在のページが変更されたらURLのpageも変更する
useEffect(() => {
const params = new URLSearchParams(window.location.search);
params.set("page", currentPage.toString());
const newUrl = `${window.location.pathname}?${params.toString()}`;
window.history.replaceState({}, "", newUrl);
}, [currentPage]);
return {
currentPage,
setCurrentPage,
};
};
react-paginate
を使用して実装する例です。
"use client";
import ReactPaginate from "react-paginate";
interface Props {
//バックエンドで用意したtotalPages
pageCount: number;
//フックで定義したステート
currentPage: number;
//フックで定義したステートの更新関数
setCurrentPage: (currentPage: number) => void;
}
export const Paginate: React.FC<Props> = ({
pageCount,
currentPage,
setCurrentPage,
}) => {
const handlePageChange = (e: { selected: number }) => {
setCurrentPage(e.selected);
};
const baseClassName =
"py-1 px-2 w-8 border rounded text-center block border-dark_brown";
return (
<div className="py-3 w-full bg-white/30 flex justify-end">
<ReactPaginate
//前のページへのボタンのラベル
previousLabel={"前"}
//次のページへのボタンのラベル
nextLabel={"次"}
//総ページ数
pageCount={pageCount}
//最初と最後に表示するページ番号の数
marginPagesDisplayed={2}
//現在のページの前後に表示するページ番号の数
pageRangeDisplayed={5}
// ページ変更時に実行する関数
onPageChange={handlePageChange}
//現在のページを強制的に指定する
forcePage={currentPage}
//スタイル
containerClassName={"flex gap-2"}
previousLinkClassName={`${baseClassName} mr-4 ml-3`}
nextLinkClassName={`${baseClassName} ml-4`}
pageLinkClassName={baseClassName}
activeLinkClassName={"bg-gray_bg"}
/>
</div>
);
};
これだけで下記のようなページネーションの実装が出来ます!
ページネーションは、バックエンドとフロントエンドが連携して動く仕組みです。
バックエンド は、指定された page
などのクエリパラメータをもとにデータを取得し、全体件数 (totalCount
) も返す。
フロントエンド は、現在のページ番号を管理し、リクエストを送信する。レスポンスを元にデータを表示し、ユーザー操作に応じてページを切り替える。
ロジックが理解できれば、ライブラリを活用することでスムーズに実装できます。
どんなロジックなのかザックリでも理解できると、その実現のためにどんな処理をしたらいいかわかってきて自分で書けるようになると思いますので、簡潔にまとめたつもりです。
ロジックを理解するためには、まずコードを見ずに「何をすればよいか」を考えるのが効果的かもしれません。
実務では、フロントエンドの実装担当であれば、「クエリパラメータでpageを送って」 などと指定されることが多いと思います。
page以外にlimit(取得する件数)や検索機能もあると、keyword(検索ワード),from,to(日付絞込み)などをクエリパラメータで渡すこともあると思います。
そのため、フロントエンドは 指定された通りにリクエストを送り、返ってきたデータを表示、ユーザー操作に応じてpageの切替をし、データを再取得して表示するという流れになります。
参考になれば嬉しいです!