Микроразметка schema.org для расширенных сниппетов в выдаче: хлебные крошки, логотип, контакты, статьи. Чтобы JSON-LD не прописывать для каждой страницы я написал небольшой сниппет, который генерирует всю разметку автоматически.
$context = $modx->getOption('context', $scriptProperties, 'web');
$showOrganization = (bool)$modx->getOption('organization', $scriptProperties, 1);
$showBreadcrumbs = (bool)$modx->getOption('breadcrumbs', $scriptProperties, 1);
$showWebSite = (bool)$modx->getOption('website', $scriptProperties, 1);
$explicitType = $modx->getOption('type', $scriptProperties, '');
$resource = $modx->resource;
if (!$resource) return '';
$id = $resource->get('id');
$pagetitle = $resource->get('pagetitle');
$description = $resource->get('description');
$menutitle = $resource->get('menutitle') ?: $pagetitle;
$template = $resource->get('template');
// Собираем настройки сайта
$siteUrl = $modx->getOption('site_url', null, '', true);
$siteName = $modx->getOption('site_name', null, '');
$logo = $modx->getOption('logo', null, $siteUrl . 'images/logo.png');
$sameAs = [];
if ($socialTg) $sameAs[] = $tg;
// Определяем тип страницы по установленному ID шаблона
if (!empty($explicitType)) {
$pageType = $explicitType;
} else {
// Автоопределение по шаблонам (подставьте свои ID)
$mainTemplate = 1; // ID шаблона главной
$contactsTemplate = 3; // ID шаблона контактов
$blogListTemplate = 4; // ID шаблона списка статей
$articleTemplate = 5; // ID шаблона отдельной статьи
$aboutTemplate = 2; // ID шаблона о компании
if ($template == $mainTemplate) {
$pageType = 'main';
} elseif ($template == $contactsTemplate) {
$pageType = 'contacts';
} elseif ($template == $blogListTemplate) {
$pageType = 'bloglist';
} elseif ($template == $articleTemplate) {
$pageType = 'article';
} elseif ($template == $aboutTemplate) {
$pageType = 'about';
} else {
$pageType = 'default'; // обычная WebPage
}
}
// Формируем граф
$graph = [];
// Organization
if ($showOrganization) {
$orgType = ($pageType == 'main') ? 'LocalBusiness' : 'Organization';
$organization = [
'@type' => $orgType,
'@id' => $siteUrl . '#organization',
'name' => $siteName,
'url' => $siteUrl,
'logo' => $logo,
];
if (!empty($sameAs)) {
$organization['sameAs'] = $sameAs;
}
// Для главной можно добавить адрес и телефон (если есть настройки)
if ($pageType == 'main') {
$address = $modx->getOption('address', null, '');
$phone = $modx->getOption('phone', null, '');
if ($address) {
$organization['address'] = [
'@type' => 'PostalAddress',
'addressLocality' => $address
];
}
if ($phone) {
$organization['telephone'] = $phone;
}
}
$graph[] = $organization;
}
// WebSite ---
if ($showWebSite) {
$graph[] = [
'@type' => 'WebSite',
'@id' => $siteUrl . '#website',
'name' => $siteName,
'url' => $siteUrl,
'publisher' => [
'@id' => $siteUrl . '#organization'
]
];
}
// Основной блок страницы
$currentUrl = $modx->makeUrl($id, $context, '', 'full');
// Базовый набор свойств для всех страниц
$pageData = [
'@id' => $currentUrl . '#webpage',
'url' => $currentUrl,
'name' => $pagetitle,
'isPartOf' => [
'@id' => $siteUrl . '#website'
],
'inLanguage' => 'ru',
'description' => $description ?: $pagetitle
];
// Определяем @type в зависимости от $pageType
switch ($pageType) {
case 'main':
$pageData['@type'] = 'WebPage'; // либо можно 'AboutPage', но лучше оставить WebPage
// Можно добавить свойство mainEntity, если нужно
break;
case 'contacts':
$pageData['@type'] = 'ContactPage';
break;
case 'about':
$pageData['@type'] = 'AboutPage';
break;
case 'bloglist':
$pageData['@type'] = 'CollectionPage';
break;
case 'article':
$pageData['@type'] = 'BlogPosting'; // или Article
// Добавляем специфичные для статьи свойства
// Автор: можно взять из TV или из настроек
$authorName = $modx->getOption('default_author', null, $siteName);
$pageData['author'] = [
'@type' => 'Person',
'name' => $authorName
];
// Дата публикации (если есть TV) или createdon
$createdon = $resource->get('createdon');
if ($createdon) {
$pageData['datePublished'] = date('Y-m-d', strtotime($createdon));
}
$editedon = $resource->get('editedon');
if ($editedon && $editedon != $createdon) {
$pageData['dateModified'] = date('Y-m-d', strtotime($editedon));
}
// Изображение для статьи (например, TV-параметр 'image')
$image = $modx->getOption('tv_image', null, ''); // если есть TV
if (empty($image)) {
// можно взять первую картинку из контента или просто использовать лого
$image = $logo;
}
if ($image) {
$pageData['image'] = $image;
}
break;
default:
$pageData['@type'] = 'WebPage';
break;
}
// Добавляем блок страницы в граф
$graph[] = $pageData;
// BreadcrumbList кроме главной
if ($showBreadcrumbs && $pageType != 'main') {
$parents = $modx->getParentIds($id, 10, ['context' => $context]);
$parents = array_reverse($parents);
$parents[] = $id;
$items = [];
$position = 1;
foreach ($parents as $resId) {
if ($resId == 0) continue;
$res = $modx->getObject('modResource', $resId);
if (!$res) continue;
$title = $res->get('menutitle') ?: $res->get('pagetitle');
$url = $modx->makeUrl($resId, $context, '', 'full');
$items[] = [
'@type' => 'ListItem',
'position' => $position,
'name' => $title,
'item' => $url
];
$position++;
}
if (!empty($items)) {
$graph[] = [
'@type' => 'BreadcrumbList',
'itemListElement' => $items
];
}
}
// Вывод
$output = [
'@context' => 'https://schema.org',
'@graph' => $graph
];
$json = json_encode($output, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
return '';
Что делает сниппет микроразметки
Он собирает граф сущностей в формате JSON-LD и выводит его в <script type="application/ld+json">. Внутри — Organization, WebSite, текущая WebPage (или её наследники), а также BreadcrumbList. Всё это связывается через @id, как того требует современный подход разметки данных.
Тип страницы определяется по ID шаблона (задайте свои старницы и их шаблоны). Если шаблон не совпал, тогда поставится базовый WebPage. Для статей дополнительно подтягиваются даты публикации/изменения, автор и изображение (также для автора и изображения задайте свои переменные их хранения).
Быстрая настройка
- Создайте новый сниппет (например,
schemaorg) и вставьте код. - Вызовите его в шаблоне перед
</head>: - Пропишите свои ID шаблонов в переменных
$mainTemplate,$contactsTemplateи т.д. - В
&type=передайти свой тип страницы (например,&type=AboutPage).
Параметры сниппета позволяют гибко управлять выводом:
| Параметр | Описание | По умолчанию |
|---|---|---|
| organization | Включает/выключает блок Organization | 1 вкл |
| breadcrumbs | Включает/выключает BreadcrumbList | 1 вкл |
| website | Включает/выключает блок WebSite | 1 вкл |
| type | Явно задаёт тип страницы (например AboutPage) | пусто |
Что получаем на выходе
Для обычной страницы (например, «Услуги») будет сгенерирован такой граф:
{
"@context": "https://schema.org",
"@graph": [
{
"@type": "Organization",
"@id": "https://site.ru/#organization",
"name": "Моя компания",
"url": "https://site.ru/",
"logo": "https://site.ru/images/logo.png"
},
{
"@type": "WebSite",
"@id": "https://site.ru/#website",
"name": "Моя компания",
"url": "https://site.ru/",
"publisher": {"@id": "https://site.ru/#organization"}
},
{
"@type": "WebPage",
"@id": "https://site.ru/services/#webpage",
"url": "https://site.ru/services/",
"name": "Услуги",
"isPartOf": {"@id": "https://site.ru/#website"},
"inLanguage": "ru",
"description": "..."
},
{
"@type": "BreadcrumbList",
"itemListElement": [...]
}
]
}
Для статей тип меняется на BlogPosting и добавляются datePublished, author и image. Хлебные крошки строятся автоматически по дереву ресурсов.
Почему микроразметка важна для SEO
- Расширенные сниппеты: хлебные крошки прямо в выдаче, логотип в поиске по бренду, информация об организации.
- Google рекомендует именно @graph для связывания сущностей.
Пара нюансов
- Сниппет берёт данные из системных настроек (site_url, site_name, logo, address, phone). Убедитесь, что они заполнены или используйте свои поля и укажите их в сниппете
- Если используете кэширование, вызывайте сниппет некэшируемым (
) данные зависят от страницы.
Пробуйте, делитесь результатами. Валидность можно проверить в инструментах Google и Яндекс Веб-мастер. Если появятся вопросы напишите в комментариях, разберём.