Пагинация на сайте: offset, cursor, SEO и prefetch

Когда каталог разрастается до десятков тысяч позиций, а страницы с товарами перестают открываться быстро, пользователи уходят, а поисковые системы теряют часть контента. Мы разрабатываем пагинацию, которая сохраняет позицию посетителя, ускоряет загрузку и корректно индексируется поисковиками. Наша команда реализует проект под ключ — от выбора алгоритма до настройки SEO-тегов и последующей поддержки.

Разработка и обслуживание любых видов сайтов:

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

Это лишь некоторые из технических типов сайтов, с которыми мы работаем, и каждый из них может иметь свои специфические особенности и функциональность, а также быть адаптированным под конкретные потребности и цели клиента

Услуги, которые мы предлагаем
Показано 1 из 1Все 2062 услуг
Пагинация на сайте: offset, cursor, SEO и prefetch
Простой
от 1 дня до 3 дней
Часто задаваемые вопросы

Пагинация на сайте: offset, cursor, SEO и prefetch

Отметим: когда каталог превышает 10 000 товаров или таблица с логами содержит 500 000 строк, без пагинации не обойтись. В нашей практике был проект с 1,5 млн записей в PostgreSQL — COUNT выполнялся 4 секунды даже после индексации. Пользователи теряли позицию при нажатии «Назад», а поисковые боты не индексировали все страницы. Мы реализовали пагинацию для 50+ проектов, от интернет-магазинов до аналитических дашбордов. Особенно критично это для проектов на Laravel и Next.js, где неправильная пагинация приводит к деградации Core Web Vitals и снижению конверсии. В этой статье разберём реализацию пагинации: от выбора алгоритма до SEO-тегов и prefetch.

Выбор типа пагинации для интернет-магазина

Выбор между offset и cursor зависит от задачи. Offset-пагинация проста в реализации: LIMIT 20 OFFSET 40. Но на больших наборах данных COUNT — узкое место. Мы используем приблизительный подсчёт через pg_class.reltuples в PostgreSQL, что ускоряет запрос в 100 раз. Cursor-пагинация быстрее, но не даёт URL для каждой страницы — это проблема для SEO.

Критерий Offset Cursor
Скорость на 1 млн строк 2-4 с (COUNT) <10 мс
Стабильность при вставках Смещение Нет
SEO-совместимость Да Нет
Сложность реализации Низкая Средняя

Почему prefetch важен для мобильных устройств?

Prefetch позволяет загрузить следующую страницу до клика. На мобильных устройствах с медленным соединением это снижает INP на 40%. Мы используем react-query с staleTime: 30_000 и предзагружаем данные в useEffect. Пример для Next.js:

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

Сравнение производительности: prefetch vs без prefetch

Сценарий LCP INP Время перехода
Без prefetch 2.8 с 200 мс 600 мс
С prefetch 2.1 с 120 мс 100 мс

Когда использовать cursor-пагинацию?

Cursor-пагинация подходит для лент в реальном времени (Twitter, лента комментариев) или API с большим объёмом данных, где важен порядок без смещений. Мы используем её в проектах с более чем 100 000 записей при частых вставках. Cursor-пагинация требует уникального сортировочного ключа (например, ID или created_at) и не поддерживает произвольный переход на страницу, но обеспечивает стабильность при добавлении новых записей.

Пошаговое руководство по внедрению пагинации на Next.js

  1. Определите API-контракт: выберите offset или cursor. Для offset — возвращайте current_page, last_page, total, per_page.
  2. Реализуйте серверную логику: используйте paginate() в Laravel или find().skip().limit() в MongoDB. Кешируйте COUNT через Redis на 5 минут.
  3. Разработайте UI-компонент: с эллипсисом, aria-label, адаптивностью.
  4. Синхронизируйте с URL: через useSearchParams в Next.js или history.pushState в ванильном JS.
  5. Добавьте prefetch: с помощью React Query или useEffect с fetch.
  6. Внедрите SEO-теги: <link rel="prev"/> и <link rel="next"/> для Яндекса. Google не использует rel prev/next для ранжирования (Google Search Central), но для Яндекса они важны.
  7. Протестируйте: проверьте LCP < 2.5 с, INP < 200 мс.

Серверная часть: offset против cursor-пагинации

Для ускорения COUNT мы используем приблизительный подсчёт через pg_class.reltuples в PostgreSQL или кеширование в Redis на 5 минут. Это снижает время запроса с секунд до миллисекунд.

// 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(),
    ]);
}

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

Компонент выводит номера страниц с эллипсисом при большом количестве. Кнопки с aria-label для доступности. Active-страница выделяется. Стилизация через Tailwind.

// 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>
  )
}

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 — иначе пользователь теряет позицию при обновлении и не может поделиться ссылкой. Используем useSearchParams в Next.js App Router.

// 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
}

Что входит в нашу работу по пагинации

  • Аудит текущей схемы пагинации (SQL-запросы, время ответа)
  • Проектирование API-контракта (offset/cursor, формат ответа)
  • Реализация серверной логики с кешированием COUNT
  • Разработка UI-компонента с адаптивностью и доступностью
  • Синхронизация с URL (Next.js, React Router, Vanilla)
  • Настройка prefetch для мгновенных переходов
  • Внедрение SEO-тегов (rel prev/next, canonical, sitemap для пагинации)
  • Документация и обучение команды заказчика

Сроки и стоимость

Базовый компонент с API-интеграцией — от 1 дня. Полный пакет с URL-синхронизацией, prefetch, SEO-тегами, адаптивным дизайном и обучением — от 2 до 4 дней. Стоимость рассчитывается индивидуально после анализа вашего проекта. Экономия времени разработки на готовых компонентах составляет до 30% по сравнению с написанием с нуля.

Свяжитесь с нами — оценим ваш проект. Наши инженеры с многолетним опытом помогут реализовать пагинацию с учётом SEO и производительности. Гарантируем LCP < 2.5 с и корректную индексацию в Яндексе. Закажите реализацию пагинации с гарантией Core Web Vitals. Получите консультацию инженера и прототип за 1 день.

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

Последние работы

  • Разработка сайта компании B2B ADVANCE
    Разработка сайта компании B2B ADVANCE
    1542
  • Разработка веб-приложения для компании FEEDME
    Разработка веб-приложения для компании FEEDME
    1393
  • Разработка веб-сайта для компании БЕЛФИНГРУПП
    Разработка веб-сайта для компании БЕЛФИНГРУПП
    1081
  • Разработка интернет магазина для компании FURNORO
    Разработка интернет магазина для компании FURNORO
    1368
  • Разработка веб-приложения для компании Enviok
    Разработка веб-приложения для компании Enviok
    1139
  • Разработка веб-сайта для компании ФИКСПЕР
    Разработка веб-сайта для компании ФИКСПЕР
    1060