├Ś
Startseite RSS-Feed Info
Suchmaschinen die lokalisierten Versionen der eigenen Webseite bekannt machen.

SEO - Selbstreferenzierender hreflang auf mehrsprachiger Website

SEO - Selbstreferenzierender hreflang auf mehrsprachiger Website

Auf multilingualen Webseiten m├╝ssen im Header alle Sprachversionen, einschlie├člich einer Selbstreferenz und wenn m├Âglich eine x-default Referenz f├╝r eine gute SEO angegeben werden. Den n├Âtigen Hugo Sourcecode daf├╝r erkl├Ąre ich in diesem Beitrag. Mit link rel=“alternate” hreflang="" href="" gelingt dies.

Wichtige Punkte, die bei der Nutzung von alternate Links beachtet werden m├╝ssen:

  • Jede Seite die alternate Links nutzt, ben├Âtigt einen R├╝cklink auf sich selbst mit der entsprechenden Sprache.
  • Jede Seite, auf welche mit einer Sprache verwiesen wird, muss auf die verweisenden Seite mit der entsprechenden Sprache zur├╝ckverweisen (A -> B, B -> A).
  • Alternative Versionen einer Seite mit anderen Sprachen ausweisen.
  • Eine x-default Seite als Standard- oder Fallbackseite ausweisen die Nutzern angezeigt wird, wenn keine andere Sprachvariante passend ist.

Weitere Informationen:

Im Google Search Central Blog hat Google die Aussage ├╝ber hreflang="x-default" etwas konkreter definiert. Bisher hatte ich als x-default Seite die englische Version des Blogbeitrags angegeben. Aus dem einfachen Grund - Englisch wird von mehr Menschen gesprochen als Deutsch. In einem Blogbeitrag von ahrefs.com - siehe oben - wurde in einem x-default Beispiel auch die englische Variante angegeben.

Die x-default-Seite kann eine Sprach- und L├Ąnderauswahlseite sein, die Seite, auf die Sie Benutzer umleiten, wenn Sie keinen Inhalt f├╝r ihre Region haben, oder einfach die Version des Inhalts, die Sie als Standard betrachten.

Im Prinzip h├Ątte ich nichts ├Ąndern m├╝ssen. Ich habe mich aber f├╝r die Sprach- und L├Ąnderauswahlseite entschieden und den Quellcode entsprechend ge├Ąndert. Mehr dazu weiter unten.

Zum Glück habe ich ein kostenloses Konto bei ahrefs.com. Dort wird einmal pro Woche ein kompletter Scan meiner Website durchgeführt. Mein Health Score ist von 98% auf 70% gefallen. Die Änderung des x-default im alternate link hat dies verursacht. Der folgende Fehler wird angezeigt: Missing reciprocal hreflang (no return tag)

Beim Programmieren ist es immer gut, die Bedingungen zu ber├╝cksichtigen, die man selbst zitiert hat. Die Bedingung, dass eine Webseite immer einen “Return-Link” zur aufrufenden Seite enthalten muss, habe ich beim “en/lang-selector/” nicht eingehalten. Da dies auf allen Seiten (deutsch und englisch) der Fall ist, habe ich jetzt 130 Seiten mit wirklich harten Fehlern. Durch viele ├änderungen, die nicht so offensichtlich sind, angefangen bei doFollow Links, bin ich im Google-Index abgerutscht. Damit ich jetzt im Google-Index nicht noch weiter abrutsche, ├Ąndere ich kurzfristig den Sourcecode f├╝r Part 3 wieder auf die jeweilige englische Webseite.

Dies zeige ich jetzt im Blogbeitrag nicht an, da ich an einer L├Âsung arbeite. Die ├änderung im Text erfolgt nach L├Âsung des Problems. ­čžÉ Wer keine Probleme hat, schafft sich eben welche.

Hreflang ist ein einfaches HTML-Attribut, aber es kann schwer zu greifen sein. Googles John Mueller beschrieb hreflang als einen der komplexesten Aspekte von SEO weil es sehr schnell sehr schwierig wird.

Letztendlich ein Sturm im Wasserglas. Dieser Blog behandelt Themen, die nur Webentwickler*innen interessieren. Und das haupts├Ąchlich mit der Beschr├Ąnkung auf den statischen Webseiten-Generator Hugo. Wenn jemand ├╝ber Suchmaschinen auf dieser Website landet, ist er ein Profi oder ein angehender Profi. Profis kennen die Webseiten-├ťbersetzer. Wenn sie sie benutzen wollen, werden sie es selbst tun. 80% meiner Besucher kommen nicht aus dem deutschsprachigen Raum, sondern aus der ganzen Welt.

