Metadata & SEO
Titel, Beschreibung, Vorschaubild – und wer sie berechnet
Metadata exportierst du aus einer Seite oder einem Layout. Sind die Werte erst nach dem Laden bekannt, berechnest du sie in generateMetadata.
Weil Next.js deine Seiten auf dem Server rendert, steht der Inhalt bereits im HTML – die halbe SEO-Arbeit ist damit erledigt. Bleibt die andere Hälfte: Titel, Beschreibung und Vorschaubild.
Der einfache Fall
import type { Metadata } from "next";
export const metadata: Metadata = {
title: "Über uns",
description: "Wer wir sind und woran wir arbeiten.",
};
export default function Seite() {
return <h1>Über uns</h1>;
} export const metadata: Metadata = {
metadataBase: new URL("https://beispiel.de"),
title: {
default: "Beispiel GmbH",
template: "%s | Beispiel GmbH", // Seitentitel wird eingesetzt
},
description: "Standardbeschreibung für alles ohne eigene.",
openGraph: { type: "website", locale: "de_DE" },
}; Ohne diese Angabe bleiben Bild- und Canonical-Adressen relativ – und relative Adressen funktionieren in Vorschaukarten von sozialen Netzwerken nicht. Einmal im Wurzel-Layout gesetzt, gilt sie überall.
Wenn die Werte aus den Daten kommen
export async function generateMetadata({
params,
}: {
params: Promise<{ slug: string }>;
}): Promise<Metadata> {
const { slug } = await params;
const artikel = await ladeArtikel(slug);
if (!artikel) return { title: "Nicht gefunden" };
return {
title: artikel.titel,
description: artikel.anriss,
alternates: { canonical: "/blog/" + slug },
openGraph: {
title: artikel.titel,
description: artikel.anriss,
images: [artikel.bild],
type: "article",
publishedTime: artikel.datum,
},
};
} generateMetadata und die Seite holen oft dieselben Daten. Dank Request MemoizationDerselbe `fetch` mehrfach in einem Seitenaufbau wird nur einmal wirklich ausgeführt.→ geht die Anfrage trotzdem nur einmal raus – du darfst den Aufruf also bedenkenlos zweimal hinschreiben.
Die Dateien drumherum
app/
icon.png → Favicon
apple-icon.png → Symbol für iOS
opengraph-image.png → Vorschaubild für geteilte Verweise
robots.ts → robots.txt
sitemap.ts → sitemap.xml import type { MetadataRoute } from "next";
export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
const artikel = await ladeAlleArtikel();
return [
{ url: "https://beispiel.de", changeFrequency: "weekly", priority: 1 },
...artikel.map((a) => ({
url: "https://beispiel.de/blog/" + a.slug,
lastModified: a.geaendert,
})),
];
} Was oft vergessen wird
- +
Je Seite eine eigene, beschreibende
description– keine Wiederholung des Titels - +
alternates.canonical, sobald es Adressen mit Parametern gibt - +
robots: { index: false }für Seiten ohne Inhalt im HTML – Konto, interne Werkzeuge - +
Strukturierte Daten per JSON-LD für Artikel, Produkte, FAQ
- −
Metadata in einer Client-Komponente – dort funktioniert der Export nicht
- −
Titel über 60 Zeichen; sie werden im Ergebnis abgeschnitten
- −
Für jede Seite denselben Text
Der metadata-Export wird nur in Server-Komponenten gelesen. Steht "use client" in der Datei, passiert schlicht nichts – ohne Fehlermeldung. Wenn dein Titel nicht ankommt, ist das die erste Sache, die du prüfst.
▸ Genauer erklärt: strukturierte Daten für Suche und AI-Antworten optional
Über die Metadata hinaus lohnt sich JSON-LD. Es beschreibt maschinenlesbar, was auf der Seite steht – und genau das zitieren Antwortmaschinen gern, weil sie es nicht aus dem Fließtext raten müssen.
export default async function Seite({ params }) {
const { slug } = await params;
const artikel = await ladeArtikel(slug);
const jsonLd = {
"@context": "https://schema.org",
"@type": "BlogPosting",
headline: artikel.titel,
datePublished: artikel.datum,
author: { "@type": "Person", name: artikel.autor },
};
return (
<>
<script
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
/>
<article>{artikel.inhalt}</article>
</>
);
} Statt für jeden Artikel ein Bild zu bauen, kann eine opengraph-image.tsx es zur Laufzeit erzeugen – mit Titel und Autor darauf. Das ist eine kleine Komponente, die Next.js in ein Bild verwandelt.
Sitzt das schon?
4 Fragen zu dieser Lektion. Falsche Antworten landen in deiner Statistik.