Гид по технологиям

Пагинация и бесконечная прокрутка в Next.js с TanStack Query

7 min read Frontend Обновлено 30 Dec 2025
Пагинация и бесконечная прокрутка с TanStack Query
Пагинация и бесконечная прокрутка с TanStack Query

Введение

Экран ноутбука с кодом и держателем для ручек рядом.

Большинство приложений работают с данными, и по мере роста приложений объём данных может становиться значительным. Если приложение не умеет эффективно обрабатывать большой объём данных, оно начнёт тормозить и выдавать плохой UX.

Пагинация и бесконечная прокрутка — два распространённых приёма, которые помогают оптимизировать скорость рендеринга и улучшить взаимодействие с пользователем. В этой статье мы рассмотрим, как реализовать оба подхода с помощью TanStack Query в Next.js (App directory, Next 13+).

Важно: в примерах используется публичный JSONPlaceholder как источник данных. В реальном приложении заменяйте URL на ваш API и учитывайте авторизацию, лимиты и кэш-политику.

Что такое TanStack Query (одно предложение)

TanStack Query — это библиотека управления асинхронными данными (кэширование, повторные запросы, обновление в фоне) для React-приложений. Она упрощает работу с запросами и состоянием данных.

Что такое пагинация и бесконечная прокрутка

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

Ключевой выбор зависит от задачи: удобство навигации (пагинация) против бесшовного просмотра (infinite scroll).

Репозиторий с кодом

Исходный код примеров доступен в репозитории проекта (ссылка в оригинале). Используйте его как отправную точку.

Установка и настройка Next.js (App directory)

Чтобы начать, создайте проект Next.js с App directory:

npx create-next-app@latest next-project --app

Затем установите TanStack Query (React Query):

npm i @tanstack/react-query

Интеграция TanStack Query в Next.js

Создайте и инициализируйте QueryClient в корневом layout приложения (обычно файл src/app/layout.js или src/app/layout.tsx). Оборачивайте children в QueryClientProvider.

"use client"
import React from 'react'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';

const metadata = {
  title: 'Create Next App',
  description: 'Generated by create next app',
};

export default function RootLayout({ children }) {
  const queryClient = new QueryClient();

  return (
    
      
        
          {children}
        
      
    
  );
}

export { metadata };

Эта настройка даёт доступ к TanStack Query в любом компоненте приложения.

Пагинация с useQuery — пошаговое руководство

В этой секции мы реализуем страницу Pagination с использованием хука useQuery. Создайте файл src/app/Pagination/page.js и необходимые стили.

Импорт и подготовка компонента:

"use client"
import React, { useState } from 'react';
import { useQuery } from '@tanstack/react-query';
import './page.styles.css';

Функция, которая будет получать данные (пример с JSONPlaceholder):

export default function Pagination() {
  const [page, setPage] = useState(1);

  const fetchPosts = async () => {
    try {
      const response = await fetch(`https://jsonplaceholder.typicode.com/posts?_page=${page}&_limit=10`);

      if (!response.ok) {
        throw new Error('Failed to fetch posts');
      }

      const data = await response.json();
      return data;
    } catch (error) {
      console.error(error);
      throw error;
    }
  };

  // add the following code here
}

Добавим useQuery и параметры:

  const { isLoading, isError, error, data } = useQuery({
    keepPreviousData: true,
    queryKey: ['posts', page],
    queryFn: fetchPosts,
  });

Объяснение параметров:

  • keepPreviousData: true — при переходе на другую страницу показывать предыдущие данные пока подгружаются новые. Это даёт менее дерганый UX.
  • queryKey: [‘posts’, page] — ключ привязан к странице, поэтому кэш хранит версии по каждой странице.
  • queryFn: функция, которая делает fetch.

Обработка состояний загрузки и ошибок:

  if (isLoading) {
    return (

Loading...

); } if (isError) { return (

{error.message}

); }

Рендер UI и кнопки навигации:

  return (
    

Next.js Pagination

{data && (
    {data.map((post) => (
  • {post.title}
  • ))}
)}
);

Запустите сервер разработки:

npm run dev

Откройте: http://localhost:3000/Pagination

Пример пагинации TanStack Query в Next.js приложении

Папка Pagination в app-directory автоматически становится маршрутом.

Бесконечная прокрутка с useInfiniteQuery — пошагово

Интересный UX-пример — лента, похожая на YouTube: новые элементы подгружаются автоматически.

Создайте src/app/InfiniteScroll/page.js и подключите стили.

"use client"
import React, { useRef, useEffect, useState } from 'react';
import { useInfiniteQuery } from '@tanstack/react-query';
import './page.styles.css';

