Удивительно, но несмотря на то, что как фрилансер я в основном работаю с сайтами на WordPress, в этом блоге до сегодняшнего дня не было ни одной статьи про эту CMS. Дело в том, что WP настолько популярен, изучен вдоль и поперек, а в сети можно найти сколько угодно статей с лайфхаками, которые позволяют сделать жизнь разработчика на этой CMS проще. Писать по сути не о чем. Но кое-что я все-таки наскреб и представляю на ваш суд. Хотя и в этой статье большая часть кода была позаимствована 😀.

Я не большой знаток SEO и всегда несколько наплевательски относился ко всяким мелочам, вроде «хлебных крошек». Если надо сделать, то сделаю, и это не является проблемой, но например на своем сайте у меня их нет (пока). Между тем «поисковики» очень ценят «крошки», которые они называют цепочками навигации (по крайней мере Яндекс) и могут даже выслать предупреждение о том, что ваши «цепочки» не такие, какие им нужны.

Какие-то не такие «хлебные крошки»

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

«Хлебные крошки» из плагина Yoast с микроразметкой на WordPress

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

Чтобы навигационная цепочка была читаема не только ботами, Яндекс предлагает использовать микроразметку типа BreadcrumbList в форматах Schema.org или JSON-LD.

На сайте моей заказчицы, во-первых, подключен один из самых популярных WordPress-плагинов в области SEO - Yoast, во-вторых, используется встроенная в плагин функция «хлебных крошек», в-третьих, на страницах, созданных с помощью редактора Elementor, крошки вставлены с помощью специального виджета.

Так вот, Yoast делает разметку всей страницы именно в формате JSON-LD, и «хлебные крошки» здесь не являются исключением. Однако, известно, что Яндексу этот формат не очень нравится, хотя он в этом и не признается. По крайней мере на сайте, о котором я говорю, такая разметка была, но отечественный поисковик все равно ничего не видел. Поэтому пришлось искать другое решение.

Крошки мои

На сайте WP-Kama сразу нашлось замечательное решение, которое видоизменяет html-код «хлебных крошек», созданных плагином Yoast. Спасибо ребятам, которые уже давно-давно помогают вордпрессерам упростить свою работу. Перед использованием нижеследующего кода, нужно не забыть, включить «хлебные крошки» в плагине.

functions.php
<?php

/**
 * Изменяет хлебные крошки Yoast.
 *
 * Вывести в шаблоне: do_action('pretty_breadcrumb');
 *
 */
class Pretty_Breadcrumb {

	/**
	 * Какую позицию занимает элемент в цепочке хлебных крошек.
	 *
	 * @var int
	 */
	private $el_position = 0;

	public function __construct() {
		add_action( 'pretty_breadcrumb', [ $this, 'render' ] );
	}

	/**
	 * Выводит на экран сгенерированные крошки.
	 *
	 * @return void
	 */
	public function render() {
		if ( ! function_exists( 'yoast_breadcrumb' ) ) {
			return;
		}

		// Регистрируем фильтры для изменения дефолтной вёрстки крошек
		add_filter( 'wpseo_breadcrumb_single_link', [ $this, 'modify_yoast_items' ], 10, 2 );
		add_filter( 'wpseo_breadcrumb_output', [ $this, 'modify_yoast_output' ] );
		add_filter( 'wpseo_breadcrumb_output_wrapper', [ $this, 'modify_yoast_wrapper' ] );
		add_filter( 'wpseo_breadcrumb_separator', '__return_empty_string' );

		// Выводим крошки на экран
		yoast_breadcrumb();

		// Отключаем фильтры
		remove_filter( 'wpseo_breadcrumb_single_link', [ $this, 'modify_yoast_items' ] );
		remove_filter( 'wpseo_breadcrumb_output', [ $this, 'modify_yoast_output' ] );
		remove_filter( 'wpseo_breadcrumb_output_wrapper', [ $this, 'modify_yoast_wrapper' ] );
		remove_filter( 'wpseo_breadcrumb_separator', '__return_empty_string' );

		// Обнуляем счётчик
		$this->el_position = 0;
	}

