Wissen

Mit hreflang jede Sprach- und Länderversion einer Seite finden

hreflang listet jede lokalisierte Version einer Seite auf. Wir haben untersucht, wie Top-Websites es nutzen, was dabei nicht funktioniert und warum manche Versionen sich nur aus einem Land heraus öffnen lassen.

James Meadow

James Meadow

1. Oktober 2026 · 11 Min. Lesezeit

Wenn Sie Preise, Inhalte oder Verfügbarkeit über mehrere Märkte hinweg vergleichen, besteht das erste Problem darin, dieselbe Seite in jedem Markt zu finden. Das Erraten von URL-Mustern funktioniert bei manchen Sites, scheitert aber bei den meisten: Eine Site nutzt /de/, eine andere de.example.com, eine weitere einen Query-Parameter und eine vierte eine eigene Domain pro Land.

Viele Sites veröffentlichen die Antwort bereits. Die hreflang-Annotation, die Sites für Suchmaschinen hinzufügen, listet jede Sprach- und Länderversion einer Seite samt Fundort auf. Liest man sie aus, liefert ein einziger Abruf die gesamte Menge. Dieser Leitfaden erklärt, wie das funktioniert, was wir bei der Prüfung der Nutzung durch Top-Sites festgestellt haben und warum der Besitz der richtigen URL nicht immer ausreicht, um die richtige Seite zu sehen.

Die wichtigsten Erkenntnisse

  • Am 1. Oktober 2026 gaben 82 von 253 abgerufenen Top-Site-Startseiten (32 %) hreflang-Alternates an, mit einem Median von 17,5 Versionen pro Seite und bis zu 157.
  • Ein einziger Abruf einer annotierten Seite liefert jede lokalisierte URL dieser Seite, ohne dass URL-Muster erraten werden müssen.
  • Die Annotationen sind nicht immer sauber: 12 der 82 Sites verwendeten mindestens einen Code, den Google nicht unterstützt, etwa en-uk, es-419, en_GB mit Unterstrich oder das Land vor der Sprache.
  • 86 % der von uns geprüften Alternates verlinkten direkt zurück, wie von Google gefordert. Die meisten übrigen verwiesen auf eine andere URL für dieselbe Seite oder leiteten um.
  • Manche Versionen öffnen sich nur vom richtigen Ort aus. Die US-Startseite einer Nachrichtenseite und die deutsche Storefront eines Marktplatzes leiteten unsere Verbindung aus Rumänien anderswohin um und luden normal über Exits in den Vereinigten Staaten und in Deutschland.

Wie hreflang funktioniert

Eine Seite listet ihre Alternates in ihrem <head>:

<link rel="alternate" hreflang="en" href="https://example.com/" />
<link rel="alternate" hreflang="de" href="https://example.com/de/" />
<link rel="alternate" hreflang="pt-BR" href="https://example.com/pt/" />
<link rel="alternate" hreflang="x-default" href="https://example.com/" />

Jeder Wert ist ein Sprachcode, gegebenenfalls gefolgt von einer Schrift oder einer Region: de für Deutsch überall, en-GB für Englisch im Vereinigten Königreich, zh-Hant für traditionelles Chinesisch. x-default benennt den Fallback für Besucher, die zu keinem der Codes passen. Googles Dokumentation legt die Regeln fest, an die sich die meisten Sites halten:

  • Sprachen nutzen ISO-639-1-Codes, Schriften ISO 15924 und Regionen ISO 3166-1 Alpha-2. Eine Region allein ist nicht gültig, und Codes außerhalb dieser Standards, etwa es-419 für lateinamerikanisches Spanisch, werden nicht unterstützt.
  • URLs müssen vollständig qualifiziert sein, einschließlich https://.
  • Jede Version muss sich selbst und jede andere Version auflisten, und wenn zwei Seiten nicht aufeinander verweisen, können die Annotationen ignoriert werden.
  • Dieselbe Information kann im HTML, in einem HTTP-Link-Header (nützlich für PDFs) oder in einer Sitemap angegeben werden.

Für die Datenerhebung ist die dritte Regel die nützliche. Da jede Version jede andere auflisten soll, ist jede einzelne von ihnen eine vollständige Karte der Menge.

