
SEO шаблоны
SEO-шаблон — правило, по которому собираются title, H1, description и текст для подборки с применённым фильтром. Одно правило работает на всех значениях фильтра: SEO-шаблон для ключа made_in даёт заголовок и для «Германии», и для любой другой страны.
Создание SEO-шаблона
mFilter → SEO шаблоны → Добавить.
| Поле | Что задаёт |
|---|---|
| Название | Только для списка в админке |
| Страница | SEO-шаблон действует на этой странице. Пустое поле — глобальный SEO-шаблон, на всех страницах |
| Ключи фильтров | Обязательное. Ключи, при которых SEO-шаблон срабатывает: made_in, color. Вводятся по одному, через Enter |
| Шаблон H1 | Заголовок страницы |
| Шаблон Title | Тег <title> |
| Шаблон Description | Мета-описание |
| Шаблон текста | SEO-текст под выдачей |
| Приоритет | Разрешает ничью между SEO-шаблонами. Меньше — раньше. От 0 до 100 |
| Активен | Выключенный SEO-шаблон не участвует в подборе |
SEO-шаблон без ключей фильтров не сохранится. Запись, попавшая в базу в обход админки, не сработает никогда. «SEO-шаблона на всё» не существует: роль запасного варианта играет поведение без SEO-шаблона.
Как подбирается SEO-шаблон
SEO-шаблоны делятся на два вида. С заполненным полем «Страница» шаблон страничный — он срабатывает только на ней. С пустым — глобальный, годится для любой страницы каталога; в списке такие помечены меткой «Глобальный».
Ключи выбранных фильтров сравниваются с полем «Ключи фильтров» в четыре прохода. Первый подошедший SEO-шаблон и применяется:
- SEO-шаблоны этой страницы, у которых набор ключей совпал точно;
- SEO-шаблоны этой страницы, у которых совпал хотя бы один ключ;
- глобальные SEO-шаблоны с точным совпадением;
- глобальные SEO-шаблоны с совпадением хотя бы одного ключа.
Порядок проходов важнее приоритета: страничный SEO-шаблон обойдёт глобальный, а точное совпадение — частичное. Приоритет решает только, какой из SEO-шаблонов взять, когда в одном проходе подошли несколько.
Из-за частичного совпадения SEO-шаблон для made_in применится и к странице, где выбраны made_in и color. Чтобы у такой пары был свой заголовок, заведите SEO-шаблон с обоими ключами — он выиграет на первом проходе.
Если SEO-шаблон не подобран
Заголовки собираются из названия страницы и выбранных значений:
- H1 — «Название страницы - Значения»;
- Title — то же плюс « | Название сайта»;
- Description — описание страницы плюс те же значения.
Значения подставляются подписями: раздел каталога — своим названием, производитель MiniShop3 — именем, числовое значение TV — подписью из индекса.
Плейсхолдеры
Значения фильтров
Значение подставляется по ключу фильтра, без префикса. Именно по ключу, а не по полю, из которого фильтр берёт данные: у фильтра {"brand": {"field": "product_brand"}} в SEO-шаблоне пишется {$brand}.
| Плейсхолдер | Что подставит |
|---|---|
{$made_in} | Все выбранные значения через запятую |
{$made_in_first} | Только первое значение |
{$made_in_count} | Сколько значений выбрано |
Диапазон приходит одним значением, а не двумя границами: {$price} даст «1000 — 5000», а при одной границе — «от 1000» или «до 5000». Слова и тире берутся из лексикона mfl_seo_range_from, mfl_seo_range_to, mfl_seo_range_separator.
Производители и разделы подставятся числом
Плейсхолдер выводит то значение, которое хранит фильтр. У цвета, размера, страны производства это текст. А у производителей MiniShop3, разделов каталога, категорий товаров и TV со списком ресурсов значение — идентификатор: {$vendor_id} поставит в заголовок 12, а не «Acme».
Заголовок, который mFilter собирает без SEO-шаблона, те же значения подставляет названиями. Поэтому для таких ключей SEO-шаблон пока даёт результат хуже стандартного — оставьте их без SEO-шаблона или напишите индивидуальную запись.
Поля страницы и сайта
| Плейсхолдер | Что подставит |
|---|---|
{$pagetitle}, {$longtitle}, {$menutitle}, {$introtext}, {$id}, {$alias}… | Любое поле ресурса под своим именем |
{$<имя TV>} | Значение TV этой страницы |
{$site_name} | Название сайта — единственная системная настройка, доступная в SEO-шаблоне |
Запись через точку
SEO-шаблоны, написанные по прежней версии документации, обращались к полям через точку: {$resource.pagetitle}, {$filters.vendor_id}. Такая запись работает и даёт то же, что {$pagetitle} и {$vendor_id}. Уровень вложенности — один: {$filters.vendor_id.gen} останется в заголовке как текст, падеж пишется как {$vendor_id.gen}. {$resource} и {$filters} целиком подставляются пустым местом.
Имена берутся в таком порядке: сначала поля ресурса, затем TV, затем значения фильтров. Последние записываются поверх, поэтому ключ фильтра, совпадающий с именем поля или TV — description, parent, id, — перекроет его.
Числа найденных товаров в SEO-шаблоне нет
SEO собирается в самом начале запроса, когда выборка ещё не сделана, поэтому подставить в заголовок количество найденного нечем. Число появляется позже: его выставляет сниппет mFilter, а выводит чанк обёртки — строкой «Найдено: N». В шаблоне ресурса оно доступно как {$_pls['mfilter.total']}, но только ниже вызова сниппета.
Модификаторы
Модификатор пишется через точку после ключа: {$made_in.gen}.
Падежи
| Модификатор | Падеж |
|---|---|
.nom | Именительный |
.gen | Родительный |
.dat | Дательный |
.acc | Винительный |
.ins | Творительный |
.pre | Предложный |
Множественное число — те же имена с суффиксом: .gen_plural, .dat_plural и так далее.
Формы берутся из Словоформ. Если слова там нет, подставится исходное значение — без ошибки и без записи в лог.
Склоняется одно значение. Если выбрано несколько, {$color} — это строка «красный, синий», словоформы для неё нет, и {$color.gen} выведет её в именительном падеже. Для падежа берите первое значение: {$color_first.gen}.
Обработка текста
| Модификатор | Что делает |
|---|---|
.lower, .upper | Нижний и верхний регистр |
.ucfirst, .lcfirst | Первая буква заглавная или строчная |
.strip | Убирает HTML-теги |
.trim | Убирает пробелы по краям |
.escape | Экранирует кавычки и угловые скобки |
Неизвестный модификатор ошибки не вызывает — значение подставится как есть. Поэтому опечатка в {$made_in.gne} выглядит как «падеж не применился».
Условия
Блок {if $ключ}…{/if} выводит содержимое, только когда значение непустое:
{$pagetitle} {if $color}цвета {$color.gen}{/if}Ветка {else} выводится, когда значение пустое. Она нужна SEO-шаблону, который подобран по частичному совпадению ключей: SEO-шаблон для color и made_in применится и к странице, где выбрана только страна.
{$pagetitle} {if $color}цвета {$color.gen}{else}всех цветов{/if}Внутри блока работают те же плейсхолдеры и модификаторы, блоки можно вкладывать друг в друга. Незакрытый блок или второй {else} в одном блоке выводятся как текст — так ошибку видно в заголовке.
Пробелы
Перед сборкой парсер заменяет подряд идущие пробелы и переносы строк одним пробелом, а края всего текста обрезает. Пробелы внутри ветки остаются как написаны: {$pagetitle}{if $color} — {$color}{/if} даст «Диваны — синий». Разметку многострочного SEO-текста это не портит, но пустые строки между абзацами исчезнут — разделяйте абзацы тегами.
Примеры SEO-шаблонов
Страна производства
Ключи фильтров: made_in
Title: Товары из {$made_in.gen} — купить в {$site_name}
H1: Товары из {$made_in.gen}
Description: Товары из {$made_in.gen} в наличии. Доставка по всей России.Страна и цвет
Ключи фильтров: made_in, color
Title: Товары из {$made_in.gen} {$color.gen} цвета | {$site_name}
H1: Товары из {$made_in.gen}, {$color.gen} цветаДиапазон цен
Ключи фильтров: price
Title: {$pagetitle} {$price} рублей
H1: {$pagetitle}, {$price}Индексация
Поля «не индексировать» у SEO-шаблона нет: страницы закрываются системными настройками.
| Настройка | По умолчанию | Что делает |
|---|---|---|
mfilter.seo_noindex_filtered | Нет | Закрывает все отфильтрованные страницы разом |
mfilter.seo_max_filters | 2 | Закрывает страницу, где выбрано больше указанного числа фильтров |
mfilter.seo_max_values | 1 | Закрывает страницу, где у одного фильтра выбрано больше указанного числа значений |
Диапазон считается одним фильтром, хотя в адрес попадает двумя границами. Значения же считаются внутри каждого ключа отдельно, а у границы значение одно — поэтому seo_max_values диапазон не закрывает даже при лимите в единицу.
Индивидуальная запись снимает закрытие: страница, для которой её написали, индексируется, даже если попала под лимиты. Настройка mfilter.seo_noindex_filtered сильнее — она закрывает страницу и при заполненной записи.
Canonical
Адрес вычисляется, в SEO-шаблоне его не задают.
- Открытая для индексации страница получает canonical на саму себя — отфильтрованный адрес без номера страницы, сортировки и лимита;
- закрытая — canonical на страницу без фильтров.
Адрес всегда абсолютный.
Программный доступ
$mfilter = $modx->services->get('mfilter');
$seoBuilder = $mfilter->getSeoBuilder();
$seoData = $seoBuilder->build($resourceId, $filters);
// [
// 'h1' => 'Товары из Германии',
// 'title' => 'Товары из Германии — купить в Название сайта',
// 'description' => 'Товары из Германии в наличии...',
// 'text' => '<p>SEO текст...</p>',
// 'canonical' => 'https://site.ru/catalog/made_in--germaniya/',
// 'noindex' => false
// ]Для несуществующего ресурса build() вернёт пустой массив.
| Метод | Что делает |
|---|---|
shouldNoindex(array $filters): bool | Попадает ли набор фильтров под лимиты индексации |
getCanonical(int $resourceId, array $filters): string | Canonical для набора фильтров |
clearCache(): void | Сбрасывает кэш загруженных SEO-шаблонов |
Вывод на фронтенде
Плейсхолдеры mfilter.seo.* расставляет плагин на каждой странице фильтрации; в Fenom они доступны через массив $_pls. Вне фильтрации значения пустые, у noindex — false.
Вставьте в шаблон ресурса каталога (<head> и <body>):
<title>{$_pls['mfilter.seo.title'] ?: $_modx->resource.pagetitle}</title>
<meta name="description" content="{$_pls['mfilter.seo.description'] ?: $_modx->resource.description}">
{if $_pls['mfilter.seo.canonical']}
<link rel="canonical" href="{$_pls['mfilter.seo.canonical']}">
{/if}
{if $_pls['mfilter.seo.noindex']}
<meta name="robots" content="noindex, follow">
{/if}
<h1 data-mfilter-h1>{$_pls['mfilter.seo.h1'] ?: $_modx->resource.pagetitle}</h1>
<div data-mfilter-seo-text{if !$_pls['mfilter.seo.text']} style="display:none"{/if}>
{$_pls['mfilter.seo.text']}
</div>Маркеры для AJAX
Атрибуты data-mfilter-h1 и data-mfilter-seo-text (или классы h1.mfilter-h1 и .mfilter-seo-text) обязательны: без них JS не найдёт элементы при AJAX-фильтрации, и они обновятся только после F5. Класс mfilter-h1 срабатывает только на теге <h1> — на другом теге нужен атрибут. Меты title, description, canonical, robots в <head> JS обновляет по стандартным селекторам — маркеры не нужны.
После обновления JS вызывает событие mfilter:seoUpdate — его можно слушать, если на странице есть свои элементы с SEO-данными.
Когда SEO-шаблон не нужен
SEO-шаблон описывает правило для всех значений сразу. Если текст нужен под одну конкретную подборку — свой заголовок и описание для определённого бренда, — правило заводить не обязательно: такая страница настраивается поштучно на вкладке Индивидуальные SEO.
Одно другому не мешает. Заполненные поля индивидуальной записи перекрывают SEO-шаблон, незаполненные берутся из него: правило закрывает массу страниц, а руками пишется то, ради чего стоит сесть за текст.

