Skip to content

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:

KnotenBedingung
Organizationseo.schemas.organization
LocalBusinessseo.schemas.localBusiness — ersetzt den Typ, kein zweiter Knoten
WebSiteseo.schemas.website
WebPageimmer, sobald strukturierte Daten aktiv sind
BreadcrumbListwenn die Seite breadcrumbs übergibt
FAQPagewenn 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: