Cursor Pagination: Pagination untuk Data yang Terus Berubah
Memahami keterbatasan offset pagination dan cara kerja cursor-based pagination untuk feed real-time.
Nomor halaman vs penanda buku
Membaca buku cetak, kamu pakai nomor halaman (offset): "lanjut halaman 42". Tapi feed media sosial tidak punya nomor halaman yang stabil: saat kamu scroll, postingan baru terus muncul di atas dan menggeser semuanya. Solusinya seperti penanda buku: "lanjutkan dari postingan terakhir yang saya lihat." Itulah cursor pagination.
Kenapa offset gagal untuk data dinamis
Offset (?page=3&limit=10) berarti "lewati 20 item pertama". Kalau 5 item baru muncul sebelum user pindah halaman, item yang tadinya di posisi 21-30 bergeser ke 26-35: user melihat 5 item yang sama dua kali dan melewatkan 5 item lain. Untuk katalog yang jarang berubah ini tidak masalah; untuk feed, notifikasi, atau riwayat transaksi yang hidup, ini bug nyata.
Contoh 1: request dan response cursor
GET /api/feed?limit=10&cursor=eyJpZCI6OTUwfQ== HTTP/1.1
Host: toko.contoh.id{
"data": ["...10 postingan..."],
"next_cursor": "eyJpZCI6OTQwfQ==",
"has_more": true
}Cursor adalah penanda buram (biasanya id atau timestamp ter-encode) yang berarti "lanjutkan setelah item ini". Server mengambil item setelah cursor, mengembalikan cursor baru untuk halaman berikutnya. Item baru yang muncul di atas tidak menggeser apa pun.
Contoh 2: kapan pakai yang mana
// Katalog produk (jarang berubah): offset cukup
await fetch("/api/produk?page=2&limit=20");
// Feed notifikasi (terus bertambah): cursor wajib
let cursor = null;
async function muatLagi() {
const url = "/api/notifikasi?limit=10" + (cursor ? "&cursor=" + cursor : "");
const res = await fetch(url).then(r => r.json());
tampilkan(res.data);
cursor = res.next_cursor;
}Aturan praktis: butuh "lompat ke halaman 7"? Pakai offset. Data bertambah terus dan user scroll berurutan? Pakai cursor.
Kesalahan umum
Salah: memaksa offset untuk feed real-time. Duplikasi dan item terlewat. Yang benar: cursor untuk data dinamis.
Salah: membuat cursor bisa ditebak/dimodifikasi. Cursor ?cursor=100 memungkinkan user melompat sembarang dan mengintip. Yang benar: encode dan tanda tangani cursor, atau validasi di server.
Salah: tidak menangani cursor kedaluwarsa. Item acuan sudah dihapus, cursor tidak valid. Yang benar: fallback elegan (mulai dari awal) dengan pesan yang jelas.
Contoh cursor paling sederhana
Cursor tidak harus rumit. Untuk data ber-id urut, id terakhir cukup:
// Server: ambil 10 item setelah id tertentu
const items = await db.notifikasi
.where("id", "<", cursorTerakhir)
.orderBy("id", "desc")
.limit(10);
// Response
{ "data": items, "next_cursor": items[items.length-1].id }Klien menyimpan next_cursor dan mengirimnya di request berikutnya. Tidak perlu encode rumit untuk kasus internal. Yang penting: cursor menunjuk ITEM, bukan posisi angka.
Catatan teknis: API besar seperti Twitter/X, Facebook, dan Stripe memakai cursor pagination. Kalau kamu membangun feed atau timeline, ikuti pola mereka sejak awal daripada migrasi belakangan.
Tantangan
Buktikan bug offset pagination
Simulasikan dengan array JavaScript:
- Buat array 30 angka (1-30). Ambil "halaman 2" dengan offset: lewati 10, ambil 10.
- Sekarang unshift 5 angka baru di depan (simulasi data baru), lalu ambil "halaman 3" dengan offset yang sama.
- Tulis item mana yang duplikat/terlewat, dan jelaskan kenapa cursor menghindari ini.
Tulis sebagai kode + 2 baris penjelasan.