Was wir bei Top-Sites gefunden haben

Wir haben die Startseiten der 500 führenden Domains im Tranco-Ranking populärer Sites abgerufen. 253 verschiedene Sites lieferten eine HTML-Startseite; der Rest waren Content-Netzwerke, API-Hosts und andere Domains ohne eigene Startseite, oder Duplikate einer bereits gezählten Site.

MaßzahlErgebnis
Startseiten mit hreflang-Annotationen82 von 253 (32 %)
Alternates pro annotierter SeiteMedian 17,5, Maximum 157
Seiten mit x-default63 von 82
Startseiten mit hreflang in einem HTTP-Link-Header0
Sites mit mindestens einem nicht unterstützten Code12 von 82
Sites mit relativen URLs2
Sites, die denselben Code doppelt aufführen6

Die nicht unterstützten Codes folgten einigen Mustern:

MusterBeispielSites
Land vor Sprache, plus regionale Bezeichnungenmx-es, cz-cs, emea_africa-en1 (48 Codes)
Unterstrich statt Bindestrichen_GB, de_DE1 (25 Codes)
Regionscode, der nicht ISO 3166-1 isten-uk, en-eu, sq-xk3
Lateinamerikanisches Spanisches-4192
Sprachcode, der nicht ISO 639-1 istceb, skr, das veraltete iw für Hebräisch3
Erfundene Codestc, zh-FT, ms-en3

Einige Sites erscheinen in mehr als einer Zeile. Das Muster „Land zuerst“ ist für einen Parser am trügerischsten, da mehrere dieser Codes zufällig in umgekehrter Reihenfolge gültig sind: ca ist der Code für Katalanisch, id für Indonesisch und th für Thailändisch, sodass ca-en als Katalanisch statt als Englisch für Kanada gelesen wird. Leiten Sie niemals einen Markt allein aus dem Code ab, ohne zu prüfen, ob er Sinn ergibt.

Wir haben aus jeder annotierten Startseite bis zu zwei Alternates entnommen, insgesamt 155, sie abgerufen und geprüft, ob jede die Startseite zurück verlinkt.

ErgebnisAlternates
Direkt zurückverlinkt133 (86 %)
Zurückverlinkt auf eine andere URL für dieselbe Seite9
Woanders hin umgeleitet9
Hat die Seite tatsächlich nicht aufgeführt4, bei zwei Sites

Die zweite Zeile ist die subtile. Bei mehreren Sites lag die Startseite, auf der wir landeten, unter einer URL, während die Menge eine andere benannte: / gegenüber /en/, /homepage oder /home.html, oder eine bloße URL gegenüber einer mit Sprachparameter. Die Annotationen waren konsistent; unser Einstiegspunkt war einfach nicht die URL, die die Site als kanonisch betrachtet. Beim Sammeln sollten Sie jede Version nach der URL indexieren, die die Menge selbst verwendet, nicht nach der, bei der Sie zufällig gelandet sind.

Die richtige URL reicht nicht immer aus

Die Umleitungen waren der aufschlussreichste Teil. Wir haben jeden umleitenden Alternate erneut abgerufen, direkt und über Shifter-Exits im Zielland, mit Englisch und mit lokalen Accept-Language-Headern:

SitetypAlternateAus RumänienAus dem ZiellandEntschieden durch
NachrichtenseiteUS-StartseiteUmgeleitet auf die internationale AusgabeGeladen, über einen US-ExitStandort
MarktplatzDeutsche StorefrontUmgeleitet auf die globale SiteGeladen, über einen deutschen ExitStandort
Videotelefonie-AnbieterJapanische StartseiteLud nur mit japanischem Accept-LanguageDasselbe, über einen japanischen ExitSprache
SpieleverlagArabische StartseiteUmgeleitetUmgeleitet, über einen saudischen ExitWeder noch: gelistet, aber nicht erreichbar

