**TL;DR.** Hreflang indique aux moteurs de recherche quelle variante locale servir à chaque utilisateur. Assurez-vous de quatre choses : (1) des clusters réciproques complets avec des auto-références, (2) des codes de langue ISO corrects, (3) un fallback x-default, (4) un filtrage par entité pour ne pas faire de la publicité pour des traductions inexistantes. Tout le reste est un détail d'implémentation.

## Ce que fait hreflang

Hreflang est un attribut HTML (et une annotation de sitemap équivalente) qui signale à Google quelle langue et quelle région une page cible. Un hreflang correctement implémenté :

- Sert aux utilisateurs hispanophones votre URL espagnole, pas votre URL anglaise.
- Empêche votre URL française de surpasser votre URL allemande en Allemagne.
- Consolide l'équité des liens à travers les variantes locales au lieu de les traiter comme du contenu dupliqué.

Hreflang ne booste pas les classements. Il contrôle quelle variante locale se classe où.

## Étape 1 : Décider où émettre hreflang

Trois options :

| Méthode               | Avantages                                     | Inconvénients                                  |
| --------------------- | --------------------------------------------- | ---------------------------------------------- |
| En-tête HTML          | Facile à déboguer ; une source par page      | Ajoute des octets à chaque réponse HTML       |
| En-tête HTTP `Link`   | Fonctionne pour les ressources non-HTML (PDF, etc.) | Difficile à déboguer ; rarement nécessaire    |
| Sitemap XML           | Le plus compact pour les grands catalogues    | Moins visible pour les humains ; nécessite une discipline de sitemap |

Le défaut de 2026 pour la plupart des sites e-commerce est **l'en-tête HTML**. Passez à sitemap uniquement au-dessus de 50 000 URLs.

## Étape 2 : Émettre un cluster complet

Pour chaque URL traduite, émettez hreflang pour elle-même et chaque autre variante.

Un cluster de 4 locales (en, de, fr, es) pour un produit :

```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" />
```

Ce cluster apparaît dans l'en-tête de **chaque** variante locale de ce produit. L'URL allemande émet le même cluster, l'URL française émet le même cluster, etc.

Le cluster doit être réciproque — A pointe vers B, B pointe vers A. Les clusters non réciproques sont silencieusement ignorés par Google.

## Étape 3 : x-default

`x-default` est le fallback pour les utilisateurs dont la langue de navigateur ne correspond à aucune de vos variantes. Typiquement votre URL anglaise (ou URL par défaut).

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

La même URL peut être à la fois `hreflang="en"` et `hreflang="x-default"`. Une seule URL dans le cluster obtient `x-default`.

## Étape 4 : Filtrer pour les entités réellement traduites

La plus grande erreur pratique : émettre un cluster de 9 locales pour un produit qui n'est traduit que dans 3 locales. Les URLs annoncées retournent 404 ; Google voit le cluster cassé et peut ignorer le hreflang de l'ensemble du site.

La solution : `availableLocales` par entité.

```ts
// Pseudocode
interface ProductForSeo {
  slug: string;
  availableLocales: string[];  // e.g. ["en", "de", "fr"]
}

interface StoreForSeo {
  supportedLocales: string[];  // e.g. ["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}`,
  }));
}
```

Le fichier `src/lib/seo/storefront.ts` d'Ordiko implémente cette intersection exacte.

## Étape 5 : Codes de langue corrects

Utilisez les codes de langue **ISO 639-1** :

| Langue                  | Code      |
| ----------------------- | --------- |
| Anglais                 | `en`      |
| Espagnol                | `es`      |
| Français                | `fr`      |
| Allemand                | `de`      |
| Portugais               | `pt`      |
| Italien                 | `it`      |
| Russe                   | `ru`      |
| Ukrainien               | `uk`      |
| Chinois (simplifié)     | `zh-Hans` |
| Chinois (traditionnel)  | `zh-Hant` |
| Japonais                | `ja`      |
| Coréen                  | `ko`      |
| Arabe                   | `ar`      |

Pour la langue + région (lorsque vous servez une variante locale-région) :

| Variante                | Code      |
| ----------------------- | --------- |
| Anglais US              | `en-US`   |
| Anglais UK              | `en-GB`   |
| Anglais canadien        | `en-CA`   |
| Espagnol du Mexique     | `es-MX`   |
| Espagnol d'Espagne      | `es-ES`   |
| Français de France      | `fr-FR`   |
| Français du Canada      | `fr-CA`   |
| Portugais du Brésil     | `pt-BR`   |
| Portugais du Portugal   | `pt-PT`   |

Ne pas utiliser :

- `en-EN` (invalide)
- `gb` (utilisez `en-GB`)
- `cn` (utilisez `zh-Hans`)
- `kr` (utilisez `ko`)

## Étape 6 : Valider

Trois vérifications :

1. **Auto-référence** : chaque URL doit inclure elle-même dans son propre cluster.
2. **Réciprocité** : chaque variante dans le cluster doit inclure chaque autre variante.
3. **Accessibilité** : chaque URL dans le cluster doit retourner 200.

Outils :

| Outil                            | Ce qu'il vérifie                             |
| ------------------------------- | -------------------------------------------- |
| Google Search Console           | Erreurs hreflang dans le monde réel au fil du temps |
| [hreflang.org/validator](https://hreflang.org) | Correction du cluster                     |
| Sitebulb / Screaming Frog       | Réciprocité et auto-référence               |
| Ahrefs / Semrush                | Rapports hreflang dans l'audit de site     |
| `curl` manuel                   | Accessibilité par URL                       |

Modèle de test Vitest pour CI :

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

describe('cluster hreflang', () => {
  it('chaque URL de variante retourne 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('chaque variante émet hreflang auto-référent', async () => {
    for (const url of cluster) {
      const html = await fetch(url).then((r) => r.text());
      expect(html).toContain(`hreflang="${getLocale(url)}" href="${url}"`);
    }
  });
});
```