Will ich Profis, die ├╝ber Suchmaschinen bei mir landen, statt der erwarteten technischen Informationen wirklich erst einmal eine Sprachauswahlseite oder einen Link zum Google-Webseiten├╝bersetzer anzeigen? Wie w├╝rde ich reagieren? Wahrscheinlich w├╝rde ich das Browserfenster schlie├čen und woanders weitersuchen.

Webseiten├╝bersetzer sind technisch faszinierend, aber f├╝r diesen Blog nicht hilfreich. Ich habe den Text des Blogbeitrags wieder auf meine urspr├╝ngliche Version von x-default zur├╝ckgesetzt, mit Verweis auf den englischen Blogbeitrag.

Dennoch habe ich den Quellcode f├╝r die L├Âsung meines urspr├╝nglichen Problems unten angef├╝gt.

Testen ob eine Selbstreferenz vorhanden ist

Der folgende Link stellt ein kostenloses Test Tool f├╝r hreflang alternate Links zur Verf├╝gung. Eine URL eintragen, einen User Agent w├Ąhlen und den Button “TEST URL” klicken.

Oder

im Browser die Source Ansicht ├Âffnen und im head Tag nachschauen ob alle zur Verf├╝gung gestellten Sprachen, einschlie├člich einer Selbstreferenz der aktuellen Seite vorhanden ist.

Wenn die List-Templates mit Paginator nicht w├Ąren k├Ânnte der Sourcecode sehr einfach aufgebaut werden. Da ich aber meine Blogbeitr├Ąge auf der Startseite und in den Tag-Listen mit einem Paginator versehen habe wird der Code doch ganz sch├Ân kompliziert.

Aus didaktischen Gr├╝nden und vielleicht benutzen Sie ja keinen Paginator, zeige ich hier auch die einfache Variante ohne Paginator. Die Links m├╝ssen im head Tag erstellt werden. Das head Tag ist bei bei mir in der themes/tekki/layouts/_default/baseof.html.

Der Sourcecode sieht wie folgt aus:

    {{ if .IsTranslated }}
    {{ range .Translations }}
    <link rel="alternate" hreflang="{{ .Language.Lang }}" href="{{ .Permalink }}">
    {{ end }}
    <link rel="alternate" hreflang="{{ .Language.Lang }}" href="{{ .Permalink }}">
    {{ end }}

Da ich meine Webseiten in deutsch und englisch zur Verf├╝gung stelle, wird im {{ range .Translation }} immer die jeweils andere Sprache gefunden. Danach wird der range verlassen und die Sprache der aktuellen Seite im hreflang ausgegeben. Dadurch wird die sogenannte Selbstreferenz erstellt. Der range funktioniert nat├╝rlich auch bei mehr als 2 Sprachen einer Website. In dieser Version habe ich die hreflang="x-default" Variante nicht ber├╝cksichtigt.

Bei Paginator Pages gibt .Permalink leider nicht die Seitennummer zur├╝ck. Dadurch wird der Aufwand dies manuell zu erm├Âglichen etwas komplizierter. Wenn man diesen Aufwand nicht betreibt, haben alle Paginator Pages bei multilingualen alternate Links die gleiche URL.

Die multilingualen alternate Links m├╝ssen nach dem Einrichten der Paginatoren erstellt werden. Ansonsten kann nicht auf die jeweilige Seitennummer zugegriffen werden. Den Paginator Sourcecode habe ich in dem Beitrag - Hugo - Pagination f├╝r Blogbeitr├Ąge und Tags - erkl├Ąrt. Die Erstellung der URL Seitennummer des canonical Links im Beitrag - Hugo - Canonical Pagination Links f├╝r Blogbeitr├Ąge und Tags .

Der folgende Sourcecode beinhaltet auch das Erstellen der Paginatoren. Die Erkl├Ąrung dazu gebe ich an dieser Stelle nicht.

    {{ $paginator := slice }}
    {{ if .IsHome }}
      {{ $paginator = .Paginate (where .Site.RegularPages.ByLastmod.Reverse "Section" "blog") }}
    {{ else }}
      {{ $paginator = .Paginate .Pages.ByLastmod.Reverse }}
    {{ end }}

{{/* Part 4 - $href for canonical link of the current web page */}}
    {{ $href := .Permalink }}
    {{ with $paginator }}
      {{ if gt .PageNumber 1 }}
        {{ $href = printf "%s%s/%d/" $.Permalink "page" .PageNumber }}
      {{ end }}
    {{ end }}
    <link rel="canonical" href="{{ $href }}">

