Kenapa Pagination PHP Tidak Jalan? Checklist Debug

Pagination terlihat sederhana: ambil page dari URL, hitung LIMIT dan OFFSET, lalu tampilkan data. Tetapi saat implementasinya meleset sedikit saja, gejalanya langsung terasa aneh: halaman 2 isinya sama dengan halaman 1, halaman terakhir kosong, atau link next/prev malah lompat ke data yang salah.

Masalah pagination hampir selalu ada di salah satu dari tiga titik: perhitungan offset, query total data, atau pembuatan link halaman.

Kapan Masalah Ini Biasanya Muncul?

  • Halaman 1 dan halaman 2 menampilkan data yang sama
  • Halaman terakhir kosong padahal total data masih ada
  • Tombol next/prev menuju halaman yang salah
  • Nomor halaman tampil, tetapi query database tidak berubah
  • Filter pencarian aktif, tetapi total halaman tetap menghitung semua data

Penyebab Paling Umum

1. Rumus OFFSET Salah

Rumus yang benar:

<?php
$offset = ($page - 1) * $limit;

Kalau kamu menulis $offset = $page * $limit, halaman pertama akan langsung melewati data awal.

2. Nilai page dari URL Tidak Divalidasi

<?php
$page = (int) ($_GET['page'] ?? 1);

Kalau user membuka ?page=0, ?page=-2, atau ?page=abc, hasil perhitungan pagination bisa kacau kalau tidak dibersihkan.

3. Query Total Data Tidak Sama dengan Query List Data

Ini bug yang sangat sering:

<?php
$stmtTotal = $pdo->query("SELECT COUNT(*) FROM produk");

Tetapi query list datanya memakai filter:

<?php
$stmt = $pdo->prepare("SELECT * FROM produk WHERE kategori = ? LIMIT ? OFFSET ?");

Akibatnya, jumlah halaman dihitung dari semua data, bukan dari data yang sedang difilter.

Kalau user sedang mencari ?keyword=laptop&page=2, tetapi link next hanya menjadi ?page=3, filter keyword akan hilang dan isi datanya berubah.

5. Tidak Ada ORDER BY

Pagination tanpa urutan yang konsisten bisa membuat data terasa "lompat-lompat", terutama saat ada data baru masuk ke tabel.

6. Total Halaman Tidak Dibulatkan ke Atas

Gunakan ceil(), bukan pembagian biasa:

<?php
$totalPages = (int) ceil($totalData / $limit);

Kalau tidak, sisa data di halaman terakhir bisa hilang.

Langkah Diagnosis

  1. Tampilkan nilai page, limit, dan offset sebelum query dijalankan.
  2. Pastikan halaman aktif minimal bernilai 1.
  3. Bandingkan query total data dan query list data. Filter-nya harus sinkron.
  4. Pastikan query list memakai ORDER BY yang stabil, misalnya ORDER BY id DESC.
  5. Cek apakah link pagination tetap membawa query string lain seperti keyword, sort, atau kategori.
Mulai dari Tiga Angka Ini

Kalau pagination terasa aneh, cek dulu tiga nilai berikut: page, limit, dan offset. Biasanya bug-nya langsung kelihatan dari sana.

Langkah Fix

1. Bersihkan Nilai page

<?php
$page = max(1, (int) ($_GET['page'] ?? 1));

2. Hitung OFFSET dengan Rumus yang Benar

<?php
$limit = 10;
$offset = ($page - 1) * $limit;

3. Sinkronkan Query Total dan Query Data

<?php
$keyword = trim($_GET['keyword'] ?? '');

$stmtTotal = $pdo->prepare("
    SELECT COUNT(*) AS total
    FROM produk
    WHERE nama LIKE :keyword
");
$stmtTotal->execute([
    'keyword' => "%{$keyword}%",
]);

$totalData = (int) $stmtTotal->fetch()['total'];
$totalPages = max(1, (int) ceil($totalData / $limit));

$stmt = $pdo->prepare("
    SELECT *
    FROM produk
    WHERE nama LIKE :keyword
    ORDER BY id DESC
    LIMIT :limit OFFSET :offset
");
$stmt->bindValue(':keyword', "%{$keyword}%");
$stmt->bindValue(':limit', $limit, PDO::PARAM_INT);
$stmt->bindValue(':offset', $offset, PDO::PARAM_INT);
$stmt->execute();
<?php
$keyword = urlencode($_GET['keyword'] ?? '');
echo "<a href=\"?keyword={$keyword}&page=2\">2</a>";

5. Batasi Halaman Maksimum

Kalau user meminta ?page=999, lebih aman dibatasi:

<?php
if ($page > $totalPages) {
    $page = $totalPages;
}

Contoh Sebelum dan Sesudah

Sebelum

<?php
$limit = 10;
$page = (int) ($_GET['page'] ?? 1);
$offset = $page * $limit;

$stmt = $pdo->query("SELECT * FROM buku_tamu LIMIT $limit OFFSET $offset");

Masalahnya:

  • OFFSET salah
  • tidak ada validasi halaman
  • tidak ada ORDER BY

Sesudah

<?php
$limit = 10;
$page = max(1, (int) ($_GET['page'] ?? 1));
$offset = ($page - 1) * $limit;

$stmt = $pdo->prepare("
    SELECT *
    FROM buku_tamu
    ORDER BY id DESC
    LIMIT :limit OFFSET :offset
");
$stmt->bindValue(':limit', $limit, PDO::PARAM_INT);
$stmt->bindValue(':offset', $offset, PDO::PARAM_INT);
$stmt->execute();

Error Umum

Halaman 2 Isinya Sama dengan Halaman 1

Biasanya OFFSET tidak berubah atau rumusnya salah.

Halaman Terakhir Kosong

Sering terjadi karena:

  • total halaman dihitung dari query yang berbeda
  • user membuka nomor halaman di luar batas
  • hasil COUNT(*) tidak memakai filter yang sama

Nomor Halaman Sudah Benar, Tetapi Data Tetap Lompat

Kemungkinan query belum memakai ORDER BY, sehingga database bebas mengurutkan hasil.

Pencegahan

  1. Selalu bersihkan $_GET['page'] dengan max(1, ...).
  2. Gunakan ORDER BY yang stabil di semua query pagination.
  3. Hitung total data dengan filter yang sama persis.
  4. Pakai ceil() untuk menghitung total halaman.
  5. Simpan query string penting saat membangun link pagination.

Bacaan Terkait

FAQ

Kenapa pagination saya jalan di halaman awal, tetapi error saat ada filter?

Biasanya query COUNT(*) dan query list data tidak memakai WHERE yang sama.

Kenapa LIMIT dan OFFSET saya error saat pakai prepared statement?

Beberapa kasus butuh bindValue(..., PDO::PARAM_INT) agar nilainya benar-benar dikirim sebagai integer.

Perlukah membatasi page maksimum?

Iya. Ini mencegah user membuka halaman di luar batas dan mengurangi kasus tampilan kosong yang membingungkan.