## Cas spéciaux

**Applications à page unique** : émettez hreflang côté serveur dans l'en-tête du document. Ne comptez pas sur l'injection côté client — le crawler de Google peut ne pas exécuter votre JS.

**ccTLDs** : hreflang s'applique toujours. `example.de` peut spécifier `hreflang="de-DE"` pour lui-même et lier à `example.com/en/...` pour `en`.

**Région sans différence de langue** : si vous servez `en-US` et `en-GB` avec un contenu identique, hreflang les différencie. Ne partagez pas l'URL — donnez à chacune sa propre URL même si le contenu est identique.

## Pièges courants

| Erreur                                   | Effet                                              |
| ---------------------------------------- | --------------------------------------------------- |
| Cluster non réciproque                   | Google ignore complètement hreflang                |
| URL de variante retourne 404             | Google ignore l'URL ; peut déclasser le cluster    |
| Mauvais code de langue                   | Variante traitée comme non ciblée                  |
| Plusieurs URLs x-default                 | Une est ignorée arbitrairement                     |
| Hreflang uniquement sur l'URL canonique  | Les sous-pages n'ont pas de signal hreflang        |
| Mélange d'URLs absolues et relatives     | Utilisez toujours des URLs absolues                |
| Domaine différent par locale sans hreflang cohérent | Le cluster se casse                       |

## Comment Ordiko émet hreflang

`src/lib/seo/storefront.ts` calcule `alternates.languages` pour chaque objet de métadonnées de page Next.js. La fonction prend :

- `store.supportedLocales`
- `entity.availableLocales` (optionnel, par défaut à store.supportedLocales)
- `store.defaultLocale` (utilisé pour x-default)

Et émet le cluster d'intersection dans l'en-tête HTML. L'auto-référence, la réciprocité et x-default sont garanties par l'implémentation. Le filtrage par entité est opt-in via le champ `availableLocales` sur le chargeur d'entité.

## FAQ

**En-tête HTML ou sitemap — lequel est meilleur ?**
L'en-tête HTML est plus facile à déboguer car vous pouvez vérifier avec Afficher la source. Le sitemap est plus compact pour les magasins avec plus de 50k URLs car hreflang est émis une fois par paire d'URLs, pas dans l'en-tête de chaque page. Choisissez HTML pour les magasins de moins de 50k URLs ; sitemap au-dessus de cela. Ordiko émet par défaut l'en-tête HTML.

**Quel code de langue dois-je utiliser pour l'espagnol d'Amérique latine par rapport à l'espagnol d'Espagne ?**
es-MX pour le Mexique, es-AR pour l'Argentine, es-CO pour la Colombie, es-ES pour l'Espagne. La combinaison langue-région est supportée. Pour une seule locale d'Amérique latine servant plusieurs pays, es-419 (le code de l'ONU pour l'Amérique latine et les Caraïbes) est valide mais moins largement supporté.

**Puis-je avoir plusieurs URLs x-default ?**
Non. Un x-default par cluster. Si votre entreprise sert l'anglais à l'échelle mondiale, le .com (ou votre URL par défaut) est le x-default. La même URL peut être à la fois hreflang='en' et hreflang='x-default'.

**Comment Ordiko gère-t-il la correction hreflang ?**
La bibliothèque SEO de la vitrine d'Ordiko (src/lib/seo/storefront.ts) croise les `availableLocales` de chaque entité avec les locales supportées du magasin lors de l'émission de `alternates.languages`. Un produit traduit uniquement en en + de ne fera de la publicité que pour ces deux locales, indépendamment de l'ensemble de locales plus large du magasin.