{{/* Part 1 - No self-referencing web page */}}
    {{ if .IsTranslated }}
      {{ range .Translations }}
        {{ $lang := .Language.Lang }}
        {{ $href := .Permalink }}
        {{ with $paginator }}
          {{ if gt .PageNumber 1 }}
            {{ $href = printf "%s%s/%d/" $href "page" .PageNumber }}
            <link rel="alternate" hreflang="{{ $lang }}" href="{{ $href }}">
          {{ else }}
            <link rel="alternate" hreflang="{{ $lang }}" href="{{ $href }}">
          {{ end }}
        {{ else }}
          <link rel="alternate" hreflang="{{ .Language.Lang }}" href="{{ .Permalink }}">
        {{ end }}
      {{ end }}
    {{ end }}

{{/* Part 2 - Self-referencing web page */}}
    {{ $lang := .Language.Lang }}
    {{ with $paginator }}
        <link rel="alternate" hreflang="{{ $lang }}" href="{{ $href }}">
    {{ else }}
      <link rel="alternate" hreflang="{{ .Language.Lang }}" href="{{ .Permalink }}">
    {{ end }}

{{/* Part 3 - x-default web page */}}
    {{ if and ( ne .Page.Lang "en" ) .IsTranslated }}
      {{ range .Translations }}
          {{ $translation := . }}
          {{ if eq $translation.Lang "en"}}
            {{ $href := .Permalink }}
            {{ with $paginator }}
              {{ if gt .PageNumber 1 }}
                {{ $href = printf "%s%s/%d/" $href "page" .PageNumber }}
                <link rel="alternate" hreflang="x-default" href="{{ $href }}">
              {{ else }}
                <link rel="alternate" hreflang="x-default" href="{{ $href }}">
              {{ end }}
            {{ else }}
              <link rel="alternate" hreflang="x-default" href="{{ .Permalink }}">
            {{ end }}
          {{ end }}
      {{ end }}
    {{ else}}
      {{ $href := .Permalink }}
      {{ with $paginator }}
        {{ if gt .PageNumber 1 }}
          {{ $href = printf "%s%s/%d/" $href "page" .PageNumber }}
          <link rel="alternate" hreflang="x-default" href="{{ $href }}">
        {{ else }}
          <link rel="alternate" hreflang="x-default" href="{{ $href }}">
        {{ end }}
      {{ else }}
        <link rel="alternate" hreflang="x-default" href="{{ .Permalink }}">
      {{ end }}
    {{ end }}

Zur einfacheren Erkl├Ąrung habe ich vier go-Kommentare in den einzelnen Abschnitten eingef├╝gt. Part 4 ist nur f├╝r den Inhalt der Variablen $href im Part 2 wichtig. Dadurch kann ich mir das Erstellen der Seitennummer Links einmal ersparen.

  • Part 1 - erstellt alle NICHT selbstreferenzierenden Links.
  • Part 2 - ist f├╝r die Selbstreferenz der aktuellen Webseite zust├Ąndig.
  • Part 3 - erstellt den hreflang="x-default" Link.
  • Part 4 - Variable $href f├╝r den canonical Link der aktuellen Webseite.

Part 2 - Selbstreferenz der aktuellen Webseite

{{/* Part 2 - Self-referencing web page */}}
    {{ $lang := .Language.Lang }}
    {{ with $paginator }}
        <link rel="alternate" hreflang="{{ $lang }}" href="{{ $href }}">
    {{ else }}
      <link rel="alternate" hreflang="{{ .Language.Lang }}" href="{{ .Permalink }}">
    {{ end }}

Da der Part 2 einfacher aufgebaut ist, erkl├Ąre ich den zuerst. Dieser Teil ist f├╝r die sogenannte Selbstreferenz n├Âtig. Immer wenn ein {{ with $paginator }} im Sourcecode abgefragt wird, beziehen sich die Anweisungen auf List-Templates. In meinem Fall sind damit die Startseite mit den Beitragsteasern und die Tag-Listen gemeint.

Innerhalb des {{ with $paginator }} kann nicht auf {{ .Language.Lang }} zugegriffen werden. Ansonsten produziert Hugo Fehler. Der Scope stimmt einfach nicht. Deshalb die Zuweisung der Sprache (der aktuellen Webseite) an die Variable $lang. Beim zweiten Parameter $href kann ebenfalls nicht auf .Permalink zugegriffen werden. Der Inhalt von $href stammt noch aus Part 4. Im Part 1 und 3 wird $href mit einem eigenen Scope neu gef├╝llt. Aus diesem Grund ist dieser Abschnitt noch recht ├╝bersichtlich.

