Обсудить задачу

Hreflang для мультиязычного сайта: Astro и Next.js

Hreflang для мультиязычного сайта - это атрибут rel=“alternate” в , который сообщает поисковику, что несколько URL - это переводы одной и той же страницы, и подсказывает, какую версию показать пользователю в его регионе. В Astro его либо генерирует @astrojs/sitemap через i18n-конфигурацию, либо добавляют вручную модулем astro:i18n в head страницы; в Next.js - через свойство alternates.languages в Metadata API. Без правильной настройки поисковик либо показывает не ту языковую версию, либо трактует переводы как дублирующийся контент.

Ошибка в hreflang - не редкость, а норма. Ahrefs проанализировал 374 756 доменов, использующих hreflang, и обнаружил, что у 67% есть минимум одна ошибка реализации: у 56,3% отсутствует x-default, у 18% нет self-referencing тега, у 16,9% hreflang указывает на битую или редиректящую страницу (Ahrefs, hreflang study).

Для команды, которая строит сайты на Astro и Next.js для клиентов, выходящих за пределы СНГ, это не абстрактная статистика - почти на каждом втором мультиязычном сайте, который мы открываем на аудит, находится хотя бы одна из этих проблем. В этой статье - конкретная реализация hreflang для Astro и для Next.js рядом, плюс чек-лист, который закрывает то, что документация фреймворков по отдельности не объединяет: canonical, hreflang, x-default, локализованные sitemap, атрибут lang и переведённые meta-теги.

Для русскоязычной команды, которая строит сайт для рынка США или Европы, мультиязычность - это не бонус поверх готового сайта, а часть архитектуры с первого дня: RU-версия для внутренней команды и инвесторов, EN или локальный язык - для рынка, на который идёт трафик и реклама. Если hreflang настроен неверно, Google может показывать в выдаче США русскоязычную версию вместо английской или, наоборот, трактовать обе версии как дубли и понижать в ранжировании обе сразу.

Hreflang: атрибут rel=“alternate” hreflang=”…” в теге внутри head или в sitemap, который указывает поисковику, что несколько URL - языковые или региональные версии одной и той же страницы.

Зачем hreflang мультиязычному сайту и что теряется без него

Без hreflang поисковик сам решает, какую языковую версию показать - по IP, языку браузера или силе ссылочного веса, а не по факту наличия перевода. Это создаёт три конкретных проблемы.

Во-первых, каннибализация: если RU и EN версии страницы не связаны hreflang, Google может расценить их как дублирующийся контент на разных языках и понизить обе в выдаче вместо того чтобы ранжировать каждую в своём регионе.

Во-вторых, неверная версия в выдаче: пользователь из Германии ищет продукт на английском, но видит в результатах русскоязычную страницу - просто потому что она сильнее по ссылочному профилю. Кликрейт падает, за ним падает конверсия.

В-третьих, потеря сигналов между версиями: без hreflang каждая языковая версия растёт как отдельный сайт - объединить ссылки, авторитет и историю кликов в общую картину домена не получается.

Для сайта, который целится в рынок США или Европы, эта потеря особенно чувствительна: рекламный бюджет и SEO-усилия идут на англоязычную версию, а без правильного hreflang часть органического трафика просто не долетает до нужной страницы.

Архитектура URL до кода: поддомен, папка или домен

Выбор структуры URL определяет, как будет выглядеть каждый hreflang-тег, поэтому его стоит закрыть до того, как писать i18n-конфигурацию.

Три рабочих варианта: example.com/en/, en.example.com и отдельный домен вроде example.de для конкретной страны. Для большинства мультиязычных B2B-сайтов на Astro или Next.js оптимальна папка (/en/, /de/) - она наследует авторитет основного домена и проще в настройке DNS и сертификатов, чем поддомен или отдельный домен.

Если сайт мигрирует со старого стека на Astro или Next.js, структуру URL логичнее спроектировать сразу под мультиязычность, а не переносить её позже - иначе редиректы и hreflang придётся настраивать дважды. Мы разбирали это подробнее в миграции с WordPress на Next.js и в переходе с Webflow на Astro - в обоих случаях языковая структура закладывается на этапе выбора роутинга, а не после запуска.

Hreflang для мультиязычного сайта в Astro

В Astro есть два независимых механизма hreflang: автоматический - через @astrojs/sitemap, и ручной - через модуль astro:i18n в head каждой страницы. Для полноценной реализации нужны оба.

Sitemap с hreflang настраивается в i18n-блоке конфигурации интеграции:

Конфигурация @astrojs/sitemap с hreflang
import { defineConfig } from 'astro/config';
import sitemap from '@astrojs/sitemap';

export default defineConfig({
  site: 'https://example.com',
  integrations: [
    sitemap({
      i18n: {
        defaultLocale: 'ru',
        locales: {
          ru: 'ru-RU',
          en: 'en-US',
          de: 'de-DE',
        },
      },
    }),
  ],
});