Zwei Sites entschieden nach dem Standort des Besuchers, der Marktplatz unabhängig von der Sprache, sodass deren eigene Annotationen auf Seiten verwiesen, die ein Besucher von anderswo nicht öffnen konnte. Eine entschied nach Sprache, unabhängig vom Standort. Und eine listete eine Version, die in jeder von uns getesteten Kombination umleitete, was man wissen sollte, bevor man darauf einen Marktvergleich aufbaut.

Hier treffen sich hreflang und Exit-Standort. Die Annotationen sagen Ihnen, welche Versionen existieren und wo; sie getreu zu erfassen bedeutet, jede Version so anzufragen, wie es ein lokaler Besucher täte, aus diesem Land und mit dieser Sprache, wie in Abgleich von Proxy-Geo, Zeitzone und Locale behandelt. Andernfalls riskieren Sie, die internationale Version unter einem deutschen Label zu erfassen.

Der Code

Das folgende Modul liest die HTML-Alternates einer Seite aus, markiert nicht unterstützte Codes und relative URLs und prüft Rücklinks, wobei es für jeden fehlschlagenden Alternate meldet, warum er fehlschlägt:

from html.parser import HTMLParser
from urllib.parse import urljoin, urlsplit, urlunsplit

import requests
from babel import Locale

# Google akzeptiert ISO-639-1-Sprachen, ISO-15924-Schriften und ISO-3166-1-Alpha-2-Regionen.
LANGUAGES = {code for code in Locale("en").languages if len(code) == 2}
NOT_ISO_3166 = {"AC", "CP", "CQ", "DG", "EA", "EU", "EZ", "IC", "QO", "TA", "UN", "XA", "XB", "XK", "ZZ"}
REGIONS = {code for code in Locale("en").territories if code.isalpha()} - NOT_ISO_3166
SCRIPTS = set(Locale("en").scripts)


class AlternateLinks(HTMLParser):
    """Collect <link rel="alternate" hreflang="..."> elements from a page's markup."""

    def __init__(self):
        super().__init__()
        self.links = []

    def handle_starttag(self, tag, attrs):
        a = dict(attrs)
        if tag == "link" and "alternate" in (a.get("rel") or "").lower().split() and a.get("hreflang") and a.get("href"):
            self.links.append((a["hreflang"].strip(), a["href"].strip()))


def valid_hreflang(code):
    """True for x-default, a language, or language-region / language-script(-region) codes."""
    if code.lower() == "x-default":
        return True
    parts = code.split("-")
    if parts[0].lower() not in LANGUAGES:
        return False
    rest = parts[1:]
    if rest and rest[0].title() in SCRIPTS:
        rest = rest[1:]
    if rest and rest[0].upper() in REGIONS:
        rest = rest[1:]
    return not rest


def hreflang_map(url, session=None, timeout=20):
    """Fetch a page and return its declared alternates as {hreflang: absolute URL}, plus any problems found."""
    session = session or requests.Session()
    response = session.get(url, timeout=timeout)
    parser = AlternateLinks()
    parser.feed(response.text)  # link tags are normally ASCII, so the decoding choice rarely matters here
    alternates, problems = {}, []
    for code, href in parser.links:
        if not href.startswith(("http://", "https://")):
            problems.append(f"relative URL for {code}: {href}")
        if not valid_hreflang(code):
            problems.append(f"invalid code: {code}")
        alternates[code] = urljoin(response.url, href)
    return response.url, alternates, problems


def normalise(url):
    """Compare URLs without default ports, host case or a trailing slash getting in the way."""
    parts = urlsplit(url)
    port = f":{parts.port}" if parts.port and parts.port not in (80, 443) else ""
    return urlunsplit((parts.scheme.lower(), (parts.hostname or "") + port, parts.path.rstrip("/") or "/", parts.query, ""))


def check_return_links(url, alternates, session=None, timeout=20):
    """Fetch each alternate and report why it breaks the return-link rule. An empty result means all link back."""
    session = session or requests.Session()
    target = normalise(url)
    problems = {}
    for code, alternate in alternates.items():
        if normalise(alternate) == target:
            continue
        final, back, _ = hreflang_map(alternate, session, timeout)
        if normalise(final) != normalise(alternate):
            problems[code] = f"redirects to {final}"
        elif not back:
            problems[code] = "has no hreflang annotations"
        elif target not in {normalise(u) for u in back.values()}:
            problems[code] = "does not link back"
    return problems

