Command Palette

Search for a command to run...

Как добавить Hreflang в WordPress (без плагина)

Как добавить Hreflang в WordPress (без плагина)

T
Toolz Team
|Aug 23, 2026|13 Мин. читать

Часть коллекции SEO инструменты

Большинство рекомендаций WordPress hreflang - это & quot;установите многоязычный плагин, и он будет обрабатывать его. & quot; Это нормально, когда плагин владеет вашими переводами. Это неправильный ответ в случае, когда я нажимаю чаще всего: сайт с двумя или тремя языковыми версиями, построенными как отдельные подкаталоги, отдельные установки или набор вручную переведенных страниц, где никто не хочет полную структуру перевода и схему его базы данных для четырех страниц.

Я поддерживаю свойства WordPress в такой форме, включая сайты документации, где переводы написаны от руки и под которыми они живут /de/ а /es/. Рефланг есть сорок строк в файле темы, он читает из одного массива, и он никогда не дрейфовал Это руководство является тем кодом, плюс части, которые идут не так, когда плагин излучает рефланг рядом с вашей темой.

Он сидит под полный путеводитель по грефлангу, рядом с Версия Next.js, йо-

TL;DR: WordPress не излучает рефланг самостоятельно. Крючок wp_head, перейдите через карту локалей и распечатайте полный обратный набор, включая x-default на каждой странице в группе. Build URL с home_url() или происхождение для каждого локаля, а не жесткое кодирование, сохраните каждую страницу и каноническую самореферентность #39;s и проверьте, что ни один плагин SEO уже не излучает конкурирующий блок. Для многосайтовых настроек, get_sites() дает карту; для одного сайта с переведенными страницами достаточно аналогов для сопоставления метаполей публикации.

Добавляет ли WordPress рефланг сам по себе?

№ Core выводит каноническую ссылку, короткую ссылку и различные каналы связи и REST wp_head, но в языке ничего не меняется. get_locale() знает, на каком языке находится сайт, и ни одна часть ядра не превращает его в аннотацию.

Плагины заполняют пробел. Polylang и WPML излучают hreflang для переводов, которыми они управляют, и большинство плагинов SEO будут излучать его, если вы настраиваете отношения перевода, о которых они могут видеть. если вы уже используете один из них, и он охватывает весь ваш сайт, используйте его и пропустите эту статью. Причина ручной прокрутки заключается в том, что ваши переводы существуют вне того, о чем знает плагин, а плагин, который излучает частичный набор, хуже того, который не излучает ни одного.

Минимальная версия: один массив, один крючок

Для сайта, переводы которого являются известным, небольшой набор префиксов URL:

<?php
/**
 * Hreflang annotations for a hand-translated multilingual site.
 * Drop in the theme's functions.php or a small mu-plugin.
 */
function mysite_hreflang_map(): array {
    return [
        'en-US'     => 'https://example.com',
        'de-DE'     => 'https://example.com/de',
        'es-ES'     => 'https://example.com/es',
        'x-default' => 'https://example.com',
    ];
}

function mysite_print_hreflang(): void {
    $path = mysite_current_path(); // e.g. '/pricing/'
    foreach ( mysite_hreflang_map() as $hreflang => $origin ) {
        printf(
            '<link rel="alternate" hreflang="%s" href="%s" />' . "\n",
            esc_attr( $hreflang ),
            esc_url( untrailingslashit( $origin ) . $path )
        );
    }
}
add_action( 'wp_head', 'mysite_print_hreflang', 1 );

Три вещи, которые можно сделать правильно, обычно это не делают рукописные блоки.

Он печатает полный комплект на каждой странице, включая страницу&#39;s собственный языкВзаимность удовлетворяется структурно: нет ветки, которая могла бы опустить запись, потому что ветки нет вообще.

x-default является строкой в одном массиве, поэтому он излучается одним и тем же циклом и не может быть забыт на одном шаблоне.

URL-адреса экранированы. esc_url на href в wp_head не является необязательным, и значения hreflang проходят esc_attr по той же причине.

Выработка текущего пути