Функция fetch и компонент:

export default function InfiniteScroll() {
  const listRef = useRef(null);
  const [isLoadingMore, setIsLoadingMore] = useState(false);

  const fetchPosts = async ({ pageParam = 1 }) => {
    try {
      const response = await fetch(`https://jsonplaceholder.typicode.com/posts?_page=${pageParam}&_limit=5`);

      if (!response.ok) {
        throw new Error('Failed to fetch posts');
      }

      const data = await response.json();
      await new Promise((resolve) => setTimeout(resolve, 2000));
      return data;
    } catch (error) {
      console.error(error);
      throw error;
    }
  };

  // add the following code here
}

Подключаем useInfiniteQuery и формируем flat-список постов из страниц:

  const { data, fetchNextPage, hasNextPage, isFetching } = useInfiniteQuery({
    queryKey: ['posts'],
    queryFn: fetchPosts,
    getNextPageParam: (lastPage, allPages) => {
      if (lastPage.length < 5) {
        return undefined;
      }
      return allPages.length + 1;
    },
  });

  const posts = data ? data.pages.flatMap((page) => page) : [];

Intersection Observer для детекции, когда подгружать следующую страницу:

  const handleIntersection = (entries) => {
    if (entries[0].isIntersecting && hasNextPage && !isFetching && !isLoadingMore) {
      setIsLoadingMore(true);
      fetchNextPage();
    }
  };

  useEffect(() => {
    const observer = new IntersectionObserver(handleIntersection, { threshold: 0.1 });

    if (listRef.current) {
      observer.observe(listRef.current);
    }

    return () => {
      if (listRef.current) {
        observer.unobserve(listRef.current);
      }
    };
  }, [listRef, handleIntersection]);

  useEffect(() => {
    if (!isFetching) {
      setIsLoadingMore(false);
    }
  }, [isFetching]);

JSX для рендера:

  return (
    

Infinite Scroll

    {posts.map((post) => (
  • {post.title}
  • ))}
{isFetching ? 'Fetching...' : isLoadingMore ? 'Loading more...' : null}
);

Откройте: http://localhost:3000/InfiniteScroll

Сравнение: когда выбирать пагинацию, а когда бесконечную прокрутку

  • Если пользователям важна возможность сохранить конкретную страницу и вернуться к ней — выбирайте пагинацию.
  • Если важно быстро показывать поток контента (ленты, социальные стены) — удобнее бесконечная прокрутка.
  • Пагинация легче индексируется поисковиками и лучше подходит для SEO.
  • Бесконечная прокрутка требует внимательного подхода к доступности и управлению памятью на клиенте.

Альтернативы и расширения подхода

  • Cursor-based pagination (позиционная/стримовая пагинация) — лучше для больших данных и для одновременных вставок/удалений в источнике.
  • Server-side pagination — отдача уже разделённых страниц с сервера (иногда быстрее и экономичнее по сети).
  • Комбинация: клиентская пагинация поверх cursor-пагинации API.

Практические советы по оптимизации и кэшированию

  • Настройте staleTime и cacheTime в QueryClient, чтобы избежать лишних запросов.
  • Используйте keepPreviousData при переходе между страницами для плавного UX.
  • Для бесконечной прокрутки контролируйте объём DOM: удаляйте или виртуализируйте старые элементы (react-window, react-virtualized) при тысячах элементов.
  • Ограничивайте количество параллельных fetch-запросов.
  • В продакшне настойчиво рекомендуем включать HTTP-кэширование и ETag/If-None-Match на стороне API.

Доступность и UX

  • Для пагинации предоставляйте явные кнопки с понятными aria-label и состояниями disabled.
  • Для бесконечной прокрутки реализуйте «Загрузить ещё» как fallback для тех, у кого отключён JS или для вспомогательных технологий.
  • Сообщайте пользователю состояние загрузки (прогресс, индикатор «подгружаю»).

Безопасность и приватность

  • Не храните чувствительные данные в кэше клиента дольше, чем нужно.
  • При работе с персональными данными учитывайте GDPR: минимизируйте передачу лишней информации, обезличивайте при необходимости.
  • Используйте защищённые запросы (HTTPS) и токены доступа. Не логируйте токены или персональную информацию в клиентских ошибках.

Критерии приёмки

  • Компонент пагинации корректно подгружает страницы при кликах и не теряет состояния страницы.
  • Компонент бесконечной прокрутки подгружает следующие страницы при скролле и корректно показывает индикаторы загрузки.
  • При плохом соединении UI показывает понятный текст ошибки и возможность повторить запрос.
  • Производительность: рендер одной страницы не занимает более приемлемого времени (зависит от проекта), нет утечек памяти при длительном использовании ленты.