Ausführung gegen unsere eigene Startseite:

import requests

from hreflang import hreflang_map, check_return_links

session = requests.Session()
session.headers["User-Agent"] = "ExampleCollector/1.0 (+https://example.com/bot)"

url, alternates, problems = hreflang_map("https://shifter.io/", session)
for code, alternate in sorted(alternates.items()):
    print(code, alternate)
print("problems:", problems)
print("return-link problems:", check_return_links(url, alternates, session))
de https://shifter.io/de
en https://shifter.io/
es https://shifter.io/es
fr https://shifter.io/fr
ja https://shifter.io/ja
ko https://shifter.io/ko
pt-BR https://shifter.io/pt
x-default https://shifter.io/
zh-Hans https://shifter.io/cn
problems: []
return-link problems: {}

Zwei Grenzen sollte man kennen. Der Code liest Annotationen nur im HTML; Sites, die sie in einem HTTP-Header oder einer Sitemap veröffentlichen, erfordern, dass auch diese ausgelesen werden, und Sitemaps als Erkennungsquelle behandelt die Sitemap-Seite davon. Und check_return_links ruft jeden ihm übergebenen Alternate ab, also sollten Sie bei einer Seite mit 150 Versionen eine Stichprobe nehmen oder die Anfragen zeitlich strecken.

In der Praxis einsetzen

  • Beginnen Sie mit einer Seite pro Vorlage. Ein Produkt, eine Kategorie und eine Artikelseite teilen sich meist ein Annotationsmuster, sodass ein Abruf jeder dieser Seiten zeigt, wie die gesamte Site ihre Märkte abbildet.
  • Indexieren Sie Versionen nach den eigenen URLs der Menge. Speichern Sie die URL, die jeder Alternate angibt, und behandeln Sie Umleitungen als Befunde statt als Rauschen.
  • Rufen Sie jede Version wie ein lokaler Besucher ab. Nutzen Sie einen Exit im jeweiligen Markt und dessen Sprache, und prüfen Sie dann, ob Sie nicht umgeleitet wurden. Der Standort entschied in zwei unserer vier Umleitungsfälle, wie auch in welche Länder am häufigsten geoblockt werden.
  • Validieren Sie Codes, bevor Sie ihnen vertrauen. Ein vertauschter oder erfundener Code kann eine Seite unbemerkt dem falschen Markt zuordnen.
  • Normalisieren Sie, was Sie erfassen. Sobald Sie jede Version haben, unterscheiden sich Preise, Daten und Zahlen weiterhin im Format, wie in Normalisierung von Preisen, Zahlen und Daten über Locales hinweg behandelt, und der Vergleich selbst ist Thema von Erkennung geo-personalisierter Preisgestaltung.

Fazit

hreflang kommt dem nahesten, was das Web an einem veröffentlichten Index der Märkte einer Seite besitzt. Ein Drittel der Top-Sites stellt es bereit, ein einziger Abruf offenbart die gesamte Menge, und der größte Teil der Menge verlinkt sich wie vorgesehen untereinander.

Es ist eine Karte, keine Garantie. Codes können falsch sein, die URL, auf der Sie landen, ist möglicherweise nicht die, die die Menge verwendet, und manche Versionen öffnen sich nur für Besucher am richtigen Ort oder mit der richtigen Sprache. Lesen Sie die Annotationen, validieren Sie sie, und rufen Sie jede Version so ab, wie es ein lokaler Besucher täte.

Quellen und Referenzen

  • Google Search Central, Tell Google about localized versions of your page.
  • Tranco, Liste Q2K34, die Domain-Rankings vom 1. bis 30. September 2026 aggregiert.
  • Babel, dessen CLDR-Daten die Sprach-, Schrift- und Regionslisten im Code liefern.
  • Startseiten und Alternates, abgerufen von Shifter am 1. Oktober 2026, direkt und über Shifter-Exits, mit dem oben genannten Code.

Bereit, loszulegen?

Testen Sie Shifters Residential-Proxys, 205M+ IPs, 195+ Länder, ab 0,75 $/GB.

Jetzt starten