Im else vom {{ with $paginator }} welcher au├čerhalb des Paginator Scopes liegt, kann ganz normal auf {{ .Language.Lang }} und {{ .Permalink }} zugegriffen werden.

{{/* Part 1 - No self-referencing web page */}}
    {{ if .IsTranslated }}
      {{ range .Translations }}
        {{ $lang := .Language.Lang }}
        {{ $href := .Permalink }}
        {{ with $paginator }}
          {{ if gt .PageNumber 1 }}
            {{ $href = printf "%s%s/%d/" $href "page" .PageNumber }}
            <link rel="alternate" hreflang="{{ $lang }}" href="{{ $href }}">
          {{ else }}
            <link rel="alternate" hreflang="{{ $lang }}" href="{{ $href }}">
          {{ end }}
        {{ else }}
          <link rel="alternate" hreflang="{{ .Language.Lang }}" href="{{ .Permalink }}">
        {{ end }}
      {{ end }}
    {{ end }}

Wenn f├╝r die aktuelle Webseite ├ťbersetzungen in andere Sprachen vorliegen - {{ if .IsTranslated }} dann werden diese im {{ range .Translation }} abgearbeitet.

Damit innerhalb von {{ with $paginator }} auf die Sprache und den noch zu erweiternden Permalink (wegen dem fehlenden Scope) zugegriffen werden kann, werden diese Daten in Variablen zwischengespeichert.

Der ersten Webseite einer Paginator Liste wei├čt Hugo keine Seitennummer zu. Aus diesem Grund darf der Seite 1 im else Zweig von {{ if gt .PageNumber 1 }} keine Seitennummer zugewiesen werden.

Wenn die Seitennummer gr├Â├čer als 1 ist, wird die Variable $href mit der Seitennummer erg├Ąnzt.

Der else Zweig von {{ with $paginator }} ist f├╝r alle ├╝bersetzten NICHT Paginator Webseiten zust├Ąndig. Da au├čerhalb des $paginator Scopes, kann in diesem Fall wieder auf .Language.Lang und .Permalink zugegriffen werden.

Dieser Link ist leider noch aufw├Ąndiger zu erstellen. Wenn keine andere Sprachvariante passend ist, legt die Standard- oder Fallbackseite fest in welcher Sprache die Seite den Nutzern angezeigt wird. Da englisch eher als deutsch auf unserem Planeten verstanden wird, m├Âchte ich die englische Variante der Webseite anzeigen.

{{/* Part 3 - x-default web page */}}
    {{ if and ( ne .Page.Lang "en" ) .IsTranslated }}
      {{ range .Translations }}
          {{ $translation := . }}
          {{ if eq $translation.Lang "en"}}
            {{ $href := .Permalink }}
            {{ with $paginator }}
              {{ if gt .PageNumber 1 }}
                {{ $href = printf "%s%s/%d/" $href "page" .PageNumber }}
                <link rel="alternate" hreflang="x-default" href="{{ $href }}">
              {{ else }}
                <link rel="alternate" hreflang="x-default" href="{{ $href }}">
              {{ end }}
            {{ else }}
              <link rel="alternate" hreflang="x-default" href="{{ .Permalink }}">
            {{ end }}
          {{ end }}
      {{ end }}
    {{ else}}
      {{ $href := .Permalink }}
      {{ with $paginator }}
        {{ if gt .PageNumber 1 }}
          {{ $href = printf "%s%s/%d/" $href "page" .PageNumber }}
          <link rel="alternate" hreflang="x-default" href="{{ $href }}">
        {{ else }}
          <link rel="alternate" hreflang="x-default" href="{{ $href }}">
        {{ end }}
      {{ else }}
        <link rel="alternate" hreflang="x-default" href="{{ .Permalink }}">
      {{ end }}
    {{ end }}

Mit der Bedingung {{ if and ( ne .Page.Lang "en" ) .IsTranslated }} stelle ich fest, ob die Sprache der aktuellen Webseite deutsch ist und ├╝bersetzt wurde. Der else Zweig ist f├╝r englisch zust├Ąndig.

Wenn die Sprache der aktuellen Webseite deutsch ist, frage ich mit {{ range .Translations }} die anderen Sprachen ab. In meinem Fall ist dies nur englisch.