Чек-листы для ролей (разработчик / QA / PM)

Разработчик:

  • Реализовал кэширование и handling ошибок.
  • Добавил ограничения по количеству элементов в DOM или внедрил виртуализацию.
  • Написал unit/integ тесты для ключевой логики загрузки страниц.

QA:

  • Проверил переходы между страницами при разных задержках сети.
  • Проверил поведение при отсутствии сети и при респонсах с ошибкой.
  • Оценил память клиента при длительном использовании ленты.

PM (Product Manager):

  • Оценил метрики успеха: время до первого взаимодействия, средняя глубина просмотра.
  • Утвердил UX для мобильных и десктопных устройств.

Тест-кейсы и приёмочные критерии

  1. Загрузка первой страницы: отображается список и нет ошибок.
  2. Навигация «Prev/Next»: корректно переключается, кнопки disabled в нужных состояниях.
  3. Бесконечный скролл: при достижении низа вызывается fetchNextPage.
  4. Ошибки API: отображается сообщение об ошибке и кнопка «повторить».
  5. Производительность: после 1000 элементов в ленте приложение остаётся отзывчивым (или виртуализация включена).

Сценарии, когда подход может не подойти (контрпримеры)

  • Если пользователь должен ссылаться на конкретную запись через URL (например, каталоги или архивы), бесконечная прокрутка мешает навигации.
  • Если требуется точная нумерация результатов и их подсчёт — предпочтительна пагинация на сервере.

Ментальные модели и эвристики

  • Модель «окно»: представьте, что пользователь видит только окно из N элементов. Подгружайте следующую «страницу», когда окно приближается к нижней границе.
  • Эвристика «дежурного времени»: если загрузка новой страницы занимает меньше 300–500 мс, пользователь обычно не заметит лаг; если дольше — показывайте прогресс.

Миграция и совместимость

  • При миграции с React Query v3 на TanStack Query (v4+) обратите внимание на изменение API (переименование пакета и некоторые опции). Тестируйте ключи запросов и дедупликацию.
  • Для SSR/SSG учитывайте, что App directory в Next.js имеет особенности рендеринга; TanStack Query можно использовать и на сервере, но требуется дополнительная логика для гидратации.

Краткая методология внедрения (mini-playbook)

  1. Определите требования UX и SEO.
  2. Выберите стратегию пагинации (offset vs cursor).
  3. Настройте QueryClient и дефолтные опции (staleTime, retry, cacheTime).
  4. Реализуйте базовый компонент и покрытие тестами.
  5. Добавьте обработку ошибок, индикаторы загрузки и метрики.
  6. Проведите нагрузочное тестирование и профилирование на целевых устройствах.

Короткий глоссарий (1 строка на термин)

  • Pagination: деление результата на страницы.
  • Infinite scroll: автоматическая подгрузка данных при прокрутке.
  • Cursor-based pagination: пагинация по курсору/позиции, устойчива к вставкам.
  • keepPreviousData: опция TanStack Query, сохраняющая старые данные во время фетча.

Социальный превью (рекомендации)

OG title: Пагинация и бесконечная прокрутка с TanStack Query OG description: Пошаговое руководство по useQuery и useInfiniteQuery в Next.js, с чеклистами, тестами и оптимизациями.

Резюме

  • TanStack Query помогает упростить работу с пагинацией и бесконечной прокруткой.
  • Пагинация лучше для навигации и SEO; бесконечная прокрутка — для лент и бесшовного просмотра.
  • Важны кэширование, управление памятью, доступность и безопасность при работе с большими данными.

Дополнительные ресурсы: документация TanStack Query, статьи по cursor-based pagination, библиотеки виртуализации (react-window).

Поделиться: X/Twitter Facebook LinkedIn Telegram
Автор
Редакция

Похожие материалы

RDP: полный гид по настройке и безопасности
Инфраструктура

RDP: полный гид по настройке и безопасности

Android как клавиатура и трекпад для Windows
Гайды

Android как клавиатура и трекпад для Windows

Советы и приёмы для работы с PDF
Документы

Советы и приёмы для работы с PDF

Calibration в Lightroom Classic: как и когда использовать
Фото

Calibration в Lightroom Classic: как и когда использовать

Отключить Siri Suggestions на iPhone
iOS

Отключить Siri Suggestions на iPhone

Рисование таблиц в Microsoft Word — руководство
Office

Рисование таблиц в Microsoft Word — руководство