Пагинация в WordPress: как вывести постраничную навигацию

Пагинация в WordPress: как вывести постраничную навигацию

Кратко: пагинация в WordPress — это разбивка списка записей на страницы со ссылками «предыдущая», «следующая» и номерами. Выводится одной из трёх функций: paginate_links() даёт полный контроль над разметкой, the_posts_pagination() печатает готовый блок с номерами, next_posts_link() и previous_posts_link() — только ссылки «туда-обратно». Номера страниц работают лишь при постоянных ссылках, отличных от простых, и только там, где есть основной цикл с известным max_num_pages. Для своей выборки на WP_Query нужно передавать параметр paged и вызывать wp_reset_postdata(), а для кастомных типов записей — исправлять запрос через pre_get_posts. Три главные грабли: 404 на второй странице из-за несброшенных правил перезаписи, лишний подсчёт записей без no_found_rows и расхождение количества записей в запросе и в самой функции пагинации.

Зачем нужна пагинация и как её видит WordPress

Пагинация отвечает на простой вопрос: что делать, если записей больше, чем разумно показывать на одном экране. Вариантов три — выводить всё подряд, показывать по 10–20 записей со страницами или подгружать следующие порции по кнопке. Первый вариант убивает скорость и внимательность читателя, поэтому на живых сайтах остаются второй и третий, и оба в WordPress опираются на один механизм.

Механизм такой. Постоянная ссылка на вторую страницу архива формируется по шаблону /page/2/, и значение номера страницы WordPress передаёт в объект запроса как переменную paged. Дальше основной цикл получает уже готовый набор записей нужной страницы, а вам остаётся только напечатать ссылки навигации. Никакой отдельной «страницы архива» в базе не существует: это тот же шаблон, тот же цикл, но со сдвигом выборки.

Отсюда первое практическое требование: пагинация с номерами страниц работает только при постоянных ссылках, отличных от простых вида ?p=123. Если в настройках стоит простой формат, номера страниц не построятся — вместо них останутся только ссылки «следующая» и «предыдущая». Как переключить адреса и не потерять существующие ссылки, разобрано в статье про ЧПУ в WordPress и настройку ссылок.

Второй важный момент: пагинация бывает в трёх разных местах, и в каждом она настраивается по-своему. Это архив записей или рубрика, где есть основной цикл. Это отдельная запись, разбитая тегом <!--nextpage--> на несколько страниц. И это ваша собственная выборка на WP_Query — от блока «Похожие статьи» до каталога товаров. Путать эти случаи нельзя: функции вывода и параметры у них различаются.

Три функции вывода: что выбрать

В ядре WordPress есть несколько готовых решений. Они не конкурируют между собой, а закрывают разные задачи, поэтому выбор сводится к вопросу «сколько контроля нужно» и «нужны ли номера страниц».

Ветеран темы — paginate_links(). Эта функция принимает массив параметров и возвращает уже готовую разметку: либо строку ссылок, либо список, либо массив ссылок по одной. Именно она стоит внутри остальных функций вывода, поэтому если нужно изменить вид, классы или порядок элементов, работать надо с ней.

the_posts_pagination() — обёртка для главного цикла: она сама подставляет общее число страниц, оборачивает результат в <nav>, поддерживает заголовки для скринридеров и словари переводов. Её стоит брать, когда тема стандартная и требуется просто аккуратный блок с номерами.

next_posts_link() и previous_posts_link() печатают только две ссылки. Это вариант для блогов с последовательным чтением, где читатель листает ленту, а не прыгает на страницу 7.

Функция Что выводит Где вызывать Когда выбирать
paginate_links() Номера страниц, ссылки «назад» и «вперёд», любой формат разметки Внутри шаблона, после цикла; для кастомных запросов требует переданного total Нужен контроль над разметкой, классами, размером «окна» и текстами
the_posts_pagination() Готовый nav с номерами и заголовком Только для главного запроса после the_post()-цикла Стандартная тема, нужен быстрый результат без правок разметки
next_posts_link() / previous_posts_link() Две ссылки: следующие и предыдущие записи Главный запрос, блоги и ленты Последовательное чтение, где номера страниц не нужны
wp_link_pages() Разбивку одной записи на части по <!--nextpage--> Внутри single.php, до endwhile Длинная статья или пошаговая инструкция внутри одной записи

