**TL;DR.** Hreflang sagt Suchmaschinen, welche Sprachvariante jedem Benutzer angezeigt werden soll. Achte auf vier Dinge: (1) vollständige wechselseitige Cluster mit Selbstreferenzen, (2) korrekte ISO-Sprachcodes, (3) x-default-Fallback, (4) pro-Entity-Gating, damit du keine nicht existierenden Übersetzungen bewirbst. Alles andere ist Implementierungsdetail.

## Was hreflang tut

Hreflang ist ein HTML-Attribut (und äquivalente Sitemap-Anmerkung), das Google signalisiert, welche Sprache und Region eine Seite anvisiert. Richtig implementiertes hreflang:

- Dient spanischen Nutzern deine spanische URL, nicht deine englische.
- Verhindert, dass deine französische URL deine deutsche URL in Deutschland übertrumpft.
- Konsolidiert Link-Eigenkapital über Sprachvarianten hinweg, anstatt sie als doppelte Inhalte zu behandeln.

Hreflang erhöht nicht die Rankings. Es steuert, welche Sprachvariante wo eingestuft wird.

## Schritt 1: Entscheide, wo hreflang ausgegeben werden soll

Drei Optionen:

| Methode               | Vorteile                                      | Nachteile                                   |
| --------------------- | --------------------------------------------- | ------------------------------------------- |
| HTML-Kopf             | Einfach zu debuggen; eine Quelle pro Seite    | Fügt Bytes zu jeder HTML-Antwort hinzu     |
| HTTP `Link`-Header    | Funktioniert für Nicht-HTML-Ressourcen (PDFs usw.) | Schwer zu debuggen; selten benötigt         |
| XML-Sitemap           | Kompakt für große Kataloge                     | Weniger sichtbar für Menschen; benötigt Sitemap-Disziplin |

Der Standard für die meisten E-Commerce-Seiten im Jahr 2026 ist **HTML-Kopf**. Wechsel zu Sitemap nur bei über 50.000 URLs.

## Schritt 2: Ein vollständiges Cluster ausgeben

Für jede übersetzte URL, gebe hreflang für sich selbst und jede andere Variante aus.

Ein 4-Sprachen-Cluster (en, de, fr, es) für ein Produkt:

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

Dieses Cluster erscheint im Kopf **jeder** Sprachvariante dieses Produkts. Die deutsche URL gibt dasselbe Cluster aus, die französische URL gibt dasselbe Cluster aus usw.

Das Cluster muss wechselseitig sein — A verweist auf B, B verweist auf A. Nicht-wechselseitige Cluster werden von Google stillschweigend ignoriert.

## Schritt 3: x-default

`x-default` ist der Fallback für Benutzer, deren Browsersprache mit keiner deiner Varianten übereinstimmt. Typischerweise deine englische (oder Standard-Locale) URL.

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

Die gleiche URL kann sowohl `hreflang="en"` als auch `hreflang="x-default"` sein. Nur eine URL im Cluster erhält `x-default`.

## Schritt 4: Filtern auf tatsächlich übersetzte Entitäten

Der größte praktische Fehler: ein 9-Sprachen-Cluster für ein Produkt auszugeben, das nur in 3 Sprachen übersetzt ist. Die beworbenen URLs geben 404 zurück; Google sieht das defekte Cluster und könnte das gesamte hreflang der Seite ignorieren.

Die Lösung: pro-Entity `availableLocales`.

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