Путь - это та часть, которая нуждается в уходе, потому что он должен быть путем в пределах язык, а не полный URI запроса.

function mysite_current_path(): string {
    $home = wp_parse_url( home_url(), PHP_URL_PATH ) ?: '';
    $req  = wp_parse_url( $_SERVER['REQUEST_URI'] ?? '/', PHP_URL_PATH ) ?: '/';

    // Strip the language prefix this install is served under.
    if ( $home && str_starts_with( $req, $home ) ) {
        $req = substr( $req, strlen( $home ) );
    }
    return user_trailingslashit( '/' . ltrim( $req, '/' ) );
}

Если ваша немецкая установка живет по адресу example.com/de и посетитель включен example.com/de/preise/, это возвращается /preise/, который затем получает префикс с каждой локалью&#39;s origin.The trailing slash readmanding имеет большее значение, чем выглядит: /preise а /preise/ это разные URL-адреса, и указание аннотации на неканоническую форму - это несоответствие задней косой черты это приводит к ошибкам обратной метки в наборе, который в противном случае выглядит идеально. user_trailingslashit уважает настройку permalink сайта и #39;s, поэтому она соответствует всему, что обслуживает WordPress.

Здесь предполагается, что переведенные URL-адреса структурно параллельны: /pricing/ карты на /de/pricing/. Если ваш немецкий slugs тоже переведен, /de/preise/, этой функции недостаточно, и вам нужно явное отображение, которое является следующим разделом.

Когда slugs отличаются: постмета-сопоставление

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

/**
 * Meta key holds a JSON map: {"de-DE": 123, "es-ES": 456} of post IDs,
 * or absolute URLs for external installs.
 */
function mysite_alternates_for( int $post_id ): array {
    $raw = get_post_meta( $post_id, '_hreflang_alternates', true );
    $map = $raw ? json_decode( $raw, true ) : [];
    if ( ! is_array( $map ) ) {
        return [];
    }

    $out = [];
    foreach ( $map as $hreflang => $target ) {
        $url = is_numeric( $target ) ? get_permalink( (int) $target ) : $target;
        if ( $url ) {
            $out[ $hreflang ] = $url;
        }
    }
    return $out;
}

Затем wp_head обратный вызов объединяет текущую запись и #39;s собственную запись на этой карте перед печатью, поэтому набор является полным и самореферентным:

function mysite_print_hreflang_mapped(): void {
    if ( ! is_singular() ) {
        return;
    }
    $post_id = get_queried_object_id();
    $alts    = mysite_alternates_for( $post_id );
    if ( count( $alts ) < 1 ) {
        return; // Nothing to declare: a one-entry cluster is not a cluster.
    }

    $alts[ mysite_hreflang_for_locale( get_locale() ) ] = get_permalink( $post_id );
    $alts['x-default'] = $alts['en-US'] ?? get_permalink( $post_id );

    foreach ( $alts as $hreflang => $url ) {
        printf(
            '<link rel="alternate" hreflang="%s" href="%s" />' . "\n",
            esc_attr( $hreflang ),
            esc_url( $url )
        );
    }
}

Стоимость обслуживания заключается в том, что мета должен быть написан с обеих сторон. пользовательский интерфейс администратора с выборщиком аналогов - это честное решение; команда WP-CLI, которая заполняет карту из CSV, является быстрой. Чего вы не должны делать, так это заполнять ее только с одной стороны, потому что одностороннее объявление - это именно то отсутствующий тег возврата провал.

Мультисайт: получите карту из сети

Если каждый язык является сайтом в многосайтовой сети, карта уже существует:

function mysite_network_hreflang_map(): array {
    $map = [];
    foreach ( get_sites( [ 'number' => 50 ] ) as $site ) {
        switch_to_blog( (int) $site->blog_id );
        $map[ mysite_hreflang_for_locale( get_locale() ) ] = home_url();
        restore_current_blog();
    }
    return $map;
}