Если тема нестандартная и вывод собирается с нуля, шаблон страницы придётся править в дочерней теме — о том, как её завести и не потерять правки при обновлении, рассказано в статье шаблоны страниц в WordPress.

paginate_links: разбор параметров на примере

Самый частый случай в теме — вывод пагинации в архиве с уточнёнными настройками: короткое «окно» вокруг текущей страницы, свои тексты ссылок и сохранение GET-параметров фильтра. Ниже рабочий пример для шаблона архива.

<?php
// Пагинация в archive.php / category.php — полный контроль над видом
$total = $wp_query->max_num_pages;

if ( $total > 1 ) {
	$links = paginate_links( array(
		'total'     => $total,
		'current'   => max( 1, get_query_var( 'paged' ) ),
		'type'      => 'array',
		'mid_size'  => 1,
		'end_size'  => 1,
		'prev_text' => 'Назад',
		'next_text' => 'Вперёд',
		// сохраняем параметры фильтра при переходе между страницами
		'add_args'  => array_filter( array(
			'sort'  => isset( $_GET['sort'] ) ? sanitize_key( $_GET['sort'] ) : '',
			'price' => isset( $_GET['price'] ) ? sanitize_text_field( $_GET['price'] ) : '',
		) ),
	) );

	if ( $links ) {
		echo '<nav class="pager" aria-label="Навигация по страницам">';
		foreach ( $links as $link ) {
			echo $link; // строки уже экранированы ядром
		}
		echo '</nav>';
	}
}

Разберём ключевое. Параметр total берётся из max_num_pages основного запроса — если подставить сюда произвольное число, ссылки начнут вести в никуда. type => 'array' отдаёт массив строк вместо списка, поэтому разметку и классы вы задаёте сами: это единственный способ добавить свои атрибуты, не переписывая CSS темы. mid_size и end_size управляют «окном» вокруг активной страницы: при 40 страницах архива без них пагинация растянется на две строки и начнёт мешать.

Отдельно про add_args. Если на сайте есть фильтр по метаполям или сортировке, без этого параметра переход на вторую страницу сбросит фильтр, и посетитель получит совсем другой список. Значения обязательно пропускайте через sanitize_key() и sanitize_text_field(), прежде чем отдавать в разметку. Если же вид пагинации нужно изменить глобально — например, добавить rel или обернуть каждую ссылку в свой контейнер, — удобнее подключиться к фильтрам paginate_links и paginate_links_output. Как устроены фильтры и чем они отличаются от действий, подробно разобрано в материале про хуки WordPress: actions и filters.

the_posts_pagination и страницы внутри записи

Когда разметка устраивает, достаточно одной строки. Функция сама получит общее число страниц основного запроса и напечатает блок с номерами, стрелками и скрытым заголовком для скринридеров.

<?php
// Стандартная пагинация основного цикла: 2 номера вокруг активной страницы
the_posts_pagination( array(
	'mid_size'           => 2,
	'prev_text'          => '&larr; Предыдущая',
	'next_text'          => 'Следующая &rarr;',
	'screen_reader_text' => 'Навигация по записям',
) );

Важное ограничение: функция работает только с главным запросом страницы и должна вызываться после цикла while ( have_posts() ). Для выборки, которую вы создали сами через new WP_Query, она возьмёт max_num_pages глобального объекта, а не вашего, — и ссылки будут вести не туда. Это самая частая причина «пагинация есть, но вторая страница пустая».

Другая задача — разбить длинную статью на части внутри одной записи. Здесь работает тег <!--nextpage--> в редакторе и функция wp_link_pages() в шаблоне. Полезно передать ей next_or_number => 'number', чтобы читатель видел номера частей, и before с after, чтобы обернуть вывод в свой контейнер. Тот же приём используют для пошаговых инструкций и больших обзоров: страница получает чистые адреса вида /page/2/ внутри записи, а не одну бесконечную простыню.

И ещё один слой, о котором вспоминают позже всего: если архив строится не по записям, а по своему типу контента — портфолио, отзывы, каталог услуг, — вывод и запрос придётся собирать вручную. Основа такого архива описана в статье про кастомные типы записей и таксономии.

Пагинация в своём WP_Query

Здесь ломается большинство самодельных выборок. Проблема не в функции вывода, а в том, что запрос и пагинация должны «договориться» об одних и тех же числах: сколько записей на странице и какая страница запрошена.

