Der Canonical-Fehler, der Ihre Übersetzungen versteckt

agentready.md erscheint in zehn Sprachen mit einem Präfix im Pfad. Jede Seite gibt es als /about, /es/about, /ja/about und sieben weitere. Monatelang lieferten alle neun Übersetzungen dieselbe Zeile im <head> aus:

<link rel="canonical" href="https://agentready.md/about">

/es/about sagte den Suchmaschinen: Ich bin ein Duplikat der englischen Seite, indexiert lieber die. /ja/about sagte dasselbe. Die anderen sieben auch. Zehn Sprachen, eine davon indexierbar.

Was ein Canonical tatsächlich aussagt

rel="canonical" ist kein Hinweis darauf, welche URL am aufgeräumtesten aussieht. Es ist eine Behauptung: Diese Seite und die genannte sind dieselbe, und die genannte gehört in den Index. Links, Ranking-Signale und der Indexeintrag selbst werden dem Ziel gutgeschrieben. Diese Seite tritt nicht mehr an.

Genau das will man, wenn /produkt?farbe=blau auf /produkt zeigt. Genau das will man bei einer Übersetzung nicht. Eine spanische Seite ist kein Duplikat der englischen: Sie ist eine andere Seite für einen anderen Leser und soll für spanische Suchanfragen eigenständig ranken.

Warum der Fehler so lange überlebt hat

Unser Router entfernt das Sprachpräfix vor dem Routing, eine Anfrage an /es/about erreicht den Handler also als /about, und die Sprache reist getrennt mit. Das ist ein vernünftiger Entwurf, weil Routen einmal deklariert werden und in jeder Sprache funktionieren, aber request.url enthält das Präfix nicht mehr, und das <head>-Partial baute den Canonical direkt aus diesem nackten Pfad.

Auf Englisch liefert der Bug das richtige Ergebnis, weil Englisch kein Präfix hat. Alle Seiten, die man beim Entwickeln in der eigenen Sprache ansieht, sind korrekt. Kaputt ist es nur in den neun, die man nicht liest.

Die Korrektur ist ein Aufruf, der das Präfix vor der Ausgabe zurückgibt:

<%# request.url hat das Präfix /es/ bereits verloren — localizedUrl setzt es zurück. %>
<% const canonicalHref = localizedUrl(canonicalPath); %>
<link rel="canonical" href="<%= baseUrl %><%= canonicalHref %>">

Die Regel: Jede Sprachversion ist ihr eigener Canonical

Selbstreferenzierender Canonical, immer. /es/about deklariert /es/about. /ja/about deklariert /ja/about. Für Übersetzungen gibt es keine Ausnahme. Und wenn zwei Sprachversionen sich stark ähneln, dieselbe Produktseite mit drei geänderten Wörtern, ändert das nichts: Sprache ist keine Duplizierung.

hreflang: Jede Version listet jede Version

Der Canonical sagt, welche Seite indexiert wird. hreflang sagt, welche der indexierten Seiten welchem Leser gezeigt wird. Man braucht beides, und zwei Regeln von hreflang werden regelmäßig übersehen.

Jede Version listet jede Version, sich selbst eingeschlossen. Zehn Sprachen bedeuten zehn alternate-Links auf allen zehn Seiten, Selbstreferenz inklusive. Die fehlende Selbstreferenz ist der häufigste Defekt in einem ansonsten korrekten Satz.

Die Verweise müssen wechselseitig sein. Nennt die englische Seite die spanische und die spanische die englische nicht zurück, verwirft die Suchmaschine die Beziehung, oft den ganzen Satz, weil sie nicht überprüfbar ist. Jeder kann behaupten, die Übersetzung von jemandem zu sein. Bestätigen kann es nur die andere Seite.

Erzeugt sie aus einer Liste, statt zehn zu pflegen:

<% ['en','es','fr','de','pt','it','ja','zh','ko','ru'].forEach(l => { %>
<link rel="alternate" hreflang="<%= l %>" href="<%= baseUrl %><%= localizedUrl(canonicalPath, l) %>">
<% }) %>
<link rel="alternate" hreflang="x-default" href="<%= baseUrl %><%= canonicalPath %>">

Nehmt den reinen Sprachcode (es), wenn eine Version alle Sprecher bedient. Eine Region (es-ES, pt-BR) kommt nur dazu, wenn ihr wirklich getrennte Versionen pro Markt veröffentlicht; erfundene Regionen zersplittern den Satz ohne Gegenwert.

x-default ist keine weitere Übersetzung

x-default benennt die Version für Leser, deren Sprache ihr nicht veröffentlicht. Jemand sucht auf Niederländisch, ihr habt kein Niederländisch: Er bekommt das, worauf x-default zeigt.

Es ist nicht „die wichtigste Sprache“ und kein weiterer Eintrag in der Reihe. Unser x-default bleibt auf der englischen URL ohne Präfix, dem Rückfall, auf den die ganze Site ohnehin zurückfällt. Zeigt er auf eine sprachgebundene Seite, /es/about, schickt ihr jeden Leser ohne passende Übersetzung auf eine spanische Seite, und das ist schlechter als der Standard.

Die Prüfung in einer Zeile

Der ganze Fehler ist auf der Kommandozeile sichtbar. Lauft über eure Sprachen und gebt aus, was jede Seite behauptet:

for p in "" es/ fr/ de/ pt/ it/ ja/ zh/ ko/ ru/; do
  printf '%-4s ' "${p:-en}"
  curl -s "https://beispiel.com/${p}about" | grep -o '<link rel="canonical"[^>]*>'
done

Korrekte Ausgabe nennt in jeder Zeile den eigenen Pfad:

en   <link rel="canonical" href="https://beispiel.com/about">
es   <link rel="canonical" href="https://beispiel.com/es/about">
fr   <link rel="canonical" href="https://beispiel.com/fr/about">

Zehn Zeilen mit derselben URL sind der Bug aus diesem Artikel. Lasst das gegen die geänderten Templates laufen, nicht nur gegen die Startseite: Canonicals entstehen meist pro Layout, und eine Site mit drei Layouts kann in zweien richtig liegen.

Die anderen Arten, das kaputtzumachen

Tracking-Parameter im Canonical. Wer den Canonical aus der rohen Request-URL baut, liefert Besuchern, die über ?utm_source=newsletter kommen, einen Canonical auf ?utm_source=newsletter. Jedes Teilen erzeugt eine neue URL, die sich selbst zum Canonical erklärt, also eine unbegrenzte Menge URLs für eine einzige Seite. Baut ihn aus dem Pfad und nehmt nur die Parameter zurück, die wirklich eine andere Seite bezeichnen, eine gefilterte Liste oder eine Paginierung, in fester Reihenfolge, damit eine Seite ihren eigenen Namen nicht auf zwei Arten schreiben kann.

Canonical auf eine Weiterleitung. Deklariert /es/about die URL /es/about/ und die leitet per 301 zurück auf /es/about, habt ihr dem Crawler eine Schleife zum Auflösen und einen Grund zum Misstrauen gegeben. Zeigt auf die URL, die 200 liefert.

Zwei widersprüchliche Canonicals. Der <link> im HTML und ein HTTP-Header Link: <...>; rel="canonical" sind beide gültig, und CDNs, Plugins und Reverse Proxies setzen sie gern. Widersprechen sie sich, ist das Ergebnis undefiniert und meist nicht das gewünschte. Prüft also auch die Header:

curl -sI https://beispiel.com/es/about | grep -i "^link:"

Nichts zu sehen ist die richtige Antwort, außer ihr habt den Header absichtlich gesetzt.

Website analysieren · Meta-Tags-Generator · KI-Readiness-Checkliste