Erscheinungsbild
0007 — Auszeichnung nur zu sichtbarem Inhalt
Status: angenommen · Milestone: M4
Kontext
Metadaten sind Aussagen über eine Seite, die niemand sieht. Genau deshalb bleiben Fehler dort jahrelang stehen: Ein Canonical, der überall auf die Startseite zeigt, ein hreflang auf eine Sprachfassung, die nie gebaut wurde, eine FAQ-Auszeichnung ohne Fragen auf der Seite — die Website sieht in allen drei Fällen völlig richtig aus.
Die dritte Variante ist nicht nur wirkungslos, sondern gefährlich: Google behandelt Structured Data ohne sichtbare Entsprechung als Verstoß und ahndet sie mit einer manuellen Maßnahme.
Ein Starter, der in zwanzig Kundenprojekten kopiert wird, darf solche Aussagen nicht erlauben, nur weil sie sich leicht erzeugen lassen.
Entscheidung
Die Ausgabe behauptet nichts, was es nicht gibt. Drei Regeln setzen das um:
JSON-LD entsteht ausschließlich aus typisierten Buildern in
src/lib/seo/jsonld.ts, die ihre Werte aus der geprüften Projektkonfiguration oder aus Seiten-Props beziehen. Alle Knoten einer Seite stehen in einem gemeinsamen@graphund verweisen über@idaufeinander.Auszeichnung, die sichtbaren Inhalt beschreibt, entsteht aus derselben Quelle wie dieser Inhalt. Brotkrumen kommen als eine Liste an
BaseLayoutund ergeben beides: den sichtbaren Pfad und dieBreadcrumbList.<Faq />erzeugt aus einer Liste die sichtbaren Klappabschnitte und dieFAQPage. Beides ist konstruktiv nicht zu trennen.hreflangentsteht nur aus deklarierten Übersetzungen. Zwei konfigurierte Sprachen sind noch keine zwei Seiten. Erst wenn eine Seite ihre Übersetzung selbst nennt, entsteht ein Alternate:astro<BaseLayout title="Impressum" translations={{ 'en-GB': '/en/imprint' }} />Eine Sprache, die nicht in
localization.localessteht, bricht den Build ab.
Organization und LocalBusiness sind dabei ein Knoten mit zwei möglichen Typen, nicht zwei Knoten: LocalBusiness ist eine Unterart von Organization, und zwei Knoten für dieselbe Firma würden sie zu zwei Firmen machen.
Begründung
Der gemeinsame Graph statt mehrerer einzelner Skripte, weil Suchmaschinen so erkennen, dass Organisation, Website und Seite dieselbe Sache beschreiben. Drei unverbundene Blöcke sind drei unverbundene Angaben.
Die Kopplung von Anzeige und Auszeichnung ist der Kern. Eine Regel in der Dokumentation („bitte nur auszeichnen, was sichtbar ist") wird gebrochen, sobald es eilig wird. Eine Komponente, die beides aus einer Liste erzeugt, kann nicht gebrochen werden — es gibt keinen Weg, das eine ohne das andere zu bekommen. Der Post-Build-Test prüft zusätzlich in beide Richtungen.
Bei hreflang war die bequeme Alternative naheliegend: aus locales automatisch Alternates nach dem Schema /<sprache>/pfad erzeugen. Das hätte in der Fixture „full" — zwei Sprachen, eine gebaute Sprachfassung — sofort Verweise auf Seiten erzeugt, die es nicht gibt. Genau der Fehler, den niemand bemerkt, weil die Website richtig aussieht.
Verworfene Alternativen
Alle sinnvollen Schemas ausgeben, was schadet es. Es schadet: Auszeichnung ohne Entsprechung ist ein Grund für eine manuelle Maßnahme, und die trifft die ganze Domain.
hreflang automatisch aus localization.locales. Schnell fertig, erzeugt tote Verweise.
Eigenes twitter:title und twitter:description neben Open Graph. X liest die Open-Graph-Angaben, wenn kein eigenes Tag daneben steht. Zwei Tags wären zwei Wahrheiten über dieselbe Seite, von denen eine irgendwann veraltet. Ausgegeben werden nur twitter:card und optional twitter:site.
Konsequenzen
seo.defaultOgImageundseo.logoverweisen auf Schlüssel im Asset-Manifest, nicht auf Pfade. Ein toter Verweis bricht den Build ab (ADR 0005).- Die Builder
articleNode,serviceNodeundfaqPageNodesind vorhanden und einzeln getestet, aber noch an keiner Seite des Starters im Einsatz — es gibt dort keinen passenden sichtbaren Inhalt. Sie kommen mit den Inhaltstypen zum Zug. - Der Post-Build-Test prüft, dass in keiner der drei Ausgaben ein
rel="alternate"steht. Sobald der Starter eine echte Übersetzung mitbringt, ist diese Zusage anzupassen. - JSON-LD wird über
set:htmlausgegeben. Jedes<wird durch seine Unicode-Escape-Form ersetzt, damit im Text kein</script>entstehen kann.