Zum Hauptinhalt springen

Devlog · Phase 0 · 7/7

Mehrsprachiges SEO und GEO: 6 Locales, SSR und KI-Extraktion

Sechs indexierbare URLs pro Sprache, ein bidirektionales BCP47-hreflang, ein per @id verknüpfter JSON-LD-Graph und eine llms.txt: wie aubia.dev sich den Suchmaschinen wie den KIs zu lesen gibt.

Veröffentlicht am 24 August 20265 Min. Lesezeit
  • seo
  • i18n
  • geo
  • inertia

Eine Website vor dem Start hat auf der Sichtbarkeitsseite eine einzige Aufgabe: auffindbar zu sein, in mehreren Sprachen, für die klassischen Suchmaschinen wie für die generativen Maschinen, die inzwischen anstelle der zehn blauen Links antworten. aubia.dev wird in sechs Sprachen ausgeliefert, serverseitig gerendert, mit einer für die Extraktion gedachten Auszeichnung. Dieser letzte Artikel der Serie zerlegt diese Mechanik. Und der Blog, den Sie lesen, läuft auf denselben Regeln.

Sechs Locales, eine URL pro Sprache

Die Website ist unter einem Locale-Präfix geroutet: /fr, /en, /es, /de, /it, /pt. Jede Sprache hat also ihre eigene indexierbare URL, niemals einen Parameter oder ein Cookie, das den Inhalt vor den Bots verbergen würde. Eine Middleware löst die Locale vor dem Routing auf, aus dem URL-Segment, dem Cookie, dann dem Header Accept-Language. Die Wurzel / leitet zur am besten erkannten Sprache weiter.

Für das internationale Publikum außerhalb der abgedeckten Zonen zeigt x-default auf das Englische. Das Rendering erfolgt im SSR über Inertia: Das vollständige HTML geht beim ersten Byte hinaus, was eine nicht verhandelbare Invariante für das SEO ist. Eine Maschine, die eine Seite besucht, erhält ihren gerenderten Inhalt, nicht eine leere Hülle, die auf die JavaScript-Hydratation wartet.

Diese sechs Sprachen haben nicht alle denselben Status, und ich stehe zu dieser Hierarchie. Das Französische ist meine redaktionelle Quelle, von Hand geschrieben. Das Englische ist ein manuell übersetzter Pivot, weil es die Sprache meines internationalen Publikums ist und kein Ungefähres duldet. Spanisch, Italienisch, Deutsch und Portugiesisch werden anschließend automatisch aus diesem englischen Pivot übersetzt. Und die kritischsten Texte, der Titel des Heros, der Claim, die Kernargumente, bleiben von der automatischen Übersetzung ausgeschlossen und gehen stets durch meine Hand.

Hinter diesen sechs Sprachen steht keine fremde i18n-Bibliothek. Die verbreitete npm-Lösung erwies sich als inkompatibel mit React 19 und riskierte, Zustand zwischen den Anfragen unter dem langlebigen Server von Octane durchsickern zu lassen. Die i18n-Engine steckt daher in einem selbst gebauten Hook von etwa fünfzig Zeilen, angebunden an das native Prop-Sharing von Inertia, ohne eine einzige zusätzliche Abhängigkeit.

Das hreflang, in beide Richtungen

Das hreflang sagt einer Maschine: Diese Seite existiert auch in diesen anderen Sprachen, unter diesen genauen URLs. Die Regel, an der die meisten Implementierungen scheitern, ist die Bidirektionalität: Jede Version muss sich selbst referenzieren und alle anderen referenzieren. Ich verwende vollständige BCP47-Codes, präziser als ein kurzer Code, um die Länder-Ambiguität zwischen pt-PT und pt-BR aufzulösen.

Die dynamische Sitemap erzeugt dieses Geflecht für jede veröffentlichte Seite, mit einem Alternativlink pro Locale:

foreach (self::LOCALE_TO_BCP47 as $altLocale => $bcp47) {
    $altLoc = $baseUrl . '/' . $altLocale . $path;
    $xml .= '<xhtml:link rel="alternate" hreflang="' . $bcp47 . '" href="' . $altLoc . '"/>';
}

Auf der Seitenebene setzt die Komponente SeoHead dieselben Signale in den <head>, aber nur auf den indexierbaren Seiten. Die kurzlebigen Seiten, wie die Warteliste-Bestätigung, sind noindex: Ein Canonical oder ein hreflang hinzuzufügen, würde ein widersprüchliches Signal senden.

Ein per @id verknüpfter JSON-LD-Graph

Die strukturierten Daten von Aubia bilden einen einzigen @graph, in dem sich jede Entität über eine stabile @id referenziert: die Organization, die WebSite, die SoftwareApplication und die Gründer-Person zeigen aufeinander. Diese Verdrahtung hilft den Suchmaschinen und den KI-Suchen zu verstehen, dass diese Objekte dieselbe Entität beschreiben und nicht vier zusammenhanglose Dinge.

const jsonLd = JSON.stringify({
    '@context': 'https://schema.org',
    '@graph': [
        { '@type': 'Organization', '@id': `${SITE_URL}/#organization`, /* ... */ },
        { '@type': 'Person', '@id': `${SITE_URL}/#founder`, /* ... */ },
        { '@type': 'WebSite', '@id': `${SITE_URL}/#website`, /* ... */ },
        ...additionalJsonLd,
    ],
});

Der Erweiterungspunkt ist additionalJsonLd: Jede Seite injiziert ihre eigenen Knoten in den geteilten Graphen. Die Landingpage fügt ihren FAQPage-Block hinzu. Ein Blogartikel fügt sein BlogPosting und seinen Brotkrumenpfad hinzu, ohne das Fundament Organization/WebSite zu berühren.

Der animierte JSON-LD-Graph: WebSite, Person und SoftwareApplication über ihre @id mit Organization verknüpft, mit FAQPage und BlogPosting, die pro Seite über additionalJsonLd injiziert werden.

GEO: für die lesenden Maschinen schreiben

Ich habe diese Seiten geschrieben, um zitiert zu werden, nicht nur gerankt: Wenn eine KI anstelle der zehn blauen Links antwortet, kommt es darauf an, dass sie einen ganzen Abschnitt extrahieren kann, ohne ihn zu verzerren. Zwei konkrete Hebel auf aubia.dev.

Der erste ist eine Datei public/llms.txt im Wurzelverzeichnis: eine strukturierte Zusammenfassung des Produkts, seiner Positionierung und seines Status, geschrieben, um von einer Maschine gelesen zu werden. Sie legt dar, was Aubia ist, seinen Unterschied der Cross-review, sein Publikum und seine Roadmap, in reinem Text ohne Oberflächenrauschen.

Der zweite ist eine Schreibregel, angewandt auf diesen Artikel selbst: autarke Abschnitte. Jeder H2-Titel öffnet mit einer Definition und entfaltet sich dann in einem Block von etwa hundertfünfzig Wörtern, der für sich steht, außerhalb des Seitenkontexts. Eine Maschine, die die Seite in Auszüge zerlegt, muss jeden beliebigen Block zitieren können, ohne dass der Sinn einstürzt. Kurze Listen und explizite Titel vollenden die Arbeit.

Die Meta-Schleife: dieser Blog wendet seine eigenen Regeln an

Dieser Blog ist selbst die Demonstration all dessen, was vorangeht. Er ist Flat-File: Jeder Artikel ist eine Markdown-Datei im Repo, beim Parsing in HTML umgewandelt, ohne Datenbank, treu der Grundsatzentscheidung der Phase 0. Einen Artikel zu veröffentlichen heißt, einen Branch zu mergen, mehr nicht.

Die Veröffentlichung wird durch das Datum im Front Matter geplant: Das Modul hält alle Artikel im Cache, filtert aber bei der Anfrage jene heraus, deren Datum vergangen ist. Ein auf einen Montag datierter Artikel erscheint an genau diesem Montag, ohne Redeployment, und bleibt davor unsichtbar: abwesend aus dem Index, dem RSS-Feed, der Sitemap, und mit einem 404 auf seiner URL.

Jeder veröffentlichte Artikel erhält seinen Sitemap-Eintrag mit seinem echten Datum der letzten Änderung, seinen Sprachalternativen, seiner BlogPosting-Auszeichnung und seinem Brotkrumenpfad. Dieselbe Mechanik aus sechs Locales, bidirektionalem hreflang und JSON-LD-Graph, die ich für die Landingpage beschrieben habe, gilt, Artikel für Artikel, für diese Seiten.

Das war der letzte Teil der Serie über den Aufbau der Phase 0. Ich habe die gesamte Phase 0 auf diesem Detailniveau gebaut, und Ihre Rückmeldungen werden über das Weitere entscheiden. Die Warteliste ist der Ort, um es aus der Nähe zu verfolgen.

Dieses Projekt wird hier erzählt, Artikel für Artikel. Kommen Sie an Bord: Ihr Feedback zeichnet den weiteren Weg.

Auf die Warteliste