<?php
// 1. Своя выборка с корректной пагинацией
$paged = max( 1, (int) get_query_var( 'paged' ), (int) get_query_var( 'page' ) );

$news = new WP_Query( array(
	'post_type'      => 'post',
	'posts_per_page' => 12,
	'paged'          => $paged,
	'category_name'  => 'news',
) );

if ( $news->have_posts() ) {
	while ( $news->have_posts() ) {
		$news->the_post();
		the_title( '<h3><a href="' . esc_url( get_permalink() ) . '">', '</a></h3>' );
	}

	// 2. Печатаем пагинацию по данным ИМЕННО этого запроса
	echo paginate_links( array(
		'total'   => $news->max_num_pages,
		'current' => $paged,
		'format'  => '?paged=%#%',
	) );
}

wp_reset_postdata(); // обязательно: возвращаем глобальный $post

Три детали в этом коде решают всё. Первая — paged вычисляется из get_query_var('paged'), а на статической главной странице ещё и из get_query_var('page'): там WordPress использует другую переменную. Если взять только одну, на главной пагинация будет всегда показывать первую страницу. Вторая — total берётся из $news->max_num_pages, а не из глобального объекта. Третья — wp_reset_postdata() после цикла: без него ломается всё, что идёт дальше на странице, включая хлебные крошки и связанные записи.

Обратная задача — переопределить количество записей в стандартных архивах. Правки в шаблоне здесь не работают: нужно вмешаться в запрос на раннем этапе.

<?php
// 3. Меняем число записей на странице архива своего типа контента
add_action( 'pre_get_posts', 'my_archive_per_page' );

function my_archive_per_page( $query ) {
	if ( is_admin() || ! $query->is_main_query() ) {
		return;
	}

	if ( $query->is_post_type_archive( 'portfolio' ) ) {
		$query->set( 'posts_per_page', 24 );
	}
}

Проверки is_admin() и is_main_query() обязательны: без них правило доберётся до админки и до второстепенных выборок, и вы получите то пропавшие записи в списке правки, то сломанную сортировку виджетов. Тот же приём используют для таксономий и поиска, меняя posts_per_page точечно по условию.

ЧПУ, вторая страница и ошибка 404

Самая известная болезнь пагинации — 404 на /page/2/, при том что первая страница архива открывается нормально. Причина почти всегда одна: WordPress не знает, куда вести такой адрес, потому что правила перезаписи не соответствуют текущим настройкам.

Что проверять по порядку. Первое — формат постоянных ссылок: при простом формате номеров страниц не существует в принципе. Второе — сохранение настроек постоянных ссылок: WordPress пересобирает правила при открытии страницы «Постоянные ссылки», и если правила пересобирали вручную, а потом добавили новый тип контента, старые правила могут не знать о нём. Третье — код, который регистрирует свои правила через add_rewrite_rule(): при первом подключении их нужно один раз сбросить.

<?php
// 4. Однократный сброс правил перезаписи при активации темы
add_action( 'after_switch_theme', 'my_flush_rewrite_once' );

function my_flush_rewrite_once() {
	// сначала регистрируем свои правила, потом сбрасываем ОДИН раз
	add_rewrite_rule( '^news/page/([0-9]+)/?$', 'index.php?paged=$matches[1]', 'top' );
	flush_rewrite_rules();
}

Вызывать flush_rewrite_rules() на каждом запросе нельзя: это тяжёлая операция, которая перестраивает таблицу правил и заметно замедляет сайт. Правильное место — активация темы или плагина, как в примере выше. И ещё одна тихая причина 404 — конфликт с плагинами, которые чистят адреса или режут запросы; если после отключения одного из них вторая страница открылась, ищите причину там. Общий разбор типовых сбоев с пошаговыми решениями собран в статье ошибки WordPress: 10 частых проблем и их решение.

Отдельно следите за тем, чтобы страницы пагинации не попадали в карту сайта как самостоятельные документы, но при этом оставались доступными для обхода. Как это устроено, описано в материале про XML-карту сайта в WordPress.

Архивы таксономий и каталог WooCommerce

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

В WooCommerce каталог на странице магазина использует собственную пагинацию, и правки стандартных функций там не сработают. Количество товаров на странице меняют фильтром, а саму навигацию — вызовом функции шаблона списка. Если каталог собирается вручную, а не через шорткод, настройки выборки описаны в статье про настройку каталога товаров WooCommerce.

