Erscheinungsbild
Structured Data
JSON-LD ist die technische Ausgabeform, schema.org das Vokabular. Beides steht in src/lib/seo/jsonld.ts: typisierte Builder, die je einen Knoten erzeugen.
Ein Graph je Seite
Alle Knoten einer Seite landen in einem gemeinsamen @graph und verweisen über @id aufeinander:
json
{
"@context": "https://schema.org",
"@graph": [
{
"@type": "Organization",
"@id": "https://www.muster.de/#organization",
"name": "Muster GmbH"
},
{
"@type": "WebSite",
"@id": "https://www.muster.de/#website",
"publisher": { "@id": "https://www.muster.de/#organization" }
},
{
"@type": "WebPage",
"@id": "https://www.muster.de/leistungen/#webpage",
"isPartOf": { "@id": "https://www.muster.de/#website" }
}
]
}Getrennte Skripte wären drei unverbundene Angaben. Im Graphen erkennt eine Suchmaschine, dass Organisation, Website und Seite dieselbe Sache beschreiben.
Was wann entsteht
features.structuredData ist der Hauptschalter — steht er auf false, entsteht kein einziges Skript. Darüber hinaus entscheiden die Flags unter seo.schemas:
| Knoten | Bedingung |
|---|---|
| Organization | seo.schemas.organization |
| LocalBusiness | seo.schemas.localBusiness — ersetzt den Typ, kein zweiter Knoten |
| WebSite | seo.schemas.website |
| WebPage | immer, sobald strukturierte Daten aktiv sind |
| BreadcrumbList | wenn die Seite breadcrumbs übergibt |
| FAQPage | wenn die Seite <Faq /> einsetzt |
Organization und LocalBusiness sind ein Knoten
LocalBusiness ist eine Unterart von Organization. Zwei Knoten für dieselbe Firma würden sie zu zwei Firmen machen. Ist localBusiness aktiv, wechselt der Typ — und company.address wird zur Pflicht, sonst entstünde unbrauchbares JSON-LD.
Leere Felder entstehen nicht: Was nicht konfiguriert ist — sameAs, Telefon, Anschrift — fehlt im Knoten, statt als leere Angabe dazustehen.
Brotkrumen
Der sichtbare Pfad und die BreadcrumbList entstehen aus einer Liste:
astro
<BaseLayout
title="Beratung"
breadcrumbs={[
{ label: 'Startseite', href: '/' },
{ label: 'Leistungen', href: '/leistungen' },
{ label: 'Beratung' },
]}
/>Der letzte Eintrag ist die aktuelle Seite und trägt kein href: Ein Verweis auf die Seite, auf der man schon ist, führt nirgendwohin und wird von Screenreadern trotzdem als Ziel angesagt. Er bekommt stattdessen aria-current="page".
Häufige Fragen
astro
---
import Faq from '../components/seo/Faq.astro';
---
<Faq
titel="Häufige Fragen"
eintraege={[{ frage: 'Wie lange dauert es?', antwort: 'Etwa zwei Wochen.' }]}
/>Die Komponente rendert die Fragen als Klappabschnitte und zeichnet dieselbe Liste als FAQPage aus. Antworten sind reiner Text — wer Auszeichnung in der Antwort braucht, hat keine FAQ, sondern einen Artikel.
Auszeichnung ohne sichtbaren Inhalt ist ein Verstoß
Google ahndet FAQ-Auszeichnung ohne sichtbare Fragen mit einer manuellen Maßnahme, und die trifft die ganze Domain. Deshalb gibt es keinen Weg, die Auszeichnung ohne die Anzeige zu bekommen.
Seitenbezogene Knoten
Für Inhalte, die es im Starter noch nicht gibt, stehen typisierte Builder bereit. Sie werden über jsonLd übergeben:
astro
---
import { articleNode } from '../lib/seo/jsonld';
import project from 'virtual:project-config';
import { canonicalUrl, organizationId } from '../lib/seo';
const canonical = canonicalUrl(project.site.url, Astro.url.pathname);
---
<BaseLayout
title="Ein Beitrag"
ogType="article"
jsonLd={[
articleNode({
headline: 'Ein Beitrag',
description: 'Worum es geht.',
canonical,
datePublished: '2026-07-29',
authorName: 'Redaktion',
publisherId: organizationId(project.site.url),
}),
]}
/>Verfügbar sind articleNode, serviceNode und faqPageNode. Sie sind einzeln getestet, aber im Starter noch an keiner Seite im Einsatz — es gibt dort keinen passenden sichtbaren Inhalt.
Prüfen
Der Post-Build-Test stellt sicher, dass jeder Block gültiges JSON ist, den richtigen Kontext trägt und nur die konfigurierten Schemata enthält. Für die inhaltliche Prüfung vor dem Livegang: