Devlog · Phase 0 · 7/7
Mehrsprachiges SEO auf aubia.dev: URLs, hreflang und JSON-LD
Die sechs Sprachen von aubia.dev haben eigene URLs und hreflang-Links. Das ausgelieferte HTML verknüpft Artikel mit ihrem Autor, garantiert aber weder Indexierung noch Zitate durch KI.
- seo
- i18n
- geo
- inertia

Ein Aufruf von /fr/blog/multilingual-seo-geo muss diesen Artikel auf Französisch zurückgeben, selbst wenn der Browser Englisch bevorzugt. Auch das Sprach-Cookie darf den Inhalt unter dieser URL nicht verändern. Damit ich eine Übersetzung teilen oder indexieren lassen kann, muss ihre Adresse immer dieselbe Sprache bezeichnen.
Die öffentlichen Seiten von aubia.dev verwenden sechs Präfixe: /fr, /en, /es, /de, /it und /pt. Die Slugs bleiben in allen Versionen auf Englisch. Das Präfix reicht aus, um die Versionen zu unterscheiden.
Das Server-Rendering von Inertia stellt den Inhalt und die Metadaten in der initialen HTML-Antwort bereit. Ein Crawler kann sie lesen, ohne das JavaScript der Seite auszuführen. Voraussetzung ist, dass SSR funktioniert: Schlägt es fehl, erlaubt die Konfiguration der Website einen Rückfall auf clientseitiges Rendering. Der Inhalt hängt dann von JavaScript ab, wie der Artikel zu diesem Stack im Detail erklärt.
Die Sprache der URL und die des Browsers
Die Middleware SetLocale wählt die Sprache vor dem Rendering aus. Sie prüft das URL-Segment, dann das Präferenz-Cookie, den Accept-Language-Header des Browsers und schließlich die Anwendungskonfiguration. Sie verwendet den ersten erkannten Wert.
Auf einer Seite unter /fr hat das URL-Segment somit immer Vorrang. Unter / gibt es kein Sprachsegment: Das Cookie, die Browsereinstellungen und die Standardsprache Englisch bestimmen das Ziel der Weiterleitung.
Beim Lesen von Accept-Language geht der Server die Sprachen in der Reihenfolge ihrer Präferenz durch und vergleicht ihre ersten beiden Buchstaben mit den sechs unterstützten Sprachen. Hat kein Cookie Vorrang, kann ein Browser, der pt-BR anfordert, daher nach /pt weitergeleitet werden.
Die Root-URL antwortet mit einer 302-Weiterleitung. Eine 301 würde einen dauerhaften Umzug anzeigen, obwohl das Ziel vom Besucher und seiner aktuellen Präferenz abhängt.
Eine Antwort, die von der Anfrage abhängt
Ein gemeinsam genutzter Cache darf eine Weiterleitung ins Französische nicht für einen Besucher wiederverwenden, der Italienisch anfordert. Der Vary-Header gibt an, welche Anfrage-Header die Antworten voneinander unterscheiden.
Inertia setzt Vary: X-Inertia, um seine HTML- und JSON-Antworten zu trennen. Seine Middleware ersetzt diesen Header. Deshalb ergänzt SetLocale die sprachbezogenen Werte, nachdem diese Middleware die Antwort zurückgegeben hat, und zwar nur auf der Weiterleitungsroute:
if ($request->routeIs('home.redirect')) {
$response->headers->set('Vary', ['Cookie', 'Accept-Language'], replace: false);
}
Die Antwort enthält dann X-Inertia, Cookie und Accept-Language. Die lokalisierten Seiten behalten Inertias Vary bei, ohne Ergänzungen zur Sprachaushandlung: Ihre URL bestimmt die Sprache bereits.
Dieser Mechanismus hängt vom Cache ab, der die Antwort erhält. Cloudflare berücksichtigt standardmäßig nicht alle Vary-Werte. Das Senden des Headers allein beweist daher nicht, dass das CDN diese Varianten unterscheidet; seine Cache-Regeln müssen mit den ausgelieferten Antworten vereinbar sein.
Hreflang verknüpft Übersetzungen
Eine eigene URL pro Sprache macht jede Version unabhängig zugänglich. Die hreflang-Annotation gibt Suchmaschinen an, welche Seiten Übersetzungen voneinander sind.
Jede Version listet ihre eigene URL und die der anderen verfügbaren Sprachen auf. Google verlangt diese gegenseitige Verlinkung: Verweisen zwei Seiten nicht aufeinander, können die Annotationen für dieses Paar ignoriert werden. Paare mit gegenseitigen Verweisen können weiterhin verarbeitet werden.
Ich verwende reine Sprachcodes: fr, en, es, de, it und pt. Die Website bietet eine portugiesische Version für Portugiesischsprachige an, keine getrennten Inhalte für Portugal und Brasilien. pt beschreibt diese Entscheidung; pt-PT würde portugiesischsprachige Inhalte für Portugal kennzeichnen.
Die Annotation informiert die Suchmaschine über die Zielgruppe. Die HTTP-Weiterleitung der Root-URL wählt das Ziel eines Besuchs anhand der empfangenen Anfrage aus.
Eine Fallback-Version
Die Annotation x-default kennzeichnet eine Fallback-Version für nicht abgedeckte Sprachen oder Regionen. Auf aubia.dev verweist sie auf Englisch. Bei einem Artikel, dessen englische Übersetzung nicht veröffentlicht ist, entfällt sie: Eine URL mit Status 404 wäre kein sinnvolles Fallback-Ziel.
Die Annotationen des Blogs beschränken sich auf veröffentlichte Übersetzungen desselben Slugs. Ein Artikel, der in drei Sprachen verfügbar ist, listet diese drei Versionen auf, auch wenn die übrige Website sechs Sprachen unterstützt.
Die Komponente SeoHead schreibt diese Links in den <head>. Die Sitemap erzeugt sie ebenfalls anhand der veröffentlichten Sprachen. Beide Methoden sind für Google gleichwertig; ihre Kombination bringt keinen Ranking-Vorteil. Beide zu pflegen bedeutet vor allem, ihre Ziele konsistent zu halten.
Eine kanonische URL für jede Sprache
Der canonical-Link beantwortet eine andere Frage: Welche URL soll unter identischen oder sehr ähnlichen Seiten bevorzugt werden? Eine vollständige Übersetzung ist nicht allein deshalb ein Duplikat des Originals, weil sie dasselbe Thema in einer anderen Sprache behandelt.
Jede Version eines Artikels deklariert deshalb ihre eigene kanonische URL. Die französische Seite benennt nicht die englische als kanonisch. Google empfiehlt ein Ziel in derselben Sprache, sofern eines existiert, und behält die endgültige Entscheidung über die ausgewählte URL.
Die Bestätigungs- und Abmeldeseiten verwenden noindex,nofollow. Die Website fügt ihnen weder Canonical- noch hreflang-Links hinzu und schließt sie aus der Sitemap aus. So bleiben die Sprachversions-Annotationen auf Seiten beschränkt, die zur Indexierung bestimmt sind.
Open Graph verwendet ein anderes Format: og:locale ist beispielsweise fr_FR. Die Open-Graph-Spezifikation definiert für diese optionale Eigenschaft das Format language_TERRITORY. Dieses Format mit Gebietsangabe lässt sich nicht auf die Zielgruppenangaben von hreflang übertragen.
Sprachlinks vor der Ausführung von JavaScript
Die Sprachauswahl enthält echte Links zu den anderen Versionen der Seite. Google empfiehlt diese Links zusätzlich zu den hreflang-Annotationen, damit Besucher selbst wählen können.
Die Komponente verwendet die nativen HTML-Elemente details und summary. Die Anker werden auch bei geschlossenem Panel gerendert. Mit SSR sind sie daher im empfangenen HTML vorhanden, ohne dass erst ein Klick sie dem Dokument hinzufügen muss.
Der Browser übernimmt das Öffnen per Maus oder Tastatur. Ein React-Effekt ergänzt das Schließen mit Escape und bei einem Klick außerhalb. Ein Menü, das seinen Inhalt erst beim Öffnen mountet, würde diese Links im initialen Rendering nicht bereitstellen. Diese Einschränkung hängt vom Verhalten der Komponente ab, nicht allein von einem React-Portal.
Die Ziele kommen vom Server. Auf einer Artikelseite sind sie wie die hreflang-Links auf veröffentlichte Übersetzungen beschränkt. Die Sprachauswahl bietet keine Sprache an, in der der Artikel fehlt.
Ein identifizierter Autor im JSON-LD-Graphen
Die Komponente SeoHead beschreibt drei gemeinsame Entitäten in JSON-LD: die Organisation Aubia, mich als Person und die Website. JSON-LD verwendet hier das Vokabular von schema.org, um Typen und Beziehungen zu benennen.
Jede Entität hat eine stabile @id. Verweise auf diese Kennungen deklarieren eine Beziehung, ohne das ganze Objekt zu wiederholen. Der Graph enthält die folgenden Verknüpfungen, hier in einem auf Typen, Kennungen und Beziehungen beschränkten Ausschnitt:
{
"@context": "https://schema.org",
"@graph": [
{
"@type": "Organization",
"@id": "https://aubia.dev/#organization",
"founder": { "@id": "https://aubia.dev/#founder" }
},
{
"@type": "Person",
"@id": "https://aubia.dev/#founder",
"worksFor": { "@id": "https://aubia.dev/#organization" }
},
{
"@type": "WebSite",
"@id": "https://aubia.dev/#website",
"publisher": { "@id": "https://aubia.dev/#organization" }
}
]
}
Die Seiten ergänzen ihre Daten über additionalJsonLd. Die Startseite liefert eine FAQPage mit den sichtbaren Fragen und Antworten. Ein Artikel fügt ein BlogPosting und eine BreadcrumbList hinzu, seine Breadcrumb-Navigation.
Im BlogPosting verweist author auf #founder und publisher auf #organization. Der Artikelautor wird damit als dieselbe Person deklariert wie der Gründer der Website. Ein Objekt zu @graph hinzuzufügen erzeugt für sich genommen keine Beziehung zu allen anderen Objekten: Die Eigenschaften und ihre Verweise beschreiben diese Beziehungen.
Die sichtbare Autorenzeile jedes Artikels verlinkt auf dasselbe öffentliche Profil wie der Knoten Person. Die Open-Graph-Metadaten deklarieren den Typ article sowie das Veröffentlichungs- und Änderungsdatum.
Der Graph enthält weder SoftwareApplication noch ein Vorbestellungsangebot: Die Website bietet eine Warteliste, keinen Softwaredownload. Rezensionen oder Bewertungen werden nicht erfunden, um ein Rich-Suchergebnis zu erhalten. Die sichtbare FAQ behält ihr Markup mit der URL der lokalisierten Seite als Kennung, aber Google zeigt seit dem 7. Mai 2026 keine FAQ-Rich-Suchergebnisse mehr an. Diese Angaben garantieren weder ihre Nutzung durch eine Suchmaschine noch die Indexierung.
GEO, ohne Garantie auf Zitierung
Der Begriff GEO, kurz für Generative Engine Optimization, bezeichnet Praktiken, die die Sichtbarkeit von Inhalten in KI-generierten Antworten verbessern sollen. Er steht nicht für ein gemeinsames Protokoll, das sicherstellt, dass eine Seite gelesen, ausgewählt oder zitiert wird.
Um als Quellenlink in AI Overviews oder AI Mode von Google Search zu erscheinen, muss eine Seite von Google indexiert und für die Anzeige mit einem Snippet geeignet sein. Ein spezielles schema.org-Markup ist nicht erforderlich. Selbst eine Seite, die diese Bedingungen erfüllt, hat keine Garantie, als Quelle zu erscheinen.
Ich versuche, Abschnitte zu schreiben, die verständlich sind, ohne den gesamten Artikel noch einmal lesen zu müssen. Das hilft Lesern, einer Erklärung zu folgen und eine Passage zu zitieren, garantiert aber nicht, dass eine KI sie getreu wiedergibt.
Eine llms.txt-Datei, die Pflege braucht
Die Website liefert auch eine llms.txt-Datei aus. Sie enthält eine Produktübersicht in Markdown und Links zu öffentlichen Seiten. Eine Benutzeroberfläche muss dafür nicht bedient werden. Die Datei orientiert sich am llms.txt-Vorschlag, der von den Crawl-Regeln in robots.txt getrennt ist.
Diese Datei wird unabhängig von der Website und ihren Artikeln geschrieben. Ihre Aussagen und Links müssen deshalb überprüft werden, wenn sie sich an anderer Stelle ändern. Ihre Existenz beweist weder, dass eine Suchmaschine sie liest, noch, dass sie das Ranking oder die Zitierungen der Website verbessert.
Übersetzungen veröffentlichen
Für jeden Blogartikel gibt es eine Markdown-Datei pro Sprache. Sein Veröffentlichungsdatum wird aus dem Front Matter in UTC gelesen. Der Build kompiliert Markdown und Phiki-Hervorhebung in einen lokalen Katalog, der in Produktion vorgeschrieben ist und auch künftige Artikel enthält. Die Daten werden bei jedem Lesen geprüft: Vor dem Veröffentlichungsdatum gibt die URL 404 zurück; sobald es erreicht ist, wird der Artikel ohne weiteres Deployment zugänglich. Ein fehlender, ungültiger oder veralteter Katalog führt auf den davon abhängigen Routen zu einer 503-Antwort, ohne Phiki während der Anfrage auszuführen.
Der Index listet ihn auf, und der RSS-Feed veröffentlicht seinen Vorspann mit dem Veröffentlichungsdatum. Die Sitemap verwendet für lastmod das Aktualisierungsdatum oder, wenn keines angegeben ist, das Veröffentlichungsdatum. Sie ergänzt hreflang-Annotationen für die verfügbaren Übersetzungen. Die Seite selbst liefert das BlogPosting und die Breadcrumb-Navigation.
Der RSS-Feed und die Sitemap werden ohne Sessions und Cookies ausgeliefert und geben eine HTTP-Cache-Dauer von einer Stunde an. Ihre Aktualisierung in einem Zwischencache oder Feedreader kann sich gegenüber der Freigabe des Artikels in der Anwendung verzögern. Der lastmod-Wert jedes Blogindex entspricht dem neuesten Änderungs- oder Veröffentlichungsdatum seiner veröffentlichten Artikel. Statische Seiten deklarieren kein unbekanntes Datum. Der <head> der Blogseiten enthält außerdem einen Link zur Erkennung des lokalisierten RSS-Feeds.
Oberflächentexte folgen einem anderen Ablauf
Die Texte der Benutzeroberfläche werden in lang/<locale>.json-Dateien gespeichert. Französisch ist die Ausgangssprache, Englisch die Zwischensprache für Spanisch, Deutsch, Italienisch und Portugiesisch.
Wird eine Änderung an der französischen Datei committet, sendet der Hook dem Übersetzungsagenten die hinzugefügten oder geänderten Schlüssel, die nicht ausgeschlossen sind. Er führt die zurückgegebenen Werte mit der englischen Datei zusammen und behält die übrigen bei. Löschungen von Schlüsseln werden getrennt angewendet. Die Übertragung vom Englischen in die vier anderen Sprachen wird durch einen manuellen Befehl ausgelöst.
Drei Präfixe sind von automatischen Ergänzungen und Änderungen ausgeschlossen: usp.*, features.tagline.* und letter.body.*. Für die ersten beiden gibt es in den aktuellen Wörterbüchern keine Schlüssel. Das dritte schützt den Text des Briefs des Gründers. Der Claim und der Titel des Startseitenbanners gehören nicht zu diesen Ausnahmen und kommen daher für die automatische Übersetzung infrage.
Der Server lädt die Übersetzungen für die angeforderte Sprache und ergänzt fehlende Schlüssel mit Englisch. Der React-Hook useT() liest anschließend die über Inertia-Props bereitgestellten Werte. Artikel durchlaufen dieses Skript nicht: Ihre Markdown-Dateien werden separat übersetzt.
Der Text des Briefs existiert nur in den französischen und englischen Wörterbüchern. Auf Spanisch, Deutsch, Italienisch und Portugiesisch erscheinen seine Absätze daher auf Englisch, obwohl die umgebenden Überschriften und Beschriftungen übersetzt sind. Ihr Container deklariert lang="en": Diese Annotation kennzeichnet ihre Sprache, ohne sie zu übersetzen.
Dieses Projekt wird hier erzählt, Artikel für Artikel. Was als Nächstes kommt, hängt davon ab, was Sie dazu sagen.
Auf die Warteliste