Кэшировать результат в переходный период; switch_to_blog в а wp_head крюк на каждый запрос - это реальная стоимость на сети любого размера. И будьте осторожны с get_locale() под switch_to_blog: он отражает коммутируемый сайт только в том случае, если сайт&#39;s WPLANG установлен параметр, что является обычным случаем, но не гарантируется в сетях, настроенных через константу.

Какой подход подходит вашему сайту?

снабжать подход Усилия
Параллельно slugs, несколько локалей Статический массив плюс производный путь Самый низкий; одна функция
Переведено slugs Карта-аналог постмета Умеренный; нужен рабочий процесс редактирования
Мультисайт, по одному сайту на язык Получить из get_sites(), кэшировано Низкий код, нужна дисциплина кэша
Polylang или WPML, управляющие переводами Используйте вывод плагина&#39;s Проверьте, что он излучает x-по умолчанию
Плагин SEO с полями ручного перевода Используйте его, затем проверьте визуализированную головку Следите за дублирующими блоками

Вместо этого поместите на карту сайта рефланг

Если заголовки ваших страниц уже переполнены, или у вас достаточно локалей, что блок длиннее остальной части головы, альтернативой является карта сайта. WordPress 5.5 и более поздняя поставка основные карты сайта, и они поддаются фильтрации, хотя фильтры не были разработаны с учетом альтернатив и объявления пространства имен <urlset> это не то, что ядро позволяет вам чисто прикасаться.

Прагматичный маршрут - это отдельная карта сайта, подаваемая на основе правила переписывания, а не боя wp_sitemaps,

add_action( 'init', function () {
    add_rewrite_rule( '^sitemap-i18n\.xml$', 'index.php?mysite_i18n_sitemap=1', 'top' );
} );

add_filter( 'query_vars', fn( $vars ) => array_merge( $vars, [ 'mysite_i18n_sitemap' ] ) );

add_action( 'template_redirect', function () {
    if ( ! get_query_var( 'mysite_i18n_sitemap' ) ) {
        return;
    }
    header( 'Content-Type: application/xml; charset=UTF-8' );

    echo '<?xml version="1.0" encoding="UTF-8"?>' . "\n";
    echo '<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" ';
    echo 'xmlns:xhtml="http://www.w3.org/1999/xhtml">' . "\n";

    foreach ( mysite_translated_paths() as $path ) {
        echo "  <url>\n    <loc>" . esc_url( home_url( $path ) ) . "</loc>\n";
        foreach ( mysite_hreflang_map() as $hreflang => $origin ) {
            printf(
                '    <xhtml:link rel="alternate" hreflang="%s" href="%s"/>' . "\n",
                esc_attr( $hreflang ),
                esc_url( untrailingslashit( $origin ) . $path )
            );
        }
        echo "  </url>\n";
    }
    echo '</urlset>';
    exit;
} );

Две вещи, которые нужно сделать правильно. The xmlns:xhtml декларация о <urlset> требуется; без него альтернативные записи игнорируются, и файл по-прежнему проверяется как карта сайта, которая снова является шаблоном бесшумного отказа. И правила перезаписи Flush один раз после добавления правила, при активации, а не при каждом запросе.

Сделав это, удалите головной вывод. Исключить оба - это методы реализации смешивания, что дает вам два дрейфующих источника.

Проблема дублирующего блока

Это WordPress-специфичный сбой и тот, который стоит проверить, прежде чем писать какой-либо код. несколько плагинов излучают hreflang, и более одного может быть активным одновременно. плагин SEO, настроенный с отношениями перевода, многоязычный плагин, и ваша новая функция темы все будет счастливо печатать, чтобы wp_head, и в результате получается два или три блока с разным содержанием и, возможно, два x-default записи.

Обработка противоречивых аннотаций в Google&#39;s на одной странице призвана им не доверять. Таким образом, сайт, у которого не было рефланга и теперь есть три блока, может оказаться в худшем положении, чем раньше.

Проверьте отображаемый вывод, а не настройки плагина:

curl -s https://example.com/de/preise/ | grep 'rel="alternate"' | grep hreflang

