この記事の対象読者
- Prismaで一覧画面を作り始めた人
-
skipとtakeは使ったことがあるが、cursorはまだよく分からない人 - ページネーションの選び方に迷っている人
本題に入る前に
この話は、投稿一覧、予約一覧、注文履歴など、件数が増える一覧画面を実装する人を想定しています。UIとしてページ番号が必要なのか、もっと見る形式でよいのかを考える開発担当者に向けた内容です。
最近、業務画面を作るときに、一覧画面の体験は思った以上に大事だと感じます。ユーザー一覧、予約一覧、注文履歴、問い合わせ一覧など、業務アプリには「大量のデータを探して確認する画面」がたくさんあります。
一覧画面は地味ですが、毎日使う人にとっては仕事場そのものです。ページを送るたびに遅い、同じデータがまた出る、さっき見た行が消える、となると小さなストレスが積み重なります。
最初は件数が少なくても、サービスが使われるほどデータは増えていきます。全部を一度に表示する作りのままだと、DBにも画面にも負担がかかります。この記事では、一覧を少しずつ表示するためのページネーションを、UI要件とデータ量の両方から考えます。
一覧画面でページを分けたくなる理由
一覧画面を作っていると、データが10件くらいのうちは何も困りません。全部表示しても軽いし、見た目も問題ありません。
でも、投稿、予約、ユーザー、注文履歴のようなデータはどんどん増えます。1万件のデータを一度に画面へ返すと、DBにもサーバーにもブラウザにも負担がかかります。ユーザーもそんな長い一覧は読み切れません。
そこで、データを少しずつ区切って表示します。これがページネーションです。本でたとえるなら、分厚い本を1ページずつ読むようなものです。
Prismaは、TypeScriptからDBを扱いやすくするORMです。SQLを直接たくさん書かなくても、findMany のようなメソッドでデータを取得できます。その中で一覧を区切る方法として、skip/take と cursorを使う方法があります。
この記事では「どちらが偉いか」ではなく、「どんな画面ならどちらが自然か」を考えます。
ページネーションはSQLでも実装できますし、GraphQLやAPI設計でも考えるテーマです。この記事では、手元でイメージしやすいようにPrismaのコードで説明します。
offset pagination
offset paginationは、何件スキップして何件取るかを指定する方法です。
Prismaでは skip と take を使います。
const page = 3;
const perPage = 10;
const posts = await prisma.post.findMany({
skip: (page - 1) * perPage,
take: perPage,
orderBy: {
createdAt: "desc",
},
});
分かりやすいので、管理画面のように「1ページ目、2ページ目、3ページ目」と移動したいUIに向いています。
ただし、データがとても多くなり、深いページまで移動すると、スキップする件数も増えます。また、一覧の途中でデータが追加・削除されると、ページをまたいだときに表示がずれることがあります。
cursor pagination
cursor paginationは、「このデータの次から取る」という考え方です。
const posts = await prisma.post.findMany({
cursor: {
id: "post_100",
},
skip: 1,
take: 10,
orderBy: {
id: "asc",
},
});
skip: 1 は、cursorに指定したレコード自身を結果から外すために使います。
cursor paginationは、無限スクロールや「もっと見る」ボタンと相性がよいです。
createdAtだけで並べると少し怖い
一覧では createdAt desc で並べたくなることが多いです。
ただ、同じ時刻のデータが複数あると順番が不安定になることがあります。実務では createdAt と id のように、最後に一意になる値も含めて順序を安定させることを考えます。
const posts = await prisma.post.findMany({
take: 10,
orderBy: [
{ createdAt: "desc" },
{ id: "desc" },
],
});
ざっくり選び方
迷ったら、最初はこのくらいで考えるとよいです。
| UIや条件 | 向いている方法 |
|---|---|
| ページ番号で移動したい | offset |
| 件数が少ない管理画面 | offset |
| 無限スクロール | cursor |
| データ量が多い一覧 | cursor |
| 更新が多く、表示ずれを減らしたい | cursor |
まとめ
Prismaのページネーションは、まず skip / take で始めても大丈夫です。
ただ、データ量が増えたり、無限スクロールのようなUIになったりしたら、cursor paginationも選択肢に入れるとよいです。
「どちらが正しいか」より、「その画面でユーザーがどう移動するか」から考えるのがおすすめです。