	/**
	 * Изменяет html код li элементов.
	 *
	 * @param string $link_html Дефолтная вёрстка элемента хлебных крошек.
	 * @param array  $link_data Массив данных об элементе хлебных крошек.
	 *
	 * @return string
	 */
	function modify_yoast_items( $link_html, $link_data ) {
		// Шаблон контейнера li
		$li = '<li itemprop="itemListElement" itemscope="itemscope" itemtype="https://schema.org/ListItem" %s>%s</li>';

		// Содержимое li в зависимости от позиции элемента
		if ( strpos( $link_html, 'breadcrumb_last' ) === false ) {
			$li_inner = sprintf( '
                <a itemprop="item" href="%s" class="pathway">
                    <span itemprop="name">%s</span>
                </a>
            ', $link_data['url'], $link_data['text'] );
			$li_inner .= '<span class="divider"> / </span>';
			$li_class = '';
		} else {
			$li_inner = sprintf( '<span itemprop="name">%s</span>', $link_data['text'] );
			$li_class = 'class="active"';
		}

		$li_inner .= sprintf( '<meta itemprop="position" content="%d"/>', ++ $this->el_position );

		// Вкладываем сформированное содержание в li и возвращаем полученный элемент хлебных крошек.
		return sprintf( $li, $li_class, $li_inner );
	}

	/**
	 * Возвращает псевдо wrapper, который в будущем будет вырезан из вёрстки.
	 * Если этого не сделать, то будущие li будут обёртнуты в единый span Yoast'ом.
	 *
	 * @return string
	 */
	function modify_yoast_wrapper() {
		return 'wrapper'; // Будущий "уникальный" тег для вырезки из html
	}

	/**
	 * Изменяет дефолтный html код крошек Yoast.
	 *
	 * @param string $html
	 *
	 * @return string
	 */
	function modify_yoast_output( $html ) {
		// Убираем псевдо wrapper
		$html = str_replace( [ '<wrapper>', '</wrapper>' ], '', $html );

		// Формируем контейнер для li элементов
		$ul = '<ul itemscope="itemscope" itemtype="https://schema.org/BreadcrumbList" class="breadcrumb">%s</ul>';

		// Вставляем в контейнер li элменты
		$html = sprintf( $ul, $html );

		return $html;
	}
}

new Pretty_Breadcrumb();

Данный код добавляет в «хлебные крошки» микроразметку в формате schema.org, а выводятся они на странице с помощью <?php do_action('pretty_breadcrumb'); ?>. В моем же случае, я не мог использовать php-вставку в редакторе Elementor (хотя слышал, что есть плагин, который позволяет это сделать). Поэтому мне пришлось переделать данный код, превратив его в шорткод. Как и предыдущий код, этот я вставлял в файл functions.php, хотя можно использовать для этого специальные плагины, или специальные файлы.

functions.php
<?php

/**
 * Шорткод для кастомных хлебных крошек Yoast.
 */
add_shortcode( 'pretty_breadcrumb', 'pretty_breadcrumb_shortcode' );

function pretty_breadcrumb_shortcode() {
  // Создаем объект Pretty_Breadcrumb
  $breadcrumb = new Pretty_Breadcrumb();

  // Вызываем метод render для вывода хлебных крошек
  $breadcrumb->render();

  // Возвращаем пустую строку, чтобы шорткод не выводил дополнительного текста
  return '';
}

/**
 * Класс для кастомизации хлебных крошек Yoast.
 */
class Pretty_Breadcrumb {

  /**
   * Позиция элемента в цепочке хлебных крошек.
   *
   * @var int
   */
  private $el_position = 0;

  public function __construct() {
    // Ничего не делаем в конструкторе, все действия происходят в методе render
  }

