Реалізація пагінації на сайті

Наша компанія займається розробкою, підтримкою та обслуговуванням сайтів будь-якої складності. Від простих односторінкових сайтів до масштабних кластерних систем, побудованих на мікро сервісах. Досвід розробників підтверджено сертифікатами від вендорів.

Розробка та обслуговування будь-яких видів сайтів:

Інформаційні сайти або веб-програми
Сайти візитки, landing page, корпоративні сайти, онлайн каталоги, квіз, промо-сайти, блоги, ресурси новин, інформаційні портали, форуми, агрегатори
Сайти або веб-програми електронної комерції
Інтернет-магазини, B2B-портали, маркетплейси, онлайн-обмінники, кешбек-сайти, біржі, дропшиппінг-платформи, парсери товарів
Веб-програми для управління бізнес-процесами
CRM-системи, ERP-системи, корпоративні портали, системи управління виробництвом, парсери інформації
Сайти або веб-програми електронних послуг
Дошки оголошень, онлайн-школи, онлайн-кінотеатри, конструктори сайтів, портали надання електронних послуг, відеохостинги, тематичні портали

Це лише деякі з технічних типів сайтів, з якими ми працюємо, і кожен із них може мати свої специфічні особливості та функціональність, а також бути адаптованим під конкретні потреби та цілі клієнта.

Пропоновані послуги
Показано 1 з 1 послугУсі 2065 послуг
Реалізація пагінації на сайті
Проста
від 1 робочого дня до 3 робочих днів
Часті питання

Наші компетенції:

Етапи розробки

Останні роботи

  • image_website-b2b-advance_0.png
    Розробка сайту компанії B2B ADVANCE
    1262
  • image_web-applications_feedme_466_0.webp
    Розробка веб-додатків для компанії FEEDME
    1171
  • image_websites_belfingroup_462_0.webp
    Розробка веб-сайту для компанії БЕЛФІНГРУП
    874
  • image_ecommerce_furnoro_435_0.webp
    Розробка інтернет магазину для компанії FURNORO
    1094
  • image_crm_enviok_479_0.webp
    Розробка веб-додатків для компанії Enviok
    831
  • image_bitrix-bitrix-24-1c_fixper_448_0.png
    Розробка веб-сайту для компанії ФІКСПЕР
    851

Реалізація пагінації на веб-сайті

Пагінація ділить великі набори даних на сторінки фіксованого розміру. Це базовий елемент будь-якого сайту з каталогом, таблицею або лентою публікацій. Реалізація включає серверну частину (запити з LIMIT/OFFSET), API-контракт та UI-компонент навігації.

Серверна частина

// Laravel — стандартний paginate()
public function index(Request $request): JsonResponse
{
    $perPage = min($request->integer('per_page', 20), 100);

    $products = Product::where('is_active', true)
        ->orderByDesc('created_at')
        ->paginate($perPage);

    return response()->json([
        'data'         => ProductResource::collection($products->items()),
        'current_page' => $products->currentPage(),
        'last_page'    => $products->lastPage(),
        'per_page'     => $products->perPage(),
        'total'        => $products->total(),
        'from'         => $products->firstItem(),
        'to'           => $products->lastItem(),
    ]);
}

Запит до API: GET /api/products?page=3&per_page=20

Під капотом Laravel виконує два SQL-запити: COUNT(*) для total і SELECT ... LIMIT 20 OFFSET 40. На таблицях з 500k+ рядків COUNT може гальмувати — вирішується кешуванням total або переходом на приблизний підсумок через pg_class.reltuples.

UI-компонент пагінації

// components/Pagination.tsx
interface PaginationProps {
  currentPage: number
  lastPage: number
  onPageChange: (page: number) => void
}

export function Pagination({ currentPage, lastPage, onPageChange }: PaginationProps) {
  const pages = buildPageRange(currentPage, lastPage)

  return (
    <nav aria-label="Навігація по сторінках">
      <ul className="flex items-center gap-1">
        <li>
          <button
            onClick={() => onPageChange(currentPage - 1)}
            disabled={currentPage === 1}
            aria-label="Попередня сторінка"
          >
            ←
          </button>
        </li>

        {pages.map((page, i) =>
          page === '...' ? (
            <li key={`ellipsis-${i}`} aria-hidden="true">…</li>
          ) : (
            <li key={page}>
              <button
                onClick={() => onPageChange(page as number)}
                aria-current={page === currentPage ? 'page' : undefined}
                className={page === currentPage ? 'font-bold' : ''}
              >
                {page}
              </button>
            </li>
          )
        )}

        <li>
          <button
            onClick={() => onPageChange(currentPage + 1)}
            disabled={currentPage === lastPage}
            aria-label="Наступна сторінка"
          >
            →
          </button>
        </li>
      </ul>
    </nav>
  )
}

// Виробляє масив на кшталт [1, 2, '...', 7, 8, 9, '...', 20]
function buildPageRange(current: number, last: number): (number | '...')[] {
  if (last <= 7) return Array.from({ length: last }, (_, i) => i + 1)

  const delta = 2
  const range: (number | '...')[] = []
  const left = current - delta
  const right = current + delta

  let prev: number | null = null
  for (let i = 1; i <= last; i++) {
    if (i === 1 || i === last || (i >= left && i <= right)) {
      if (prev !== null && i - prev > 1) range.push('...')
      range.push(i)
      prev = i
    }
  }
  return range
}

Синхронізація з URL

Номер сторінки повинен жити в URL — інакше користувачі втрачають позицію при оновленні сторінки та не можуть поділитися посиланням.

// Next.js App Router
'use client'
import { useRouter, useSearchParams, usePathname } from 'next/navigation'

function usePageParam() {
  const searchParams = useSearchParams()
  const router = useRouter()
  const pathname = usePathname()

  const page = Number(searchParams.get('page') ?? '1')

  const setPage = (newPage: number) => {
    const params = new URLSearchParams(searchParams.toString())
    params.set('page', String(newPage))
    router.push(`${pathname}?${params.toString()}`, { scroll: true })
  }

  return [page, setPage] as const
}
// Vanilla React — history API
function setPageInUrl(page: number) {
  const url = new URL(window.location.href)
  url.searchParams.set('page', String(page))
  window.history.pushState({}, '', url)
}

Prefetch наступної сторінки

Щоб переходи ощущались миттєвими, завантажуємо дані наступної сторінки заздалегідь:

import { useQueryClient } from '@tanstack/react-query'

function useProductsPrefetch(currentPage: number) {
  const queryClient = useQueryClient()

  useEffect(() => {
    if (currentPage < lastPage) {
      queryClient.prefetchQuery({
        queryKey: ['products', currentPage + 1],
        queryFn: () => fetchProducts(currentPage + 1),
        staleTime: 30_000,
      })
    }
  }, [currentPage])
}

SEO

Для пошукових ботів сторінки з пагінацією повинні бути правильно пов'язані:

<!-- На сторінці /catalog?page=3 -->
<link rel="prev" href="/catalog?page=2" />
<link rel="next" href="/catalog?page=4" />
<link rel="canonical" href="/catalog?page=3" />

Google офіційно не використовує rel=prev/next з 2019 року, але Яндекс — так. У Laravel Blade або Next.js Metadata API ці теги варто виводити.

Часові рамки

Базовий компонент з API-інтеграцією — 1 день. З синхронізацією URL, prefetch, SEO-тегами та адаптивним відображенням — 2 дні.