**TL;DR.** Hreflang вказує пошуковим системам, яку локалізацію показувати кожному користувачеві. Виправте чотири речі: (1) повні взаємні кластери з самозгадками, (2) правильні ISO коди мов, (3) x-default запасний варіант, (4) обмеження за сутностями, щоб не рекламувати неіснуючі переклади. Усе інше — деталі реалізації.

## Що робить hreflang

Hreflang — це HTML атрибут (і еквівалентна анотація в мапі сайту), який сигналізує Google, на яку мову та регіон націлена сторінка. Правильно реалізований hreflang:

- Показує іспанським користувачам вашу іспанську URL-адресу, а не англійську.
- Запобігає тому, щоб ваша французька URL-адреса перевершувала вашу німецьку URL-адресу в Німеччині.
- Консолідує лінковий капітал між локалізаціями замість того, щоб розглядати їх як дублікати контенту.

Hreflang не підвищує рейтинги. Він контролює, яка локалізація займає яке місце.

## Крок 1: Визначте, де вказувати hreflang

Три варіанти:

| Метод                  | Плюси                                        | Мінуси                                       |
| ---------------------- | --------------------------------------------- | -------------------------------------------- |
| HTML head              | Легко налагоджувати; одне джерело на сторінку | Додає байти до кожної HTML-відповіді        |
| HTTP `Link` заголовок  | Працює для не-HTML ресурсів (PDF тощо)       | Важко налагоджувати; рідко потрібно         |
| XML мапа сайту        | Найбільш компактна для великих каталогів     | Менш видима для людей; потребує дисципліни мапи сайту |

За замовчуванням у 2026 році для більшості інтернет-магазинів — **HTML head**. Перейдіть на мапу сайту, якщо більше 50,000 URL.

## Крок 2: Вкажіть повний кластер

Для кожної перекладеної URL-адреси виведіть hreflang для себе та кожного іншого варіанту.

Кластер з 4 локалізацій (en, de, fr, es) для продукту:

```html
<link rel="alternate" hreflang="en" href="https://example.com/en/products/leather-bag" />
<link rel="alternate" hreflang="de" href="https://example.com/de/products/leather-bag" />
<link rel="alternate" hreflang="fr" href="https://example.com/fr/products/leather-bag" />
<link rel="alternate" hreflang="es" href="https://example.com/es/products/leather-bag" />
<link rel="alternate" hreflang="x-default" href="https://example.com/en/products/leather-bag" />
```

Цей кластер з'являється в заголовку **кожної** локалізації цього продукту. Німецька URL-адреса виводить той самий кластер, французька URL-адреса виводить той самий кластер тощо.

Кластер має бути взаємним — A вказує на B, B вказує на A. Невзаємні кластери ігноруються Google без попередження.

## Крок 3: x-default

`x-default` — це запасний варіант для користувачів, чия мова браузера не відповідає жодному з ваших варіантів. Зазвичай це ваша англійська (або URL-адреса за замовчуванням).

```html
<link rel="alternate" hreflang="x-default" href="https://example.com/en/products/leather-bag" />
```

Та сама URL-адреса може бути як `hreflang="en"`, так і `hreflang="x-default"`. Лише одна URL-адреса в кластері отримує `x-default`.

## Крок 4: Фільтруйте для фактично перекладених сутностей

Найбільша практична помилка: вказувати кластер з 9 локалізацій для продукту, який перекладено лише на 3 локалізації. Рекламовані URL-адреси повертають 404; Google бачить зламаний кластер і може ігнорувати весь hreflang сайту.

Виправлення: обмеження за сутностями `availableLocales`.

```ts
// Псевдокод
interface ProductForSeo {
  slug: string;
  availableLocales: string[];  // наприклад, ["en", "de", "fr"]
}

interface StoreForSeo {
  supportedLocales: string[];  // наприклад, ["en", "de", "fr", "es", "it"]
  defaultLocale: string;
}

function buildHreflangCluster(product: ProductForSeo, store: StoreForSeo) {
  const effective = product.availableLocales.filter((l) =>
    store.supportedLocales.includes(l)
  );
  return effective.map((locale) => ({
    hreflang: locale,
    href: `https://example.com/${locale}/products/${product.slug}`,
  }));
}
```

Ordiko's `src/lib/seo/storefront.ts:316` реалізує цю точну перетворення.

## Крок 5: Правильні коди мов

Використовуйте **ISO 639-1** коди мов:

| Мова                     | Код       |
| ------------------------ | --------- |
| Англійська               | `en`      |
| Іспанська                | `es`      |
| Французька               | `fr`      |
| Німецька                 | `de`      |
| Португальська           | `pt`      |
| Італійська               | `it`      |
| Російська                | `ru`      |
| Українська               | `uk`      |
| Китайська (спрощена)    | `zh-Hans` |
| Китайська (традиційна)   | `zh-Hant` |
| Японська                 | `ja`      |
| Корейська                | `ko`      |
| Арабська                 | `ar`      |

Для мови + регіону (коли ви надаєте варіант локалізації-регіону):

| Варіант                  | Код       |
| ------------------------ | --------- |
| Американська англійська  | `en-US`   |
| Британська англійська    | `en-GB`   |
| Канадська англійська     | `en-CA`   |
| Мексиканська іспанська   | `es-MX`   |
| Іспанська (Іспанія)     | `es-ES`   |
| Французька (Франція)    | `fr-FR`   |
| Французька (Канада)      | `fr-CA`   |
| Португальська (Бразилія) | `pt-BR`   |
| Португальська (Португалія)| `pt-PT`   |

Не використовуйте:

- `en-EN` (недійсний)
- `gb` (використовуйте `en-GB`)
- `cn` (використовуйте `zh-Hans`)
- `kr` (використовуйте `ko`)

## Крок 6: Перевірка

Три перевірки:

1. **Самозгадка**: кожна URL-адреса повинна включати себе в свій власний кластер.
2. **Взаємність**: кожен варіант у кластері повинен включати кожен інший варіант.
3. **Досяжність**: кожна URL-адреса в кластері повинна повертати 200.

Інструменти:

| Інструмент                        | Що перевіряє                              |
| --------------------------------- | ------------------------------------------ |
| Google Search Console             | Реальні помилки hreflang з часом         |
| [hreflang.org/validator](https://hreflang.org) | Правильність кластера                    |
| Sitebulb / Screaming Frog        | Взаємність та самозгадка                 |
| Ahrefs / Semrush                 | Звіти hreflang у аудиті сайту            |
| Ручний `curl`                    | Досяжність для кожної URL                |

Шаблон тесту Vitest для CI:

```ts
import { describe, it, expect } from 'vitest';

