Tutorial: JSON-LD, das Suchmaschinen wirklich lesen

Inhalt
Strukturierte Daten haben keinen Fehlerzustand, den jemand bemerkt. Eine Seite mit einem relativen Bildpfad, einem Datum in deutscher Schreibweise oder einem Preis mit Währungszeichen wird genauso ausgeliefert wie eine richtige, platziert sich genauso wie eine richtige und bekommt das Rich Result, für das sie geschrieben wurde, einfach nie. Die Auszeichnung ist da, der Crawler liest sie, und es passiert nichts.
Damit sind strukturierte Daten eher ein Wartungs- als ein Schreibproblem. Die Fehler sind mechanisch, sie wiederholen sich über jede Seite, die aus derselben Vorlage entsteht, und ein einziges falsches Zeichen in einem Feld, das kein Besucher je sieht, entscheidet darüber, ob ein Eintrag eine Bewertung zeigt oder einen nackten blauen Link. Was folgt, ist die Kette, die eine Auszeichnung durchlaufen muss, und die vier Stellen, an denen sie üblicherweise herausfällt.

Die zwei Zeilen, an denen alles hängt
Bevor eine einzige Eigenschaft gelesen wird, entscheiden zwei Felder darüber, ob der Block überhaupt angesehen wird. @context muss auf https://schema.org zeigen; ohne diese Angabe ist das Objekt ein Stück JSON ohne Vokabular, und jede Eigenschaft darin bedeutet nichts. @type benennt anschließend die beschriebene Sache. Dabei zählt die Schreibweise: Article ist ein Typ, article ist keiner, und der Unterschied erzeugt nirgends eine Warnung.
Wo mehrere Objekte zu einer Seite gehören – ein Artikel, sein Autor und der Brotkrumenpfad – können sie in einem Block unter @graph stehen oder in getrennten Skriptelementen. Beides geht. Was nicht geht, sind zwei Blöcke, die dieselbe Sache unterschiedlich beschreiben, denn der Auswerter hat keine Regel dafür, welcher von beiden gemeint ist.
Vier Fehler, die keine Fehlermeldung erzeugen
Auf diese vier geht der größte Teil jener Auszeichnung zurück, die gültig ist und trotzdem nichts zeigt. Sie haben eine Eigenschaft gemeinsam: das betroffene Feld ist für Besucher unsichtbar, deshalb fällt niemandem auf, dass es falsch ist.
| So geschrieben | Was geschieht | Was es sein muss |
|---|---|---|
"image": "/img/a.jpg" |
Der Pfad wird nie aufgelöst, das Bild gilt als fehlend, und damit fehlt der sichtbarste Teil des Ergebnisses. | Eine vollständige Adresse mitsamt Schema. |
"datePublished": "26.08.2026" |
Das Feld entfällt. Ein Artikel ohne Veröffentlichungsdatum verliert sein Datum im Eintrag. | ISO 8601: 2026-08-26 oder mit Uhrzeit und Zeitverschiebung. |
"price": "49,90 EUR" |
Das Angebot wird als Ganzes verworfen – Preis und Verfügbarkeit verschwinden gemeinsam. | Eine reine Zahl mit Punkt, die Währung in priceCurrency. |
"@type": "article" |
Der Typ ist unbekannt, jede Eigenschaft darunter wird als unbestimmte Sache gelesen. | Genau Article, Großbuchstabe eingeschlossen. |
Die ersten drei stammen aus Vorlagen, die die Auszeichnung aus denselben Variablen füllen wie die Seite selbst: Der Bildpfad ist relativ, weil die Vorlage relative Pfade schreibt. Das Datum ist formatiert, weil das formatierte Datum ohnehin zur Hand war. Und der Preis trägt seine Währung, weil er so gedruckt wird. Strukturierte Daten brauchen die rohen Werte, nicht die für die Anzeige aufbereiteten.
Article: das eine Pflichtfeld und die vier, auf die es ankommt
Ein Artikel braucht eine headline und sonst nichts. Alles, was ein Ergebnis wie ein Ergebnis aussehen lässt, ist freiwillig – und genau deshalb besteht so viel Auszeichnung die Prüfung und zeigt nichts.
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "Article",
"headline": "JSON-LD, das Suchmaschinen wirklich lesen",
"description": "Warum strukturierte Daten ohne Fehlermeldung scheitern.",
"image": ["https://www.example.com/images/json-ld.png"],
"author": { "@type": "Person", "name": "Lukas Wojcik" },
"datePublished": "2026-08-26",
"dateModified": "2026-08-26",
"mainEntityOfPage": { "@type": "WebPage", "@id": "https://www.example.com/blog/json-ld/" }
}
</script>
Vier Kleinigkeiten entscheiden, ob diese Auszeichnung etwas einbringt. Die headline wird jenseits von rund 110 Zeichen abgeschnitten, ein aus Seitentitel und Seitenname zusammengesetzter Titel verliert also sein Ende. Der author gehört in ein Objekt mit @type; eine bloße Zeichenkette wird angenommen, sagt aber nichts darüber, ob eine Person oder eine Organisation geschrieben hat. dateModified darf nicht vor datePublished liegen, eine Kombination, die immer dann auftaucht, wenn eine Migration das Importdatum in eines der beiden Felder schreibt. Und das Bild sollte in mehr als einem Seitenverhältnis vorliegen, denn die Darstellung, in der ein Ergebnis landet, ist nicht die, um die die Auszeichnung bittet.
FAQPage: die Regel, die bis auf die Seite reicht
Ein FAQ-Block ist der eine Typ, dessen Gültigkeit von etwas außerhalb der Auszeichnung abhängt: jede Frage und jede Antwort muss auf der Seite selbst sichtbar sein. Eine Auszeichnung, die eine Aufklappliste beschreibt, die sich nie öffnet, oder Fragen, die es nur im JSON gibt, gilt als irreführend und nicht als unvollständig – und das ist eine manuelle Maßnahme, keine fehlende Funktion.
Seit 2023 erscheint das FAQ-Rich-Result selbst fast nur noch bei behördlichen und medizinischen Seiten, für die meisten Seiten fällt die sichtbare Belohnung also weg. Die Auszeichnung lohnt trotzdem, nur für andere Leser: die interne Suche, die Assistenten, die eine Seite lesen, um eine Frage zu beantworten, und jedes nachgelagerte System, das sonst raten müsste, welcher Teil des Textes eine Frage ist. Wer davon den Eintrag mit Sternen und Pfeil erwartet, wird enttäuscht; wer ein maschinenlesbares Paar aus Frage und Antwort will, bekommt genau das.
Product: Preis, Währung, Verfügbarkeit
Ein Produkt ohne offers ist ein Produkt ohne Preis, und der Preis ist der einzige Grund, aus dem es ein Produktergebnis gibt.
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "Product",
"name": "Kompakter PoE-Switch, 8 Anschlüsse",
"image": ["https://www.example.com/images/switch.png"],
"brand": { "@type": "Brand", "name": "Example" },
"offers": {
"@type": "Offer",
"price": "149.00",
"priceCurrency": "EUR",
"availability": "https://schema.org/InStock",
"url": "https://www.example.com/switch/"
}
}
</script>
Das Feld availability nimmt eine schema.org-Adresse, nicht das Wort dahinter: InStock allein ist kein Wert. Kommt ein aggregateRating hinzu, braucht es sowohl einen ratingValue als auch eine Anzahl – eines ohne das andere entfällt, und eine Bewertung, die auf der Seite steht, aber nicht in der Auszeichnung, ist für den Eintrag nichts wert. Bewertungen, die eine Seite gar nicht zeigen kann, haben umgekehrt in der Auszeichnung nichts zu suchen.
Prüfen, bevor es live geht
Drei Prüfungen fangen nahezu alles ab, und sie beantworten verschiedene Fragen. Eine strukturelle Prüfung liest den Block für sich: sind die Pflichtfelder da, ist das Datum ein ISO-Datum, ist der Preis eine Zahl. Der Rich Results Test sagt anschließend, ob Google überhaupt einen Typ erkennt – das ist die einzige Stelle, an der ein kleingeschriebenes @type sichtbar wird. Und die Berichte zu den Verbesserungen in der Search Console zeigen, was über die ganze Seite hinweg geschieht, und dort taucht ein Vorlagenfehler als mehrere hundert gleiche Warnungen auf statt als eine.
Was keine davon beantwortet, ist die Frage, ob ein Ergebnis angezeigt wird. Gültige Auszeichnung ist eine Bedingung, kein Anspruch: Google entscheidet je Suchanfrage und je Seite, ob ein Rich Result erscheint, und ein richtiger Block, aus dem nichts folgt, ist der Normalfall und kein Fehler. Der Grund, strukturierte Daten zu schreiben, ist der, der diese Entscheidung überdauert – eine Seite, deren Aussagen für Maschinen lesbar sind, zu einem Zeitpunkt, an dem immer mehr davon mitlesen.
Fragen und Antworten
Was geschieht, wenn ein SEO-Plugin und das Theme je einen eigenen Article-Block ausgeben?
Dann beschreiben zwei Blöcke dieselbe Sache, und in der Regel nicht gleich: Das Plugin nimmt etwa den SEO-Titel und ein Bild in voller Größe, das Theme den Beitragstitel und das Vorschaubild, und die Datumsangaben weichen um eine Zeitzone voneinander ab. Für genau diesen Fall hat der Auswerter keine Regel, welcher Block gemeint ist. Eine Fehlermeldung gibt es trotzdem nicht; der Rich Results Test zeigt lediglich zwei erkannte Elemente vom Typ Article.
Die Abhilfe ist eine einzige Quelle. Ein SEO-Plugin baut oft ohnehin einen @graph mit Website, Seite, Autor und Brotkrumenpfad und ist dann die bessere Wahl, während die Ausgabe im Theme abgeschaltet wird. Soll das Theme etwas ergänzen, verweist es über @id auf den Artikel im Graphen des Plugins, statt einen zweiten Artikel anzulegen.
Nach jedem Wechsel von Plugin oder Theme lohnt ein Blick in den Quelltext einer Beitragsseite: Wie oft steht dort application/ld+json? Zwei Blöcke sind kein Fehler; zwei Blöcke, die dieselbe Seite unterschiedlich beschreiben, sind einer.
Liest Google JSON-LD, das erst per JavaScript eingefügt wird, etwa über den Tag Manager?
Google kann es lesen, weil der Crawler Seiten rendert, bevor er die strukturierten Daten auswertet, und der Rich Results Test zeigt auch so eingefügte Blöcke. Das Rendern geschieht allerdings in einem eigenen, späteren Schritt, und ein Block, der erst nach einer Nutzeraktion oder nach einer Einwilligung eingefügt wird, kommt beim Crawler nie an.
Für die übrigen Abnehmer, etwa die Assistenten und nachgelagerten Systeme, die beim FAQ-Typ genannt sind, sieht es schlechter aus. Viele Maschinen, die Seiten für Sprachmodelle einsammeln oder Vorschauen erzeugen, führen kein JavaScript aus und sehen nur das ausgelieferte HTML. Ein Block aus dem Tag Manager existiert für sie nicht. Strukturierte Daten, die mehr leisten sollen als ein einzelnes Rich Result, gehören deshalb serverseitig in das ausgelieferte HTML.
Wie entstehen Datum und Preis in einer Vorlage gleich im richtigen Format?
Indem die Vorlage den Rohwert eigens für die Auszeichnung formatiert, statt die Anzeigefassung zu übernehmen. In PHP liefert date('c', $zeitstempel) ein ISO-8601-Datum mit Uhrzeit und Zeitverschiebung, in WordPress leistet get_the_date('c') dasselbe für den Beitrag. Für den Preis ergibt number_format($preis, 2, '.', '') eine reine Zahl mit Punkt und ohne Tausendertrennzeichen, also 49.90 statt 49,90 EUR.
In JavaScript erzeugt toISOString() ebenfalls ein gültiges Datum, allerdings immer in UTC mit angehängtem Z. Das ist korrekt, nur trägt ein Beitrag, der kurz nach Mitternacht Ortszeit erscheint, dann noch das Datum des Vortags.
Welche Anzahl braucht ein aggregateRating neben dem ratingValue?
Entweder ratingCount, die Zahl der abgegebenen Bewertungen, oder reviewCount, die Zahl der Rezensionen; eines von beiden genügt. Nutzt die Bewertung eine andere Skala als eins bis fünf, gehören außerdem bestRating und worstRating in die Auszeichnung, sonst gilt die Fünferskala, und ein Wert von 8 auf einer Zehnerskala passt nicht hinein.