Skip to content

Metadaten

Jede Seite bekommt ihre Kopfdaten von <Seo />, eingebunden über BaseLayout. Was dort steht, berechnen die reinen Funktionen in src/lib/seo/metadata.ts.

Titel und Description

Der Titel einer Seite wird in site.titleTemplate eingesetzt:

ts
titleTemplate: '%s | Muster GmbH';
// <BaseLayout title="Leistungen"> ergibt: Leistungen | Muster GmbH

Das Schema erzwingt den Platzhalter %s. Fehlt die description einer Seite, gilt site.description — eine leere Description entsteht dadurch nie.

Canonical

Jede Seite nennt sich selbst als Canonical, nicht nur Seiten mit Dubletten. Sobald eine Adresse mit einem Parameter aufgerufen wird — ein utm_source aus einer Kampagne genügt — ist sie ohne diesen Verweis zweimal im Index.

Die Adresse endet auf einem Schrägstrich, weil Astro Verzeichnisse baut:

https://www.muster.de/leistungen/

Zeigt der Canonical auf /leistungen, verweist er auf eine Adresse, die der Server erst auf /leistungen/ weiterleitet — und damit auf eine Adresse, die die Seite nicht ausliefert. Der Post-Build-Test prüft den Canonical jeder Seite gegen den Ort ihrer Datei; ein Canonical, der überall auf die Startseite zeigt, fällt dadurch sofort auf.

Open Graph und X

Ausgegeben werden og:type, og:site_name, og:title, og:description, og:url und og:locale, dazu das Vorschaubild mit Maßen und Alt-Text.

Für X stehen nur twitter:card und — falls konfiguriert — twitter:site daneben. Titel, Beschreibung und Bild liest X aus den Open-Graph-Angaben, wenn kein eigenes Tag daneben steht. Zwei Tags wären zwei Wahrheiten über dieselbe Seite, von denen eine irgendwann veraltet.

Vorschaubild

Das Bild kommt über einen Schlüssel aus dem Asset-Manifest, nicht über einen Pfad:

ts
// project.config.ts
seo: {
  defaultOgImage: 'og-default',
}
astro
<!-- oder je Seite -->
<BaseLayout title="Leistungen" ogImage="leistungen-og" />

Der Schlüssel muss in src/content/assets.yaml stehen, sonst bricht der Build ab. Das Bild wird auf 1200 Pixel Breite und als JPEG erzeugt: Die Netzwerke wählen nichts aus einem srcset aus, und einige zeigen bis heute weder AVIF noch WebP. Der Alt-Text stammt aus dem Manifest.

Ohne defaultOgImage entstehen keine Bild-Tags, und twitter:card fällt auf summary zurück — eine große Karte ohne Bild wäre eine leere Fläche.

hreflang

Nur bei echter Übersetzung

Mehrere Sprachen in localization.locales erzeugen kein hreflang. Erst wenn eine Seite ihre Übersetzungen selbst nennt, entstehen Alternates.

astro
<BaseLayout title="Impressum" translations={{ 'en-GB': '/en/imprint' }} />

Daraus entsteht:

html
<link rel="alternate" hreflang="de-DE" href="https://www.muster.de/impressum/" />
<link rel="alternate" hreflang="en-GB" href="https://www.muster.de/en/imprint/" />
<link rel="alternate" hreflang="x-default" href="https://www.muster.de/impressum/" />

x-default zeigt auf die Standardsprache — sie ist die Fassung für alle, deren Sprache nicht abgedeckt ist. Eine Sprache, die nicht in localization.locales steht, bricht den Build ab.

Der Grund für diese Zurückhaltung steht in ADR 0007: Ein Alternate auf eine Seite, die es nicht gibt, fällt auf der Website nicht auf und taucht erst in der Search Console als Fehler auf.

Ein echtes Sprach-Routing bringt der Starter noch nicht mit. Bis dahin ist translations der Weg für einzelne übersetzte Seiten.