Подсчитайте строки и посчитайте отдельные коды. Одна запись на локаль плюс одна x-default, и ничего не повторяется. Если плагин излучает набор, который вы не можете настроить, обычный аварийный люк - удалить его действие wp_head вместо того, чтобы добавлять свой сверху:

// Example shape: find the real hook and callback with `has_action`.
add_action( 'init', function () {
    remove_action( 'wp_head', 'some_plugin_hreflang_output', 10 );
} );

Канонические теги, а не ломающие их

WordPress излучает rel="canonical" для отдельных видов через rel_canonical(), и он указывает на post&#39;s собственный permalink, что вы хотите Большинство SEO плагинов заменяют его на свой собственный, также самореферентный по умолчанию.

Правило из грефланг против канонического применяется без изменений: каждая страница канонизируется к себе, и hreflang описывает отношения между этими самоканоническими страницами. способ взлома со вкусом WordPress - это настройка плагина, которая канонизирует переведенные сообщения в & quot;primary& quot; языковая версия, которая читается как разумный вариант дедупликации и незаметно удаляет ваши переводы из индекса. Если настройка предлагает канонизировать разные языки, оставьте это в стороне.

Проверка результата

Получите две родственные страницы и сравните их блоки аннотаций Они должны быть идентичными, с только каноническим различающимся Затем проверьте, что каждый URL-адрес, который вы назвали, возвращает 200 без перенаправления, потому что удаленные локали и измененные permalinks - это то, как правильный набор гниет в сломанный.

Вставьте набор в бесплатный генератор грефланга для проверки кодов: каждое значение проверяется на соответствие форме ISO, флагируются нераспознанные подтеги, например en-UK, ловит дубликаты, отклоняет относительные URL-адреса и предупреждает, когда набор не имеет запасных частей. Он полностью работает в браузере, что имеет значение, когда URL-адреса описывают клиентский сайт, который еще не является общедоступным. Затем используйте Search Console&#39;s International Targeting отчет для текущей проверки, и руководство инструмента Hreflang для рабочего процесса вокруг него.

Часто задаваемые вопросы

Добавляет ли WordPress теги hreflang автоматически?

Нет. Core излучает каноническую ссылку и различные ссылки на заголовки но ничего о языке чередуется.Hreflang приходит от плагина или от вашего собственного wp_head выход.

Как добавить hreflang в WordPress без плагина?

Крюк wp_head и напечатать а <link rel="alternate" hreflang> строка для каждого местоположения на карте, которую вы контролируете, включая x-default запись, на каждой странице в группе. Escape the values с esc_attr а esc_url, йо-

Куда идут теги hreflang в теме WordPress?

В функции, к которой подключено wp_headв идеале в небольшом мю-плагине, а не в теме, поэтому переключение тем не приводит к молчанию ваших аннотаций.

Что, если мои переведенные страницы будут разными slugs?

Ничего не выводить. храните идентификаторы или URL-адреса сообщений-партнеров в мета-посте и читайте карту во время рендеринга. Заполните его с обеих сторон или создайте отсутствующий тег возврата.

Обращаются ли Polylang и WPML с hreflang?

Да, для переводов, которыми они управляют. проверьте вывод включает в себя x-default и что ни один второй плагин не излучает конкурирующий блок.

Почему у меня на одной странице два блока-рефланга?

Две вещи печатаются wp_head: обычно плагин SEO плюс многоязычный плагин, или плагин плюс ваша собственная функция Противоречивые аннотации на одной странице не доверяют, поэтому удалите один, а не многоуровневый.

Должна ли каждая языковая версия WordPress иметь свой канонический характер?

Да, самореферентный Не используйте настройку плагина, которая канонизирует переведенные сообщения на основной язык, потому что это удаляет их из индекса и отменяет набор hreflang.

Как добавить hreflang в многосайтовую сеть WordPress?

Петля get_sites()переключитесь на каждый блог, чтобы прочитать его местоположение и home_url(), и кэшировать полученную карту в переходном периоде. Бег switch_to_blog по каждому запросу в wp_head дорого в большой сети.


Comments

0 comments

0/2000 characters

No comments yet. Be the first to share your thoughts!