Hirefullstack – Software Engineering & IT-Beratung aus Berlin
← Zur Übersicht
Kern 12 Min Lesezeit

Metadata & SEO

Titel, Beschreibung, Vorschaubild – und wer sie berechnet

In einem Satz

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

app/ueber-uns/page.tsx
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>;
}
Ein exportiertes Objekt genügt. Next.js baut daraus die passenden Tags im <head>.
app/layout.tsx
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" },
};
Im Wurzel-Layout setzt du die Vorgaben. Einzelne Seiten überschreiben nur, was sie brauchen.
Tipp metadataBase nicht vergessen

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

app/blog/[slug]/page.tsx
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,
    },
  };
}
Gut zu wissen Der Ladevorgang läuft nur einmal

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/
app/
  icon.pngFavicon
  apple-icon.pngSymbol für iOS
  opengraph-image.pngVorschaubild für geteilte Verweise
  robots.tsrobots.txt
  sitemap.tssitemap.xml
Auch hier gilt: Dateiname statt Konfiguration. Das Bild neben einer page.tsx gilt nur für diese Seite.
app/sitemap.ts
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

Nimm es, wenn …
  • +

    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

Lass es, wenn …
  • 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

Stolperstein In Client-Komponenten geht es nicht

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.

app/blog/[slug]/page.tsx
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>
    </>
  );
}
Tipp Vorschaubilder erzeugen lassen

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.

Hirefullstack

Ihr braucht React-Verstärkung im Team?

Wir bauen seit Jahren React- und Next.js-Anwendungen für Kunden in ganz Deutschland – als einzelner Experte, als Verstärkung fürs Bestandsteam oder als komplettes Scrum-Team.

Projekt besprechen →