interface StoreForSeo {
  supportedLocales: string[];  // z.B. ["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` implementiert genau diese Schnittmenge.

## Schritt 5: Korrekte Sprachcodes

Verwende **ISO 639-1** Sprachcodes:

| Sprache                 | Code      |
| ----------------------- | --------- |
| Englisch                | `en`      |
| Spanisch                | `es`      |
| Französisch             | `fr`      |
| Deutsch                 | `de`      |
| Portugiesisch           | `pt`      |
| Italienisch             | `it`      |
| Russisch                | `ru`      |
| Ukrainisch              | `uk`      |
| Chinesisch (Vereinfacht) | `zh-Hans` |
| Chinesisch (Traditionell) | `zh-Hant` |
| Japanisch               | `ja`      |
| Koreanisch              | `ko`      |
| Arabisch                | `ar`      |

Für Sprache + Region (wenn du eine Locale-Region-Variante anbietest):

| Variante                | Code      |
| ----------------------- | --------- |
| US-Englisch             | `en-US`   |
| UK-Englisch             | `en-GB`   |
| Kanadisches Englisch     | `en-CA`   |
| Mexikanisches Spanisch   | `es-MX`   |
| Spanisches Spanisch      | `es-ES`   |
| Französisches Französisch | `fr-FR`   |
| Kanadisches Französisch   | `fr-CA`   |
| Brasilianisches Portugiesisch | `pt-BR`   |
| Portugiesisches Portugiesisch | `pt-PT`   |

Verwende nicht:

- `en-EN` (ungültig)
- `gb` (verwende `en-GB`)
- `cn` (verwende `zh-Hans`)
- `kr` (verwende `ko`)

## Schritt 6: Validieren

Drei Überprüfungen:

1. **Selbstreferenz**: Jede URL muss sich selbst in ihrem eigenen Cluster enthalten.
2. **Wechselseitigkeit**: Jede Variante im Cluster muss jede andere Variante enthalten.
3. **Erreichbarkeit**: Jede URL im Cluster muss 200 zurückgeben.

Werkzeuge:

| Werkzeug                          | Was es überprüft                             |
| --------------------------------- | -------------------------------------------- |
| Google Search Console             | Hreflang-Fehler in der realen Welt über die Zeit |
| [hreflang.org/validator](https://hreflang.org) | Korrektheit des Clusters                   |
| Sitebulb / Screaming Frog         | Wechselseitigkeit und Selbstreferenz        |
| Ahrefs / Semrush                  | Hreflang-Berichte im Site-Audit            |
| Manuelles `curl`                  | Erreichbarkeit pro URL                      |

Vitest-Testmuster für CI:

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

describe('hreflang cluster', () => {
  it('jede Varianten-URL gibt 200 zurück', 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('jede Variante gibt selbstreferenzierendes hreflang aus', async () => {
    for (const url of cluster) {
      const html = await fetch(url).then((r) => r.text());
      expect(html).toContain(`hreflang="${getLocale(url)}" href="${url}"`);
    }
  });
});
```

## Besondere Fälle

**Single-Page-Apps**: gebe hreflang serverseitig im Dokumentenkopf aus. Verlasse dich nicht auf clientseitige Injektion — der Crawler von Google könnte dein JS nicht ausführen.

**ccTLDs**: hreflang gilt weiterhin. `example.de` kann `hreflang="de-DE"` für sich selbst angeben und auf `example.com/en/...` für `en` verlinken.

**Region ohne Sprachunterschied**: Wenn du `en-US` und `en-GB` mit identischem Inhalt anbietest, differenziert hreflang sie. Teile die URL nicht — gib jeder ihre eigene URL, auch wenn der Inhalt identisch ist.

## Häufige Fallstricke

| Fehler                                   | Effekt                                           |
| ---------------------------------------- | ------------------------------------------------ |
| Nicht-wechselseitiges Cluster            | Google ignoriert hreflang vollständig            |
| Varianten-URL gibt 404 zurück            | Google ignoriert die URL; könnte Cluster abwerten |
| Falscher Sprachcode                      | Variante wird als nicht gezielt behandelt       |
| Mehrere x-default-URLs                   | Eine wird willkürlich ignoriert                  |
| Hreflang nur auf der kanonischen URL     | Unterseiten haben kein hreflang-Signal           |
| Mischen von absoluten und relativen URLs | Verwende immer absolute URLs                      |
| Verschiedene Domains pro Locale ohne konsistentes hreflang | Cluster bricht                       |

## Wie Ordiko hreflang ausgibt

`src/lib/seo/storefront.ts` berechnet `alternates.languages` für jedes Next.js-Seitenmetadatenobjekt. Die Funktion nimmt:

- `store.supportedLocales`
- `entity.availableLocales` (optional, standardmäßig `store.supportedLocales`)
- `store.defaultLocale` (verwendet für x-default)

Und gibt das Schnittmengen-Cluster im HTML-Kopf aus. Selbstreferenz, Wechselseitigkeit und x-default sind durch die Implementierung garantiert. Pro-Entity-Gating ist optional über das Feld `availableLocales` im Entity-Loader.

## FAQ

**HTML-Kopf oder Sitemap — was ist besser?**
HTML-Kopf ist einfacher zu debuggen, da du mit "Seitenquelltext anzeigen" überprüfen kannst. Sitemap ist kompakter für Shops mit über 50k URLs, da hreflang einmal pro URL-Paar ausgegeben wird, nicht im Kopf jeder Seite. Wähle HTML für Shops unter 50k URLs; Sitemap darüber. Ordiko gibt standardmäßig HTML-Kopf aus.

**Welchen Sprachcode verwende ich für Lateinamerikanisches Spanisch vs Spanisches Spanisch?**
es-MX für Mexiko, es-AR für Argentinien, es-CO für Kolumbien, es-ES für Spanien. Die Sprach-Region-Kombination wird unterstützt. Für eine einzelne lateinamerikanische Locale, die mehrere Länder bedient, ist es-419 (der UN-Code für Lateinamerika und Karibik) gültig, aber weniger weit verbreitet.

**Kann ich mehrere x-default-URLs haben?**
Nein. Eine x-default pro Cluster. Wenn dein Unternehmen Englisch global anbietet, ist die .com (oder deine Standard-Locale-URL) die x-default. Die gleiche URL kann sowohl hreflang='en' als auch hreflang='x-default' sein.

**Wie geht Ordiko mit der Korrektheit von hreflang um?**
Die SEO-Bibliothek von Ordiko (src/lib/seo/storefront.ts) schneidet die verfügbaren Locales jeder Entität mit den unterstützten Locales des Shops ab, wenn alternates.languages ausgegeben werden. Ein Produkt, das nur in en + de übersetzt ist, wird nur diese beiden Locales bewerben, unabhängig von der breiteren Locale-Auswahl des Shops.