describe('hreflang cluster', () => {
  it('every variant URL returns 200', async () => {
    const cluster = [
      'https://example.com/en/products/x',
      'https://example.com/de/products/x',
      'https://example.com/fr/products/x',
    ];
    for (const url of cluster) {
      const res = await fetch(url);
      expect(res.status).toBe(200);
    }
  });

  it('every variant emits self-referencing hreflang', async () => {
    for (const url of cluster) {
      const html = await fetch(url).then((r) => r.text());
      expect(html).toContain(`hreflang="${getLocale(url)}" href="${url}"`);
    }
  });
});
```

## Спеціальні випадки

**Односторінкові додатки**: вказуйте hreflang на серверній стороні в заголовку документа. Не покладайтеся на ін'єкцію на стороні клієнта — краулер Google може не виконати ваш JS.

**ccTLDs**: hreflang все ще застосовується. `example.de` може вказати `hreflang="de-DE"` для себе та посилатися на `example.com/en/...` для `en`.

**Регіон без мовної різниці**: якщо ви надаєте `en-US` та `en-GB` з ідентичним контентом, hreflang їх розрізняє. Не діліть URL — надайте кожному свій власний URL, навіть якщо контент ідентичний.

## Загальні помилки

| Помилка                                  | Ефект                                              |
| ---------------------------------------- | --------------------------------------------------- |
| Невзаємний кластер                       | Google ігнорує hreflang повністю                    |
| URL-адреса варіанту повертає 404        | Google ігнорує URL; може знизити кластер          |
| Неправильний код мови                   | Варіант розглядається як нецільовий               |
| Кілька URL-адрес x-default               | Один ігнорується випадковим чином                  |
| Hreflang тільки на канонічній URL       | Підсторінки не мають сигналу hreflang              |
| Змішування абсолютних і відносних URL    | Завжди використовуйте абсолютні URL                 |
| Різний домен для локалізації без послідовного hreflang | Кластер ламається                       |

## Як Ordiko вказує hreflang

`src/lib/seo/storefront.ts` обчислює `alternates.languages` для кожного об'єкта метаданих сторінки Next.js. Функція приймає:

- `store.supportedLocales`
- `entity.availableLocales` (необов'язково, за замовчуванням — `store.supportedLocales`)
- `store.defaultLocale` (використовується для x-default)

І виводить кластер перетворення в HTML заголовок. Самозгадка, взаємність та x-default гарантуються реалізацією. Обмеження за сутностями є опційним через поле `availableLocales` у завантажувачі сутностей.

## Часті запитання

**HTML head чи мапа сайту — що краще?**
HTML head легше налагоджувати, оскільки ви можете перевірити за допомогою перегляду виходу. Мапа сайту є більш компактною для магазинів з 50k+ URL, оскільки hreflang вказується один раз для кожної пари URL, а не в заголовку кожної сторінки. Виберіть HTML для магазинів з менше ніж 50k URL; мапа сайту для понад цього. Ordiko за замовчуванням вказує HTML head.

**Який код мови я використовую для іспанської Латинської Америки та іспанської Іспанії?**
es-MX для Мексики, es-AR для Аргентини, es-CO для Колумбії, es-ES для Іспанії. Комбінація мови-регіону підтримується. Для однієї латинської американської локалізації, що обслуговує кілька країн, es-419 (код ООН для Латинської Америки та Карибів) є дійсним, але менш широко підтримується.

**Чи можу я мати кілька URL-адрес x-default?**
Ні. Один x-default на кластер. Якщо ваш бізнес обслуговує англійську глобально, .com (або ваша URL-адреса за замовчуванням) є x-default. Та сама URL-адреса може бути як hreflang='en', так і hreflang='x-default'.

**Як Ordiko обробляє правильність hreflang?**
Бібліотека SEO Ordiko's storefront (src/lib/seo/storefront.ts) перетинає доступні локалізації кожної сутності з підтримуваними локалізаціями магазину під час виведення alternates.languages. Продукт, перекладений лише на en + de, рекламує лише ці дві локалізації, незалежно від ширшого набору локалізацій магазину.