После сборки каждая запись в sitemap.xml получает xhtml:link rel=“alternate” для всех языковых версий страницы автоматически - вручную прописывать альтернативы для sitemap не нужно.

Для тегов link rel=“alternate” hreflang в head страницы используют функцию getAbsoluteLocaleUrl из модуля astro:i18n - она формирует абсолютный URL с учётом конфигурации локалей:

Hreflang-теги в layout Astro
---
import { getAbsoluteLocaleUrl } from 'astro:i18n';

const locales = ['ru', 'en', 'de'];
const path = Astro.url.pathname.replace(`/${Astro.currentLocale}`, '');
---
{locales.map((locale) => (
  <link
    rel="alternate"
    hreflang={locale}
    href={getAbsoluteLocaleUrl(locale, path)}
  />
))}
<link
  rel="alternate"
  hreflang="x-default"
  href={getAbsoluteLocaleUrl('ru', path)}
/>

Требование Google - абсолютный URL с протоколом (https://example.com/en/…), а не относительный путь; getAbsoluteLocaleUrl формирует его сразу в нужном формате, поэтому вручную склеивать домен и путь не нужно.

Hreflang в Next.js: Metadata API и alternates.languages

В Next.js hreflang задаётся не отдельными тегами, а через объект alternates.languages в Metadata API - Next.js сам рендерит из него link rel=“alternate” hreflang в head.

Hreflang через Metadata API в Next.js
import type { Metadata } from 'next';

export const metadata: Metadata = {
  metadataBase: new URL('https://example.com'),
  alternates: {
    canonical: '/en',
    languages: {
      'ru-RU': '/ru',
      'en-US': '/en',
      'de-DE': '/de',
      'x-default': '/en',
    },
  },
};

С заданным metadataBase относительные пути в languages автоматически превращаются в абсолютные URL при сборке - тот же принцип, что и в getAbsoluteLocaleUrl в Astro, только без ручного вызова функции.

Для динамических маршрутов (страница товара, статья блога) то же поле задают внутри generateMetadata, подставляя параметр локали в путь для каждого языка - код идентичен статическому варианту, только значения languages собираются в цикле по списку поддерживаемых локалей вместо того чтобы быть захардкожены.

X-default и взаимность ссылок

Тег x-default указывает, какую версию показать пользователю, чей язык не совпадает ни с одной из перечисленных - обычно это англоязычная версия или страница выбора языка. Без него, по данным Ahrefs, 56,3% доменов из выборки не могут определить версию по умолчанию - это самая частая ошибка hreflang в исследовании.

Второе обязательное условие - взаимность: если страница /ru/ ссылается на /en/ через hreflang, страница /en/ обязана ссылаться обратно на /ru/. Одностороннюю ссылку hreflang Google игнорирует целиком, а не только в одном направлении.

Оба фреймворка снимают эту работу с рук разработчика на уровне конфигурации: у Astro список локалей задан один раз в i18n.locales, у Next.js - в languages, поэтому при добавлении нового языка достаточно добавить одну запись, а не редактировать каждую языковую версию страницы вручную.

Чек-лист перед запуском мультиязычного сайта

Шесть пунктов, которые нужно закрыть до запуска - каждый по отдельности покрыт документацией фреймворков, но вместе их редко собирают в одном месте.

  • Canonical на каждой языковой версии указывает сама на себя, а не на версию по умолчанию
  • Hreflang для каждой пары языков взаимный - A ссылается на B, B ссылается на A
  • X-default присутствует и указывает на разумную версию по умолчанию (обычно EN)
  • Локализованные sitemap - один sitemap с hreflang-аннотациями или отдельные sitemap на язык, но не смешение форматов
  • Атрибут lang в html lang=”…” соответствует реальному языку страницы, а не захардкожен на ru для всех локалей
  • Переведённые meta-теги - title, description и og:title переведены, а не продублированы из языка по умолчанию

Последний пункт чаще всего проваливается не из-за кода, а из-за процесса контента: команда переводит текст страницы, но забывает про meta-поля в CMS. Если контент и переводы хранятся в Headless CMS, поля под локализованный SEO-блок стоит закладывать сразу - разбирали выбор между Sanity, Contentful и Strapi для мультиязычных B2B-сайтов в сравнении Headless CMS.

Типичные ошибки hreflang и как их найти

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

Отсутствие self-referencing тега (18% доменов в выборке) - страница /en/ должна включать hreflang-ссылку саму на себя, а не только на остальные языки.

Битые или редиректящие ссылки в hreflang (16,9%) - если языковая версия переехала на новый URL, а hreflang продолжает указывать на старый адрес с 301-редиректом, Google обрабатывает это как ошибку и может проигнорировать пару целиком.

Неверные языковые коды - en вместо en-US там, где различие между региональными версиями критично (например, en-US и en-GB с разными ценами), или код страны вместо кода языка (hreflang=“US” вместо hreflang=“en-US”).

Отдельный практический момент: Google убрал отчёт «Международный таргетинг» из Search Console в 2022 году, и готового отчёта по ошибкам hreflang в интерфейсе больше нет. Проверять взаимность и корректность пар приходится через просмотр исходного кода страницы или краулер вроде Screaming Frog, который обходит все языковые версии и сверяет пары между собой.

Реальный кейс: RU + EN сайт для выхода на рынок США

В типовом проекте выхода на рынок США у продуктового сайта на 40-60 страниц с RU-версией для внутренней команды и EN-версией для рынка на аудите чаще всего находится одно и то же: sitemap собирается без i18n-блока, а link rel=“alternate” hreflang в head страниц отсутствует вовсе - при переезде на Astro про них просто забыли на этапе настройки роутинга.

После добавления hreflang и правильной конфигурации sitemap количество страниц EN-версии в индексе Google обычно растёт в течение 3-4 недель - до этого Google трактовал их как дубли RU-контента и не показывал в выдаче США вовсе. Точная скорость зависит от краулингового бюджета конкретного домена, но направление устойчиво повторяется в проектах с похожей структурой.

Для кого актуально

Hreflang в таком объёме нужен компаниям, которые публикуют контент минимум на двух языках и таргетируются на разные страны или регионы одновременно - продуктовый SaaS с RU и EN версией, маркетинговый сайт европейской компании с DE и EN, или локализованный лендинг под конкретный рынок. Если сайт строится с нуля под несколько языков, архитектуру роутинга и hreflang стоит закладывать на этапе выбора фреймворка и структуры проекта, а не добавлять поверх готового сайта - это разбирали в разработке сайта с нуля.

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

Что такое hreflang для мультиязычного сайта и зачем он нужен?

Hreflang для мультиязычного сайта - это атрибут в link rel=“alternate” или в sitemap, который говорит поисковику, что несколько URL - переводы одной страницы на разные языки. Без него Google выбирает языковую версию для показа сам, опираясь на силу ссылок и другие косвенные сигналы, а не на факт перевода - из-за этого пользователь может увидеть не ту версию страницы, а поисковик расценить переводы как дублирующийся контент.

Нужен ли отдельный canonical для каждой языковой версии?

Да. Canonical каждой языковой версии должен указывать сам на себя, а не на страницу языка по умолчанию. Self-referencing canonical в паре с hreflang - стандартная связка: canonical говорит, что это оригинальная версия конкретного URL, а hreflang - что у неё есть переводы. Canonical, указывающий на другой язык, фактически отменяет языковую версию для индекса Google.

Как добавить hreflang в sitemap на Astro?

Через i18n-блок в конфигурации @astrojs/sitemap: указать defaultLocale и список locales с кодами языков. При сборке интеграция автоматически добавит xhtml:link rel=“alternate” hreflang для каждой языковой версии страницы в sitemap.xml - без ручной генерации альтернатив для каждого URL по отдельности.

Как реализовать hreflang в Next.js без ошибок?

Через свойство alternates.languages в объекте Metadata или в generateMetadata - указать код языка в формате ru-RU или en-US как ключ и относительный либо абсолютный путь как значение, плюс запись x-default. С заданным metadataBase Next.js сам достроит абсолютные URL и отрендерит нужные link теги в head страницы.

Что такое x-default и когда он обязателен?

X-default - hreflang-значение, указывающее версию страницы для пользователей, чей язык не совпадает ни с одним из перечисленных в hreflang. Формально он не обязателен, но по данным Ahrefs его отсутствие - самая частая ошибка hreflang: 56,3% доменов из выборки в 374 756 сайтов не указали x-default вовсе. Для сайта, ориентированного на несколько стран, x-default стоит задавать всегда - обычно это англоязычная версия или страница выбора языка.

Итог:

  • Hreflang не опция для мультиязычного сайта, ориентированного на рынки за пределами СНГ - без него часть органического трафика не доходит до правильной языковой версии
  • В Astro связка @astrojs/sitemap и astro:i18n закрывает и sitemap, и head-теги без ручного дублирования логики
  • В Next.js вся конфигурация - это один объект alternates.languages в Metadata API, включая x-default
  • Чек-лист из шести пунктов (canonical, hreflang, x-default, sitemap, lang, переведённые meta) стоит проходить перед каждым запуском новой языковой версии, а не после первого падения трафика

Если вы запускаете сайт на несколько языков или добавляете новую языковую версию к существующему - опишите текущую архитектуру команде Exceltic.dev. Проверим hreflang, sitemap и canonical перед запуском и укажем на конкретные ошибки, если они есть.

Ещё статьи

Все →