Бесконечная лента против пагинации: сравнение на задачах
Бесконечный скролл кажется современнее нумерованных страниц, но ломает возврат к товару, память браузера и SEO. Разбираем четыре подхода на конкретных задачах: каталог, лента новостей, поиск, админка.

Вы делаете каталог на 40 000 товаров. Продакт говорит: «Как в TikTok, чтобы скроллилось бесконечно». Через месяц приходят жалобы: пользователь пролистал до 300-й позиции, нажал на товар, вернулся назад — и оказался в начале. На мобильном при этом вкладка съедает 600 МБ и вылетает. Это не баг реализации, а цена выбранного паттерна. Ниже — четыре способа показать длинный список, и что вы платите за каждый.
Задача и почему очевидное решение мешает
Список длиннее, чем помещается на экран, — самая частая задача фронтенда. Кажется, что выбор простой: либо кнопка «Далее» с номерами страниц, либо подгрузка при скролле. На деле паттернов минимум четыре, и они по-разному ведут себя при возврате назад, при расшаривании ссылки, под памятью и в индексации.
Очевидное «сделаем бесконечный скролл, это модно» проваливается на трёх вещах сразу: браузер держит в DOM тысячи узлов, состояние скролла теряется при навигации, а поисковый робот не докручивает ленту и не видит товары после первого экрана. Разберём, где каждый паттерн уместен.
Четыре подхода
1. Классическая пагинация со страницами в URL
Номера страниц, параметр вида ?page=3 в адресе. Сервер отдаёт срез, фронт рисует только его.
- Выигрываете: каждая страница — отдельный URL, её можно расшарить, забукмаркить и проиндексировать. DOM всегда маленький. Возврат назад работает бесплатно — вы просто снова на
?page=3. - Платите: при удалении или вставке элемента offset-пагинация сдвигает выдачу — товар с 21-й позиции переезжает на 20-ю, и на границе страниц пользователь видит дубль или пропуск. Клик по «Далее» — это ожидание ответа, лента не течёт.
2. Бесконечный скролл (infinite scroll)
Догрузка следующей порции при приближении к концу списка. Обычно через IntersectionObserver на элементе-триггере внизу.
- Выигрываете: ощущение непрерывности, ноль кликов, хорошо для потребления контента подряд — лента новостей, фото.
- Платите: DOM растёт линейно, память тоже. Нет естественного URL для позиции. Футер сайта становится недостижим — под ним всегда есть ещё контент. Восстановление позиции после ухода на карточку товара нужно писать руками.
3. Кнопка «Показать ещё» (load more)
Компромисс: контент добавляется по клику, а не автоматически. По сути тот же append, но пользователь управляет им сам.
- Выигрываете: футер снова достижим, действие явное. Проще, чем скролл: не нужен наблюдатель за пересечением.
- Платите: те же проблемы с растущим DOM и потерей позиции, что у скролла. URL по-прежнему не отражает, сколько показано.
4. Виртуализация (windowing)
В DOM держатся только видимые строки плюс небольшой буфер, остальные — пустое пространство заданной высоты. Данные при этом могут быть загружены все или подгружаться порциями.
- Выигрываете: DOM постоянного размера при любом числе элементов. 100 000 строк рендерятся так же плавно, как 50. Единственный способ показать по-настоящему длинный список без деградации.
- Платите: сложность. Нужна известная или измеримая высота строк. Ctrl+F по странице не находит то, что не отрисовано. SEO страдает так же, как у скролла. Библиотека обязательна — руками виртуализацию с переменной высотой писать долго и больно.
Сравнение
| Критерий | Пагинация | Скролл | Показать ещё | Виртуализация |
|---|---|---|---|---|
| Размер DOM | малый | растёт | растёт | постоянный |
| Позиция в URL | есть | нет | нет | нет |
| Возврат назад | бесплатно | руками | руками | руками |
| Индексируется роботом | да | частично | частично | частично |
| Футер достижим | да | нет | да | да |
| Список 50k+ строк | по страницам | тормозит | тормозит | плавно |
Курсор вместо offset — общая база для скролла
Отдельно про то, как сервер отдаёт порции. Если вы используете OFFSET/LIMIT, то при вставке или удалении элемента между запросами страницы съезжают. Курсорная пагинация решает это: клиент шлёт не номер страницы, а «дай мне то, что после элемента X». Курсором служит стабильное значение — обычно комбинация поля сортировки и id.
// Клиент запрашивает следующую порцию по курсору
// cursor — последний виденный id (или composite: created_at + id)
async function loadMore(cursor) {
const params = new URLSearchParams({ limit: '20' });
if (cursor) params.set('cursor', cursor);
const res = await fetch(`/api/products?${params}`);
const { items, nextCursor } = await res.json();
// nextCursor === null означает, что порций больше нет
return { items, nextCursor };
}
Курсорная схема нужна и скроллу, и «Показать ещё», и любой ленте, где данные меняются между запросами. Классическая нумерация страниц с курсором несовместима: по курсору нельзя прыгнуть сразу на страницу 50. Это ещё один довод в пользу пагинации там, где важен переход к произвольной странице.
Правило, которое экономит нервы: если у элемента списка есть отдельная страница, на которую пользователь уходит и возвращается, — либо пагинация с URL, либо честное восстановление скролла. «Бесконечная лента без восстановления позиции» — это баг, который вы заметите только на проде от реальных пользователей.
Восстановление позиции — то, что забывают
Главная скрытая стоимость скролла и «Показать ещё» — возврат. Пользователь пролистал ленту, открыл карточку, нажал «назад» и хочет оказаться там же. Из коробки этого нет.
В браузере есть свойство history.scrollRestoration. По умолчанию оно равно 'auto', и браузер пытается вернуть скролл сам — но для динамически подгруженного контента это не работает, потому что к моменту восстановления в DOM ещё нет нужных элементов. Точное имя и поведение свойства сверяйте в документации MDN для вашего браузера. Практический путь такой:
- При уходе с ленты сохраните, сколько порций уже загружено и координату скролла (в
sessionStorageили в состоянии роутера). - При возврате сначала восстановите те же порции данных, дождитесь их отрисовки, только потом верните скролл.
- Проверьте на медленной сети: если вернуть скролл до отрисовки, попадёте не туда.
Именно из-за этого шага бесконечный скролл обходится дороже в разработке, чем кажется на старте.
Когда что выбирать
- Каталог интернет-магазина, важен SEO и возврат к товару — пагинация с URL. Робот обходит страницы, пользователь возвращается на ту же страницу бесплатно.
- Лента контента для потребления подряд (новости, фото, соцсеть) — бесконечный скролл, но обязательно с восстановлением позиции.
- Список, где пользователь не хочет автозагрузки и нужен доступный футер — «Показать ещё».
- Таблица или список от нескольких тысяч строк (админка, логи, справочник) — виртуализация. Остальные паттерны здесь просто тормозят.
Что ломается на практике
- Дубли и пропуски на границе страниц при offset-пагинации, если данные меняются. Лечится курсором.
- Скачок layout при догрузке — новый контент сдвигает то, на что пользователь целился. Резервируйте высоту под элементы заранее.
- Двойная загрузка, когда триггер скролла срабатывает несколько раз подряд. Блокируйте новый запрос, пока предыдущий не завершился.
- Пустой экран у робота: если контент появляется только после скролла, а серверного рендера нет, поисковик видит пустоту. Для каталогов это прямая потеря трафика.
Что делать дальше
Начните с вопроса «нужен ли переход на произвольную страницу и индексация» — если да, берите пагинацию и не усложняйте. Для длинных изменчивых лент переведите API на курсор. Для больших таблиц подключите готовую библиотеку виртуализации (например, из экосистемы TanStack Virtual) — сверьте актуальный API в её документации, он менялся между мажорными версиями. И заведите в чек-лист ревью один пункт: «проверено ли восстановление позиции после возврата назад» — именно он чаще всего оказывается незакрытым.
Логотип и фирменный стиль: Три концепции разного характера, палитра с обоснованием, проверка на читаемость в малом размере и в один цвет.
Спросить за 60 ₽Источники: IntersectionObserver API — MDN Web Docs, History.scrollRestoration — MDN Web Docs
Частые вопросы
А если у меня контент рендерится на сервере (SSR) — проблема с SEO для скролла снимается?
Восстановление позиции не сработало — скролл прыгает не туда. В чём дело?
Можно ли совместить нумерованную пагинацию и курсор?
Виртуализация не находит текст по Ctrl+F — это лечится?
Сколько элементов в DOM — уже много и пора виртуализировать?
Материал носит информационный характер и подготовлен редакцией «Агентуры». Он не является офертой, рекламой или индивидуальной консультацией. Упомянутые продукты, компании и торговые знаки принадлежат их правообладателям. Перед принятием решений, влекущих юридические или финансовые последствия, обратитесь к профильному специалисту.