Mit {{ if eq $translation.Lang "en" }} stelle ich fest ob die Sprache der Webseite englisch ist. Danach wiederholt sich der Paginator Teil von oben mit dem Unterschied, dass hreflang="x-default" ist.

Meine NICHT benutzte L├Âsung f├╝r das x-default Problem

Das Problem der Sprach- und L├Ąnderauswahlseite en/lang-selector/ bestand darin, dass nicht alle “Return-Links” zu den aufrufenden Webseiten gesetzt wurden.

{{/* Part 3 - x-default web page */}}
    {{ $u := urls.Parse .Permalink }}
    {{ if eq $u.RequestURI "/en/lang-selector/" }}
      {{ range .Site.AllPages }}
        {{ if ne .Params.sitemap_exclude true }}
          {{ $u := urls.Parse .Permalink }}
          {{ if ne $u.RequestURI "/en/lang-selector/" }}
            {{ $u := urls.Parse .Permalink }}
            {{ if ne $u.RequestURI "/lang-selector/" }}
              <link rel="alternate" hreflang="{{ .Language.Lang }}" href="{{ .Permalink }}">
            {{ end }}
          {{ end }}
        {{ end }}
      {{ end }}
    {{ else if ne $u.RequestURI "/lang-selector/" }}
      {{ $href = printf "%s%s" (absURL "") "en/lang-selector/" }}
      <link rel="alternate" hreflang="x-default" href="{{ $href }}">
    {{ end }}

Mit - urls.Parse - erstelle ich eine URL-Struktur. Dann frage ich mit {{ if eq $u.RequestURI "/en/lang-selector/" }} ab, ob die aktuell bearbeitete Webseite in der baseof.html die Seite /en/lang-selector/ ist.

Wenn ja, schlie├če ich mit {{ if ne .Params.sitemap_exclude true }} Seiten aus, die ich nicht angezeigt haben m├Âchte. {{ if ne $u.RequestURI "/en/lang-selector/" }} verhindert eine doppelte Eingabe auf dieser Seite, da diese Selbstreferenz bereits durch Part 2 erfolgt ist. {{ if ne $u.RequestURI "/lang-selector/" }} schlie├čt die deutsche Version der Seite aus, da sie nirgendwo referenziert wird und der Verweis schon in Part 1 durchgef├╝hrt wurde. Alle anderen Seiten sollen einen Verweis auf /en/lang-selector/ erhalten.

Wenn nein und die aktuelle Seite ist nicht die deutsche Version von /lang-selector/ wird f├╝r alle anderen Webseiten ein “Return-Link” auf /en/lang-selector/ gesetzt. Das Ergebnis sieht wie folgt aus:

Return-Link im /en/lang-selector/
Return-Links auf der Webseite /en/lang-selector/ zu den aufrufenden Seiten.

An dieser Stelle ist mir bewu├čt geworden, dass die Sprach- und L├Ąnderauswahlseite keine gute Option f├╝r meinen Blog ist. Was f├╝r diese Version noch fehlt ist der x-default f├╝r die Paginator Seiten oberhalb der pageNumber 1. Es gibt keine andere M├Âglichkeit als f├╝r diese Seiten Teile von Part 3 zu benutzen. Der Verweis f├╝r diese Seiten kann nur auf die englische Seite als x-default erstellt werden. Dadurch wird kein “Return-Link” f├╝r die /en/lang-selector/ Seite ben├Âtigt.

Fazit

Die Selbstreferenz vergisst man schnell. Ist mir auch so gegangen. Die x-default Webseite ist kein muss, sollte aber laut - Google - angezeigt werden. Kompliziert wird die ganze Sache durch die Paginator Webseiten. Das dies ├╝berhaupt so funktioniert muss man Hugo hoch anrechnen. Man vergisst leicht, das Hugo ein statischer Seitengenerator ist. In dem Moment wo die Webseiten generiert werden muss alles zur Verf├╝gung stehen. Bei anderen Programmiersprachen kann man w├Ąhrend der Laufzeit immer noch den ausgegebenen HTML-Code manipulieren und dadurch auf ├änderungen reagieren.

Linkliste zu diesem Beitrag

Das k├Ânnte Sie auch interessieren

Update:  |
14 Minuten Lesezeit
0
Dieser Beitrag wurde mit der Hugo-Version 0.112.3 erstellt.

Kommentare werden bei deutscher Spracheinstellung nicht in der englischen Variante der Webseite angezeigt und umgekehrt.

© 2023 - Frank Kunert  -  Ich ├╝ber mich