Третий вариант, который часто просят «вместо пагинации», — кнопка «Показать ещё» с подгрузкой через AJAX или бесконечный скролл. У него есть цена, и её стоит понимать заранее.

Критерий Классическая пагинация Кнопка «Показать ещё» и бесконечный скролл
Адреса страниц Каждая страница имеет свой URL и может попасть в индекс Адрес один, отдельные порции контента поисковику почти не видны
Поведение читателя Читатель видит объём материала и осознанно уходит глубже Прокрутка не заканчивается, вернуться к нужному месту сложнее
Возврат на позицию Работает по ссылке и из истории браузера Требует дополнительной логики, иначе позиция теряется
Скорость и запросы Один запрос на страницу, нагрузка предсказуема Много последовательных запросов, нагрузка растёт с глубиной прокрутки
Когда выбирать Каталоги, архивы, рубрики, всё, что важно для поиска Ленты и витрины внутри одной страницы, где SEO не главное

Компромисс, который обычно и работает: визуально оставить кнопку подгрузки, но сохранить настоящие ссылки пагинации — скрытыми для мыши, зато доступными поисковику. Тогда читатель листает ленту, а обходчик видит обычную структуру страниц. Второй полезный элемент навигации, который стоит продумать вместе с пагинацией, — хлебные крошки в WordPress: они возвращают читателя на уровень выше с любой страницы.

Производительность: подсчёт записей и кэш

Пагинация — это дополнительная работа для базы. Чтобы узнать общее число страниц, WordPress выполняет запрос подсчёта найденных записей, а сам список запрашивает отдельно. На больших сайтах именно подсчёт становится узким местом.

Что влияет Как проявляется Что делать
Подсчёт найденных записей Второй тяжёлый запрос на каждой странице архива Оставить подсчёт там, где нужны номера страниц; убрать no_found_rows нельзя, если пагинация есть, — вместо этого ограничить условия выборки
Сложные метазапросы и таксономии Медленный ответ при фильтрации и сортировке Упростить условия, убрать сортировку по случайному полю, кэшировать список
Выборка полных объектов Лишние поля и запросы к метаданным Брать fields => 'ids', если нужны только ссылки и заголовки
Отсутствие кэша Один и тот же запрос повторяется при каждом заходе Кэшировать список через транзиент на несколько часов

Практический вывод прост: пагинация сама по себе недорога, дорого — сложная выборка под ней. Если архив фильтруется по десятку метаполей, никакая функция вывода не спасёт; сначала упрощают запрос, потом кэшируют результат. Как это работает целиком, разобрано в статье про кэширование WordPress для новичков, а готовые приёмы оптимизации собраны в подборке полезные сниппеты functions.php.

И одна вещь, которую проверяют в последнюю очередь, хотя стоило бы в первую: количество записей на странице должно совпадать в трёх местах — в настройках чтения, в параметре posts_per_page вашей выборки и в ожиданиях пагинации. Расхождение на единицу даёт «пропавшие» записи: они есть в базе, но не показываются ни на одной странице, потому что попали в промежуток между страницами.

Частые ошибки и порядок отладки

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

Шаг первый: убедиться, что включены постоянные ссылки, отличные от простых. Шаг второй: открыть страницу /page/2/ вручную и посмотреть, что отвечает сервер. Шаг третий: сравнить max_num_pages вашего запроса с тем, что реально выводится, — для этого удобно временно вывести var_dump при включённой отладке. Шаг четвёртый: проверить, вызывается ли wp_reset_postdata(), и не переопределяет ли кто-то запрос через pre_get_posts без проверки главного запроса. Пятый шаг: пересобрать правила перезаписи один раз и убедиться, что 404 исчез.

<?php
// 5. Диагностика: что происходит на второй странице
add_action( 'wp_footer', 'my_pager_debug' );

function my_pager_debug() {
	if ( ! current_user_can( 'manage_options' ) ) {
		return;
	}

	global $wp_query;
	printf(
		'<!-- paged=%d, page=%d, max_num_pages=%d, found=%d -->',
		(int) get_query_var( 'paged' ),
		(int) get_query_var( 'page' ),
		(int) $wp_query->max_num_pages,
		(int) $wp_query->found_posts
	);
}

