Для чего нужны эти теги в SEO?
Когда на сайте есть каталог товаров или блог с постраничной навигацией, поисковые роботы видят множество похожих URL: /catalog/, /catalog/?PAGEN_1=2, /catalog/?PAGEN_1=3 и так далее. Без правильных мета-тегов Google и Яндекс могут расценить эти страницы как дубли и размыть ссылочный вес — что плохо скажется на позициях сайта.
Теги rel="prev" и rel="next" сообщают поисковику: «Эти страницы — часть единой последовательности», помогая объединить их в один логический блок и корректно оценить общий вес. Тег rel="canonical" на страницах пагинации указывает основную версию URL (как правило, первую страницу или текущую без мусорных GET-параметров), исключая появление дублей в индексе.
В итоге три простых тега решают сразу две задачи: правильная кластеризация страниц пагинации и защита от дублей. Штатного инструмента для этого в 1С-Битрикс нет — реализуем сами.
У нас реализация была на компоненте news.list.
1. В нужном компоненте news.list создаем файл result_modifier.php. И вставляем следующий код, который только сохраняет данные пагинации:
result_modifier.php
<?php
if (!defined('B_PROLOG_INCLUDED') || B_PROLOG_INCLUDED !== true) die();
$arResult['NAV_NUM'] = $arResult['NAV_RESULT']->NavNum;
$arResult['NAV_PAGE_NOMER'] = $arResult['NAV_RESULT']->NavPageNomer;
$arResult['NAV_PAGE_COUNT'] = $arResult['NAV_RESULT']->NavPageCount;
$arResult['SECTION_CODE'] = $arParams['SECTION_CODE'];
$this->__component->SetResultCacheKeys([
'NAV_NUM',
'NAV_PAGE_NOMER',
'NAV_PAGE_COUNT',
]);
2. В этом же месте создаем еще один файл component_epilog.php. И вставляем следующий код - сохраняем данные в глобальную переменную.
component_epilog.php
<?php
if (!defined('B_PROLOG_INCLUDED') || B_PROLOG_INCLUDED !== true) die();
// Передаём данные пагинации в глобальную переменную для init.php
$GLOBALS['NEWS_PAGINATION'] = [
'cur' => (int)$arResult['NAV_PAGE_NOMER'],
'last' => (int)$arResult['NAV_PAGE_COUNT'],
];
3. Вставляем в наш init.php следующий код - читаем глобальную переменную и патчим через буфер
init.php
// добавление prev next при постраничной навигации
AddEventHandler('main', 'OnEndBufferContent', 'updatePaginationPrevNext');
function updatePaginationPrevNext(&$content)
{
if (!is_string($content)) return;
$baseUrl = $_SERVER['REQUEST_URI'];
// Чистим служебные параметры
$baseUrl = preg_replace('/([?&])PAGEN_\\d+=\\d+(&|$)/i', '$1', $baseUrl);
$baseUrl = preg_replace('/([?&])clear_cache=[^&]*(&|$)/i', '$1', $baseUrl);
$baseUrl = preg_replace('/([?&])sessid=[^&]*(&|$)/i', '$1', $baseUrl);
$baseUrl = rtrim($baseUrl, '?&');
$scheme = (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on') ? 'https' : 'http';
$host = $scheme . '://' . strtok($_SERVER['HTTP_HOST'], ':');
$glue = strpos($baseUrl, '?') !== false ? '&' : '?';
$inject = '';
// prev / next из данных компонента
if (!empty($GLOBALS['NEWS_PAGINATION'])) {
$curPage = $GLOBALS['NEWS_PAGINATION']['cur'];
$lastPage = $GLOBALS['NEWS_PAGINATION']['last'];
if ($curPage < $lastPage) {
$inject .= '<link rel="next" href="' . $host . $baseUrl . $glue . 'PAGEN_1=' . ($curPage + 1) . '">' . "\n";
}
if ($curPage > 1) {
$urlPrev = ($curPage - 1) > 1
? $host . $baseUrl . $glue . 'PAGEN_1=' . ($curPage - 1)
: $host . $baseUrl;
$inject .= '<link rel="prev" href="' . $urlPrev . '">' . "\n";
}
}
// Удаляем старый canonical
$content = preg_replace('#<link[^>]+rel=["\']canonical["\'][^>]*>\s*#is', '', $content);
// Вставляем перед </head>
$content = str_replace('</head>', $inject . '</head>', $content);
// Title / Description / H1 / Keywords с номером страницы
$pageNum = 0;
foreach ($_GET as $key => $val) {
if (preg_match('/^PAGEN_\\d+$/i', $key)) {
$pageNum = (int)$val;
break;
}
}
if ($pageNum > 1) {
$suffix = ' — страница ' . $pageNum;
// Title
$content = preg_replace(
'#(<title>)(.*?)(</title>)#is',
'$1$2' . htmlspecialchars($suffix) . '$3',
$content
);
// Description
$content = preg_replace(
'#(name=["\']description["\'][^>]*content=["\'])([^"\']*?)(["\'])#is',
'$1$2' . htmlspecialchars($suffix) . '$3',
$content
);
// Keywords
$content = preg_replace(
'#(name=["\']keywords["\'][^>]*content=["\'])([^"\']*?)(["\'])#is',
'$1$2' . htmlspecialchars($suffix) . '$3',
$content
);
// H1 — только первый на странице
$content = preg_replace(
'#(<h1[^>]*>)(.*?)(</h1>)#is',
'$1$2' . htmlspecialchars($suffix) . '$3',
$content,
1
);
}
}
// добавление prev next при постраничной навигации
Что получилось в итоге
После подключения кода в шаблоне компонента news.list каждая страница пагинации получила корректный набор мета-тегов в блоке . Проверим на примере четвёртой страницы раздела новостей:
HTML
<title>Новости — страница 4</title>
<description>Новости — страница 4</description>
<h1>Новости — страница 4</h1>
<link rel="canonical" href="https://site.ru/company/news/">
<link rel="next" href="https://site.ru/company/news/?PAGEN_1=5">
<link rel="prev" href="https://site.ru/company/news/?PAGEN_1=3">
title с номером страницы — заголовок автоматически дополняется суффиксом «— страница N», что делает каждую страницу пагинации уникальной в поисковой выдаче и исключает дубли по title.
rel="canonical" — указывает на чистый URL раздела без GET-параметров. Это сигнал для поисковика: «главная версия этого контента — вот здесь», что защищает от склейки страниц пагинации с основной.
rel="next" — ссылает на следующую страницу (PAGEN_1=5), формируя цепочку вперёд.
rel="prev" — ссылает на предыдущую страницу (PAGEN_1=3), замыкая двустороннюю последовательность.
Таким образом поисковый робот видит не набор разрозненных URL, а единую логическую цепочку страниц одного раздела. Google объединяет ссылочный вес всей серии и, как правило, показывает в выдаче первую страницу как наиболее релевантную. Яндекс rel="prev/next" технически игнорирует, однако canonical и уникальные title отрабатывают и для него.