  /**
   * Выводит на экран сгенерированные крошки.
   *
   * @return void
   */
  public function render() {
    if ( ! function_exists( 'yoast_breadcrumb' ) ) {
      return;
    }

    // Регистрируем фильтры для изменения дефолтной вёрстки крошек
    add_filter( 'wpseo_breadcrumb_single_link', [ $this, 'modify_yoast_items' ], 10, 2 );
    add_filter( 'wpseo_breadcrumb_output', [ $this, 'modify_yoast_output' ] );
    add_filter( 'wpseo_breadcrumb_output_wrapper', [ $this, 'modify_yoast_wrapper' ] );
    add_filter( 'wpseo_breadcrumb_separator', '__return_empty_string' );

    // Выводим крошки на экран
    yoast_breadcrumb();

    // Отключаем фильтры
    remove_filter( 'wpseo_breadcrumb_single_link', [ $this, 'modify_yoast_items' ] );
    remove_filter( 'wpseo_breadcrumb_output', [ $this, 'modify_yoast_output' ] );
    remove_filter( 'wpseo_breadcrumb_output_wrapper', [ $this, 'modify_yoast_wrapper' ] );
    remove_filter( 'wpseo_breadcrumb_separator', '__return_empty_string' );

    // Обнуляем счётчик
    $this->el_position = 0;
  }