Такой комментарий в коде страницы виден только администратору и сразу отвечает на главные вопросы: какая страница запрошена, сколько их всего и сколько записей нашлось. Если max_num_pages равно единице при живом архиве — проблема в запросе, а не в выводе. Если номер страницы всегда равен единице — теряется переменная paged, и возвращаться надо к своему WP_Query.

Заодно проверьте мелочи, которые портят впечатление: ссылки пагинации без осмысленных текстов, отсутствие aria-label у блока навигации, кнопки размером меньше пальца на телефоне. Если меню и ссылки в теме ведут себя непредсказуемо, полезно сначала навести порядок в навигации как таковой — об этом статья про меню WordPress и вывод навигации, а блоки похожих материалов рядом с пагинацией разобраны в публикации про связанные записи в WordPress. И обязательно работайте в дочерней теме WordPress, иначе первое же обновление темы снесёт все правки шаблонов.

TimeWeb

Частые вопросы про пагинацию в WordPress

Почему на второй странице пагинации появляется 404?

Чаще всего потому, что постоянные ссылки стоят в простом формате либо правила перезаписи не соответствуют текущим настройкам. Проверьте формат ссылок, откройте страницу настроек «Постоянные ссылки» один раз, чтобы WordPress пересобрал правила, и убедитесь, что свои правила через add_rewrite_rule сбрасываются только при активации темы или плагина, а не на каждом запросе.

Чем paginate_links отличается от the_posts_pagination?

paginate_links принимает массив параметров и возвращает разметку, которую вы полностью контролируете: классы, тексты, размер окна вокруг активной страницы. the_posts_pagination — готовая обёртка для главного запроса: она сама подставляет общее число страниц и печатает nav с заголовком для скринридеров. Для кастомного WP_Query берите paginate_links и передавайте total из max_num_pages этого запроса.

Как вывести пагинацию для своего WP_Query?

Передайте в запрос параметр paged со значением get_query_var, а на статической главной дополнительно учитывайте get_query_var page. Общее число страниц берите из max_num_pages своего объекта запроса, а не из глобального. После цикла обязательно вызывайте wp_reset_postdata, иначе пострадают все блоки ниже на странице.

Можно ли использовать пагинацию без ЧПУ?

Да, но без номеров страниц. При простом формате постоянных ссылок останутся только ссылки «следующая» и «предыдущая», а переходы будут выглядеть как параметры в адресе. Если архив важен для поиска, лучше включить человекопонятные адреса — это одно переключение в настройках, но существующие ссылки стоит проверить после переключения.

Как изменить количество записей на странице архива?

Не через шаблон, а через запрос: подключитесь к pre_get_posts, проверьте is_admin и is_main_query, а затем задайте posts_per_page через set. Проверки обязательны, иначе правило сработает в админке и в побочных выборках. Число записей в запросе и в функции пагинации должно совпадать, иначе часть записей не покажется ни на одной странице.

Стоит ли заменять пагинацию на бесконечный скролл?

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

Вывод

Пагинация в WordPress — задача на согласование чисел, а не на разметку. Основной цикл знает номер страницы и общее количество страниц; ваша выборка на WP_Query должна получить те же значения явно, иначе ссылки будут вести в пустоту. Порядок работы такой: включить человекопонятные адреса, выбрать функцию вывода под задачу, для своей выборки передать paged и взять total из max_num_pages, для кастомных архивов поправить запрос через pre_get_posts, а правила перезаписи сбрасывать один раз при активации.

Дальше остаётся производительность и аккуратность: не считать лишние записи, кэшировать сложные выборки, не забывать wp_reset_postdata и проверять, что количество записей на странице совпадает во всех местах. Если нужна разметка под дизайн — берите paginate_links и меняйте её через фильтры, а не через CSS-костыли. И проверяйте вторую страницу вручную после каждой правки шаблона: именно там всплывают и 404, и потерянные записи.

Если разбираться с пагинацией, шаблонами и выводом архивов не хочется — эту работу можно передать команде. Мы правим темы и дочерние темы, настраиваем вывод архивов, каталогов и таксономий, чиним 404 на страницах пагинации и ускоряем тяжёлые выборки. Посмотрите услуги по разработке и доработке сайтов, изучите портфолио выполненных проектов и почитайте блог о WordPress и продвижении сайтов. Опишите задачу через форму контактов — предложим решение и рассчитаем стоимость.

Clearfy

Поделиться:
Telegram ВКонтакте