何をするオプションか
cursor は Prisma のカーソルベースページネーションで「どのレコードの次から取得するか」を指定するオプションです。レコードの id や createdAt などの一意フィールドを位置マーカーとして使います。
prisma.post.findMany({
cursor: { id: lastId }, // このレコードの次から取得
take: 10,
skip: 1, // cursor 行自身をスキップ
orderBy: { id: "desc" },
});
skip: 1 が必要な理由
Prisma の cursor は「そのレコード から 始める」という意味です。skip: 1 を省略すると、カーソルで指定したレコード自身も結果に含まれ、前のページの最後の要素が重複して返ります。
// ❌ skip: 1 なし → cursor 行が重複して返る
prisma.post.findMany({ cursor: { id: 42 }, take: 5 });
// 返り値: [id:42, id:41, id:40, id:39, id:38]
// ↑ 前ページで既に返したレコード
// ✅ skip: 1 あり → cursor 行の次から返る
prisma.post.findMany({ cursor: { id: 42 }, take: 5, skip: 1 });
// 返り値: [id:41, id:40, id:39, id:38, id:37]
offset 方式との違い
| offset(skip/take) | cursor | |
|---|---|---|
| ページ指定方法 | 件数ベース(何件スキップ) | レコード位置ベース(どのIDの次) |
| 大量データの性能 | スキップ量が増えるほど遅くなる | 常に一定 |
| 総件数の取得 | 容易(count と並列取得) | 困難(cursor 方式では totalPages が出せない) |
| UI との相性 | ページ番号 UI に向く | 無限スクロール・「次へ」ナビに向く |
| 途中ページへの移動 | 簡単(page=N で指定) | 困難(途中のカーソル値が必要) |
向いている場面 / 向いていない場面
向いている場面
- SNS フィードや無限スクロールで「次の N 件」だけ取得するとき
- 数十万件以上のテーブルをページネーションするとき(offset の遅延を避けたい)
- API のレスポンスとして
nextCursorをクライアントに渡すとき
向いていない場面
- 「3 ページ目に飛ぶ」など任意ページへのジャンプが必要な管理画面
- 総件数・総ページ数を表示するページネーション UI
- ユーザーが URL を直接編集してページ指定するケース
take / skip / orderBy との関係
// カーソルページネーションの典型パターン
const items = await prisma.post.findMany({
take: limit + 1, // 1 件多く取得して「次ページあり」を判定
cursor: cursor ? { id: Number(cursor) } : undefined,
skip: cursor ? 1 : 0, // cursor がある場合のみ skip: 1
orderBy: { id: "desc" },
});
const hasNextPage = items.length > limit;
const nextCursor = hasNextPage ? String(items[limit - 1].id) : null;
take: limit + 1で 1 件多く取得し、limit超えなら次ページが存在すると判定するorderByは必須。ソート順が不定だとカーソル位置が意味をなさないcursorに使うフィールドには DB インデックスが必要(通常@idは自動でインデックス付き)
関連サンプルの見どころ
- prisma-pagination-query: offset 方式と cursor 方式の両パターンを Route Handler で実装する例
offset 方式と cursor 方式の選び分けを比較でまとめたページもあります → offset vs cursor ページネーション