  /**
   * Изменяет html код li элементов.
   *
   * @param string $link_html Дефолтная вёрстка элемента хлебных крошек.
   * @param array  $link_data Массив данных об элементе хлебных крошек.
   *
   * @return string
   */
  function modify_yoast_items( $link_html, $link_data ) {
    // Шаблон контейнера li
    $li = '<li itemprop="itemListElement" itemscope="itemscope" itemtype="https://schema.org/ListItem" %s>%s</li>';

    // Содержимое li в зависимости от позиции элемента
    if ( strpos( $link_html, 'breadcrumb_last' ) === false ) {
      $li_inner = sprintf( '
                <a itemprop="item" href="%s" class="pathway">
                    <span itemprop="name">%s</span>
                </a>
            ', $link_data['url'], $link_data['text'] );
      $li_inner .= '<span class="divider"> / </span>';
      $li_class = '';
    } else {
      $li_inner = sprintf( '<span itemprop="name">%s</span>', $link_data['text'] );
      $li_class = 'class="active"';
    }

    $li_inner .= sprintf( '<meta itemprop="position" content="%d"/>', ++ $this->el_position );

    // Вкладываем сформированное содержание в li и возвращаем полученный элемент хлебных крошек.
    return sprintf( $li, $li_class, $li_inner );
  }

  /**
   * Возвращает псевдо wrapper, который в будущем будет вырезан из вёрстки.
   * Если этого не сделать, то будущие li будут обёртнуты в единый span Yoast'ом.
   *
   * @return string
   */
  function modify_yoast_wrapper() {
    return 'wrapper'; // Будущий "уникальный" тег для вырезки из html
  }

  /**
   * Изменяет дефолтный html код крошек Yoast.
   *
   * @param string $html
   *
   * @return string
   */
  function modify_yoast_output( $html ) {
    // Убираем псевдо wrapper
    $html = str_replace( [ '<wrapper>', '</wrapper>' ], '', $html );

    // Формируем контейнер для li элементов
    $ul = '<ul itemscope="itemscope" itemtype="https://schema.org/BreadcrumbList" class="breadcrumb">%s</ul>';

    // Вставляем в контейнер li элменты
    $html = sprintf( $ul, $html );

    return $html;
  }
}

Теперь в любом месте сайта, в том числе визуальном редакторе, можно вставить шорткод [pretty_breadcrumb], а в файле шаблона <?php echo do_shortcode( '[pretty_breadcrumb]' ); ?>, чтобы там вывелись «хлебные крошки» с микроразметкой. Теперь их код изменился, добавились разные атрибуты, вроде itemscope, itemprop, content и всякое такое. Это позволит поисковикам посмотреть на ваши цепочки навигации по другому.

Страница сайта
<ul itemscope="itemscope" itemtype="https://schema.org/BreadcrumbList" class="breadcrumb">
    <li itemprop="itemListElement" itemscope="itemscope" itemtype="https://schema.org/ListItem">
        <a itemprop="item" href="/" class="pathway">
            <span itemprop="name">Главная</span>
        </a>
        <span class="divider"> / </span>
        <meta itemprop="position" content="1"/>
    </li>
    <li itemprop="itemListElement" itemscope="itemscope" itemtype="https://schema.org/ListItem">
        <a itemprop="item" href="/blog/itemlist" class="pathway">
            <span itemprop="name">Категория</span>
        </a>
        <span class="divider"> / </span>
        <meta itemprop="position" content="2"/>
    </li>
    <li itemprop="itemListElement" itemscope="itemscope" itemtype="https://schema.org/ListItem" class="active">
        <span itemprop="name">Статья</span>
        <meta itemprop="position" content="3"/>
    </li>
</ul>

Также я доработал внешний вид «крошек», чтобы они попали под стиль сайта.

style.css
ul.breadcrumb {
    list-style: none;
    display: flex;
    padding: 0;
    margin: 0;
}

ul.breadcrumb a, ul.breadcrumb span {
	transition: .35s;
    color: #606267;
    font-size: 14.45px;
    font-family: "Manrope", Sans-serif;
    font-weight: 200;
}

ul.breadcrumb a:hover > span {
	color: #FF7F09;
}

.breadcrumb .divider {
    margin-right: 4px;
}

Спустя какое-то время Яндекс съел правки и начал выводить цепочки навигации в нужном виде. Можете использовать код из этой статьи на своих сайтах, он рабочий и не вызывает ошибок. Стилизация, конечно, на ваше усмотрение.

На самом деле, задача не самая сложная, особенно если учесть, что 99% работы, уже было сделано. Я только слепил из готового кода шорткод, что может помочь тем из вас, кто не работает с чистым WordPress без редакторов и пэйдж-билдеров. Но учтите, этот материал может не подойти новичкам, которые не понимают что такое шорткод, куда вставлять код и так далее. За десятки лет существования WordPress об этом было написано, сказано и показано миллионы раз. Ищите подробную информацию в сети.

Это еще не все

Итак, Яндексу понравились наши новые «хлебные крошки» с микроразметкой. Я удалил на страницах «элементорский» виджет Breadcrumbs, поставил вместо него виджет Шорткод со своим свежесозданным шорткодом и дело было сделано.

Но осталась одна нерешенная проблема. Как я писал выше, плагин Yoast размечает «хлебные крошки» в другом формате, который плохо видит Яндекс, но хорошо видит Google. Последний, по свидетельствам очевидцев, может даже ругать Ваш сайт за то, что ваши навигационные цепочки получили микроразметку дважды, после того как вы добавите код из этой статьи.

Выход в том, чтобы удалить одну из микроразметок со страницы. Поскольку мы в России, как правило, больше подстраиваемся под Яндекс, будем удалять разметку JSON-LD.

Покопавшись в интернете и опробовав пару методов, пришел к такому решению:

functions.php
// functions.php
add_filter( 'wpseo_schema_graph_pieces', 'remove_breadcrumbs_from_schema', 11, 2 );
add_filter( 'wpseo_schema_webpage', 'remove_breadcrumbs_property_from_webpage', 11, 1 );

/**
 * Removes the breadcrumb graph pieces from the schema collector.
 *
 * @param array  $pieces  The current graph pieces.
 * @param string $context The current context.
 *
 * @return array The remaining graph pieces.
 */
function remove_breadcrumbs_from_schema( $pieces, $context ) {
    return \array_filter( $pieces, function( $piece ) {
        return ! $piece instanceof \Yoast\WP\SEO\Generators\Schema\Breadcrumb;
    } );
}

/**
 * Removes the breadcrumb property from the WebPage piece.
 *
 * @param array $data The WebPage's properties.
 *
 * @return array The modified WebPage properties.
 */
function remove_breadcrumbs_property_from_webpage( $data ) {
    if (array_key_exists('breadcrumb', $data)) {
        unset($data['breadcrumb']);
    }
    return $data;
}

Этот код пойдет в тот же файл functions.php или в отдельный спецплагин. Он был найден в документации Yoast. После этого, разметка цепочек в формате JSON-LD от плагина пропадет, а наша разметка останется. И волки сыты, и овцы целы. Проверить что мы там сделали можно в валидаторах микроразметок тут или тут.


После этой работы я решил сделать хлебные крошки и на своем сайте Sveltekit. Правда здесь обошлось без плагинов, а по старинке - руками. Такую же микроразметку тоже сделал. Теперь буду ждать, когда мой сайт начнет отображаться в Яндексе с красивыми навигационными цепочками. А пока на этом все. Спасибо, если дочитали статью до конца. До новых встреч.