Next.js SEO: Vollständiger Leitfaden – Metadata API und strukturierte Daten in der Praxis

Das Statistik-Dashboard in der Google Search Console – die große „0” auf dem Bildschirm. Tag 23 nach dem Launch. Zwei Monate Entwicklung, unzählige Nachtschichten, sorgfältig poliertes UI, flüssige User Experience – und vor dieser kalten „0” wirkt alles bedeutungslos.
Noch frustrierender: Beim Teilen des Links auf Twitter bleibt die Vorschau leer. Kein anständiges Cover-Bild.
„Next.js hat doch SSR – warum ist SEO so schlecht?” Ich habe die offizielle Dokumentation durchforstet und eine harte Wahrheit gelernt: SSR bedeutet nicht SEO-freundlich. Falsche Meta-Tags, fehlende strukturierte Daten, Open Graph nicht verstanden – Suchmaschinen ignorieren Sie trotzdem.
Diesen Schmerz kennen Sie vermutlich: Ein sorgfältig gebautes Produkt, das man nicht findet und das beim Teilen unprofessionell wirkt – nur bezahlte Werbung hilft. Mit der Next.js 15 Metadata API und ein paar Schlüsselkonfigurationen lassen sich diese Probleme lösen.
Dieser Artikel zeigt Schritt für Schritt: Metadata API für einzigartige Meta-Tags pro Seite, strukturierte Daten für auffälligere Suchergebnisse, perfekte Social-Sharing-Vorschau. Außerdem: 5 häufigste SEO-Fallen, damit Sie meine Fehler nicht wiederholen.
Warum ist das SEO Ihrer Next.js-Website so schlecht?
SSR bedeutet nicht SEO-freundlich
Ehrlich gesagt dachte ich am Anfang genauso. Next.js, serverseitiges Rendering, HTML direkt an Crawler – SEO perfekt, oder?
Pustekuchen.
Später sah ich mir den Quellcode eines Freundes an, öffnete die DevTools und prüfte das HTML: Auf allen Seiten stand <title>My App</title>, <meta name="description"> fehlte oder war überall identisch. Wie ein Feinkostladen, dessen Schild immer nur „Laden” zeigt – niemand weiß, was Sie verkaufen.
Next.js liefert SSR – aber Meta-Tags sind Ihre Verantwortung. Ohne Konfiguration bleibt es leeres HTML; Suchmaschinen wissen nicht, worum es auf der Seite geht.
5 tödliche SEO-Fehler
Diese Fallen sehe ich ständig – ich selbst bin auch hineingetappt:
1. Ein title und eine description für alle Seiten
Der häufigste Fehler. Entweder ein fester title in _document.tsx oder gar nichts. Google zeigt für Startseite, About und Produktseite denselben Titel und dieselbe Beschreibung.
Stellen Sie sich vor, in einer Buchhandlung tragen alle Bücher denselben Titel auf dem Cover. Würden Sie kaufen?
2. Canonical URL vergessen – Duplicate Content
Besonders tückisch. Pagination (?page=2), Filter (?category=tech), Sortierung (?sort=date) – verschiedene Ansichten derselben Seite, aber Suchmaschinen behandeln sie als separate Seiten und bestrafen „Duplicate Content”.
Bei einem Kundenblog sank der Traffic deshalb um 40 %. Nach dem Canonical-URL-Fix normalisierte er sich in zwei Wochen.
3. Keine strukturierten Daten – Rich Results verpasst
Suchen Sie nach „Apfelkuchen Rezept”? Manche Ergebnisse zeigen Bewertung, Backzeit, Kalorien – nicht erraten von Google, sondern per JSON-LD mitgeteilt.
Studien zeigen: Mit strukturierten Daten steigt die Klickrate im Schnitt um 20–30 %. Das entspricht fast einem Drittel mehr Traffic – für wenige Zeilen Code.
4. Bilder ohne alt – Bildsuche-Traffic verschenkt
Viele halten alt nur für Barrierefreiheit, nicht für SEO. Falsch.
Google Bildersuche ist ein großer Traffic-Kanal. Klare alt-Beschreibungen können in der Bildsuche ranken. Eine Design-Asset-Seite bekam 30 % Traffic aus Google Bildersuche – weil jedes Bild einen sorgfältigen alt-Text hatte.
5. Keine sitemap.xml und robots.txt
Diese Dateien sagen Suchmaschinen, welche Seiten gecrawlt werden dürfen und welche nicht. Ohne Sitemap kann Google Monate brauchen, neue Artikel zu finden. Mit Sitemap und Einreichung in Search Console oft Indexierung in wenigen Tagen.
Next.js App Router generiert sitemap.ts und robots.ts automatisch – warum also weglassen?
Metadata API vollständig nutzen (Next.js 15)
Die Metadata API in Next.js 15 ist das größte SEO-Geschenk des Frameworks. Früher <Head> pro Seite – jetzt exportieren Sie ein Objekt oder eine Funktion, Next.js erledigt den Rest.
Statische Metadaten: Seiten mit festem Inhalt
Einfachster Fall: „Über uns”, Datenschutz – Inhalt ändert sich selten. In page.tsx ein metadata-Objekt exportieren:
// app/about/page.tsx
import { Metadata } from 'next'
export const metadata: Metadata = {
title: 'Über uns - TechBlog',
description: 'Wir sind Entwickler, die Frontend-, Backend- und DevOps-Praxis teilen.',
keywords: ['Tech-Blog', 'Frontend-Entwicklung', 'Next.js', 'React'],
authors: [{ name: 'Max Mustermann' }],
openGraph: {
title: 'Über uns - TechBlog',
description: 'Tech-Blog mit Entwickler-Praxis',
url: 'https://yourdomain.com/about',
siteName: 'TechBlog',
images: [
{
url: 'https://yourdomain.com/og-about.jpg',
width: 1200,
height: 630,
}
],
type: 'website',
},
twitter: {
card: 'summary_large_image',
title: 'Über uns - TechBlog',
description: 'Tech-Blog mit Entwickler-Praxis',
images: ['https://yourdomain.com/og-about.jpg'],
},
}
export default function AboutPage() {
return <div>Über-uns-Inhalt...</div>
}
Typsicher, IDE-Autovervollständigung, keine Tippfehler. Next.js dedupliziert intelligent – doppelte Meta-Tags werden zusammengeführt.
Best Practices:
title: max. 60 Zeichen, sonst Abschnitt in Suchergebnissendescription: 150–160 Zeichen – ideal für Google-SnippetsopenGraph.images: 1200×630 Pixel – funktioniert auf Twitter, Facebook, LinkedIn
Dynamische Metadaten: Retter für Blog und Produktseiten
Leistungsstark: generateMetadata. Blogartikel mit unterschiedlichem Titel und Beschreibung – nicht von Hand schreibbar.
// app/blog/[slug]/page.tsx
import { Metadata } from 'next'
import { getPostBySlug } from '@/lib/posts'
export async function generateMetadata(
{ params }: { params: { slug: string } }
): Promise<Metadata> {
const post = await getPostBySlug(params.slug)
return {
title: `${post.title} - TechBlog`,
description: post.excerpt,
authors: [{ name: post.author }],
openGraph: {
title: post.title,
description: post.excerpt,
images: [post.coverImage],
type: 'article',
publishedTime: post.publishedAt,
authors: [post.author],
},
twitter: {
card: 'summary_large_image',
title: post.title,
description: post.excerpt,
images: [post.coverImage],
},
}
}
export default async function BlogPostPage({ params }: { params: { slug: string } }) {
const post = await getPostBySlug(params.slug)
return <article>{post.content}</article>
}
Jeder Artikel hat einzigartige SEO-Infos. Google sieht vollständiges HTML ohne Client-JavaScript.
Wichtiger Trick: metadataBase
Oben sind Bild-URLs absolut. Bei relativen Pfaden (/images/cover.jpg) im Root-Layout metadataBase setzen:
// app/layout.tsx
export const metadata: Metadata = {
metadataBase: new URL('https://yourdomain.com'),
}
Danach werden relative Pfade automatisch ergänzt. Ohne das schlägt Open-Graph-Crawling fehl – Social Sharing ohne Bild.
Template-System: Einheitliches Titelformat
Viele Sites nutzen „Seitenname | Site-Name” – z. B. „Startseite | TechBlog”. Statt manuell: title.template:
// app/layout.tsx (root layout)
export const metadata: Metadata = {
title: {
template: '%s | TechBlog',
default: 'TechBlog - Tech-Blog',
},
description: 'Tech-Blog zu Frontend, Backend und DevOps',
metadataBase: new URL('https://yourdomain.com'),
}
Unterseiten nur noch Seitennamen:
// app/about/page.tsx
export const metadata: Metadata = {
title: 'Über uns', // wird zu „Über uns | TechBlog"
}
Startseite ohne Suffix? title.absolute:
// app/page.tsx
export const metadata: Metadata = {
title: {
absolute: 'TechBlog - Tech-Blog Startseite', // kein template
},
}
Einheitliche Titel, Änderung nur einmal im Root-Layout.
Strukturierte Daten (Schema.org) – hervorstechen in der Suche
Was sind strukturierte Daten? Warum wichtig?
Haben Sie schon „Apfelkuchen Rezept” gesucht?
Manche Ergebnisse zeigen Bewertung (4,8 Sterne), Backzeit (45 Min.), Kalorien (320 kcal) – andere nur Titel und Kurztext.
Der Unterschied: strukturierte Daten.
JSON-LD im Standardformat teilt Suchmaschinen mit: „Das ist ein Blogartikel, Autor X, veröffentlicht am Y” oder „Produkt, Preis X, Bewertung Y”. Daraus entstehen Rich Snippets – Karten mit Bewertung, Preis, Autor.
Zahlen lügen nicht: 20–30 % höhere Klickrate im Schnitt – fast ein Drittel mehr Traffic bei minimalem Aufwand.
Häufige Schema-Typen
Schema.org definiert Hunderte Typen; für die meisten Sites reichen:
- Organization – Unternehmen (Startseite)
- BlogPosting – Blogartikel (pro Artikel)
- Product – Produkte (E-Commerce: Preis, Bewertung, Lager)
- FAQPage – FAQ (direkt in Suchergebnissen aufklappbar)
- LocalBusiness – lokale Unternehmen (Adresse, Öffnungszeiten, Telefon)
Schwerpunkt hier: die ersten beiden – für Blogs und Unternehmenswebsites.
JSON-LD in Next.js
Die <Script>-Komponente in Next.js 15 macht es einfach. Ich nutze eine wiederverwendbare Komponente:
// components/StructuredData.tsx
import Script from 'next/script'
type StructuredDataProps = {
data: object
}
export default function StructuredData({ data }: StructuredDataProps) {
return (
<Script
id="structured-data"
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(data) }}
/>
)
}
Verwendung auf der Seite:
Beispiel 1: Organization (Unternehmensinfo)
// app/layout.tsx (root layout)
import StructuredData from '@/components/StructuredData'
const organizationData = {
'@context': 'https://schema.org',
'@type': 'Organization',
name: 'TechBlog',
url: 'https://yourdomain.com',
logo: 'https://yourdomain.com/logo.png',
sameAs: [
'https://twitter.com/yourusername',
'https://github.com/yourcompany',
'https://linkedin.com/company/yourcompany',
],
contactPoint: {
'@type': 'ContactPoint',
email: '[email protected]',
contactType: 'Customer Service',
},
}
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html>
<body>
{children}
<StructuredData data={organizationData} />
</body>
</html>
)
}
Beispiel 2: BlogPosting (Blogartikel)
// app/blog/[slug]/page.tsx
import StructuredData from '@/components/StructuredData'
import { getPostBySlug } from '@/lib/posts'
export default async function BlogPostPage({ params }: { params: { slug: string } }) {
const post = await getPostBySlug(params.slug)
const articleData = {
'@context': 'https://schema.org',
'@type': 'BlogPosting',
headline: post.title,
description: post.excerpt,
image: post.coverImage,
author: {
'@type': 'Person',
name: post.author,
url: `https://yourdomain.com/author/${post.authorSlug}`,
},
publisher: {
'@type': 'Organization',
name: 'TechBlog',
logo: {
'@type': 'ImageObject',
url: 'https://yourdomain.com/logo.png',
},
},
datePublished: post.publishedAt,
dateModified: post.updatedAt,
mainEntityOfPage: {
'@type': 'WebPage',
'@id': `https://yourdomain.com/blog/${post.slug}`,
},
}
return (
<>
<article>{post.content}</article>
<StructuredData data={articleData} />
</>
)
}
Viel Code – im Kern nur Artikel-Metadaten im Standardformat an Google. Einmal konfiguriert, danach Copy-Paste.
Strukturierte Daten validieren
Zwei Tools:
-
Google Rich Results Test (https://search.google.com/test/rich-results)
- URL eingeben – Google zeigt mögliche Rich Results
- Fehler werden konkret benannt
-
Schema Markup Validator (https://validator.schema.org/)
- Prüft JSON-LD gegen Schema.org
- Strenger als Google – beide testen
Häufigster Fehler: fehlender publisher (Pflicht bei BlogPosting) oder Bild-URL nicht absolut. Validatoren sagen, was fehlt.
Open Graph und Twitter Cards in der Praxis
Warum Social Sharing wichtig ist
Link auf Twitter oder Facebook geteilt – Vorschau leer oder falsches Bild (Logo, Zufallsgrafik)?
Wirkt unprofessionell. Korrekt konfigurierte Sites zeigen Cover, Titel, Beschreibung – 2–3× höhere Klickrate.
Open Graph und Twitter Cards steuern die Darstellung in sozialen Netzwerken.
Open-Graph-Protokoll im Detail
Open Graph stammt von Facebook, wird von Twitter, LinkedIn, Slack, Discord unterstützt.
Bei der Metadata API haben wir openGraph schon konfiguriert – hier die Kernfelder:
export const metadata: Metadata = {
openGraph: {
// Pflichtfelder
title: 'Artikeltitel', // Titel in Social Media
description: 'Artikel-Kurztext', // ca. 150 Zeichen
url: 'https://yourdomain.com/article', // URL des Inhalts
siteName: 'TechBlog', // Site-Name
// Bilder (am wichtigsten!)
images: [
{
url: 'https://yourdomain.com/og-image.jpg',
width: 1200,
height: 630, // empfohlen 1200×630
alt: 'Bildbeschreibung (Barrierefreiheit und SEO)',
},
],
// Inhaltstyp
type: 'article', // article für Artikel, website sonst
// Artikel-spezifisch (bei type: article)
publishedTime: '2025-01-15T08:00:00.000Z',
modifiedTime: '2025-01-16T10:30:00.000Z',
authors: ['Max Mustermann', 'Anna Beispiel'],
tags: ['Next.js', 'SEO', 'Frontend-Entwicklung'],
// Lokalisierung (Mehrsprachigkeit)
locale: 'de_DE',
alternateLocale: ['en_US', 'ja_JP'],
},
}
Schlüsselpunkt: Bildgröße
1200×630 Pixel (1,91:1) – überall gut:
- Facebook, LinkedIn: vollständige Anzeige
- Twitter: Zuschnitt 2:1 bleibt akzeptabel
- Slack, Discord: ebenfalls geeignet
Maximale Dateigröße: 8 MB, sonst Build-Fehler.
Twitter Cards konfigurieren
Twitter hat eigene Meta-Tags, fällt aber auf Open Graph zurück – trotzdem separat konfigurieren:
export const metadata: Metadata = {
twitter: {
card: 'summary_large_image', // Großbild-Modus (empfohlen)
site: '@yourusername', // Twitter-Account der Site
creator: '@authorusername', // Twitter-Account des Autors
title: 'Artikeltitel',
description: 'Artikel-Kurztext',
images: ['https://yourdomain.com/twitter-image.jpg'],
},
}
card-Werte:
summary: Kleines Bild links, Text rechtssummary_large_image: Großes Bild oben (empfohlen)
Twitter-Limit: 5 MB (strenger als OG).
Dynamische Social-Sharing-Bilder (Fortgeschritten)
Pro Artikel manuell 1200×630 Cover? Mühsam.
Ab Next.js 13.3 OG-Bilder per Code – ideal für Blogs:
// app/blog/[slug]/opengraph-image.tsx
import { ImageResponse } from 'next/og'
import { getPostBySlug } from '@/lib/posts'
export const runtime = 'edge'
export const alt = 'Blog post cover'
export const size = {
width: 1200,
height: 630,
}
export const contentType = 'image/png'
export default async function Image({ params }: { params: { slug: string } }) {
const post = await getPostBySlug(params.slug)
return new ImageResponse(
(
<div
style={{
fontSize: 60,
background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
width: '100%',
height: '100%',
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'center',
color: 'white',
padding: '80px',
}}
>
<h1 style={{ fontSize: 72, fontWeight: 'bold', textAlign: 'center' }}>
{post.title}
</h1>
<p style={{ fontSize: 36, marginTop: 20, opacity: 0.9 }}>
by {post.author}
</p>
</div>
),
{
...size,
}
)
}
Jeder Artikel bekommt ein dynamisches Sharing-Bild – ohne manuelles Design.
Eigene Schriftarten oder Hintergründe: Next.js-Dokumentation zu next/og.
Social-Sharing testen und validieren
Vor dem Teilen testen:
-
Facebook Sharing Debugger (https://developers.facebook.com/tools/debug/)
- URL eingeben – Vorschau wie Facebook sie crawlt
- Facebook cached OG-Daten – nach Code-Änderung „Scrape Again” klicken
-
Twitter Card Validator (https://cards-dev.twitter.com/validator)
- URL eingeben – Twitter-Card-Vorschau
- Seit 2023 Developer-Account nötig – alternativ direkt auf Twitter testen
-
LinkedIn Post Inspector (https://www.linkedin.com/post-inspector/)
- LinkedIn-Vorschau, Cache aktualisierbar
Mein Fehler: Nach OG-Bild-Update zeigte Facebook noch das alte Bild. Im Sharing Debugger Cache refreshen – sonst zweifeln Sie an allem.
Weitere SEO-Pflichtkonfigurationen
Metadata API, strukturierte Daten und Open Graph sind das Herzstück – diese Punkte nicht vergessen.
sitemap.xml – Suchmaschinen über Ihre Seiten informieren
Sitemap ist eine XML-Datei mit allen URLs. Google- und Bing-Crawler lesen sie, verstehen die Struktur, indexieren schneller.
Next.js App Router: app/sitemap.ts erstellen:
// app/sitemap.ts
import { MetadataRoute } from 'next'
import { getAllPosts } from '@/lib/posts'
export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
const posts = await getAllPosts()
const baseUrl = 'https://yourdomain.com'
// Statische Seiten
const staticPages: MetadataRoute.Sitemap = [
{
url: baseUrl,
lastModified: new Date(),
changeFrequency: 'daily',
priority: 1,
},
{
url: `${baseUrl}/about`,
lastModified: new Date(),
changeFrequency: 'monthly',
priority: 0.8,
},
]
// Dynamische Blog-Seiten
const blogPages: MetadataRoute.Sitemap = posts.map((post) => ({
url: `${baseUrl}/blog/${post.slug}`,
lastModified: new Date(post.updatedAt),
changeFrequency: 'weekly' as const,
priority: 0.7,
}))
return [...staticPages, ...blogPages]
}
Next.js generiert https://yourdomain.com/sitemap.xml automatisch.
Danach zwei Schritte:
- In Google Search Console einreichen (https://search.google.com/search-console)
- In Bing Webmaster Tools einreichen (https://www.bing.com/webmasters)
Indexierung oft 50 %+ schneller. Ohne Sitemap: neue Artikel erst nach zwei Wochen indexiert; mit Einreichung oft in drei Tagen in Google.
robots.txt – Crawler-Zugriff steuern
robots.txt sagt Crawlern, was erlaubt ist und was nicht.
Next.js App Router – per Code:
// app/robots.ts
import { MetadataRoute } from 'next'
export default function robots(): MetadataRoute.Robots {
return {
rules: [
{
userAgent: '*', // alle Crawler
allow: '/', // alle Seiten erlaubt
disallow: ['/admin', '/api', '/private'], // diese Pfade verbieten
},
],
sitemap: 'https://yourdomain.com/sitemap.xml',
}
}
Erzeugt https://yourdomain.com/robots.txt:
User-agent: *
Allow: /
Disallow: /admin
Disallow: /api
Disallow: /private
Sitemap: https://yourdomain.com/sitemap.xml
Typische Szenarien:
- Admin (
/admin) nicht indexieren - API-Routen (
/api) nicht crawlen - Entwürfe/Vorschau per
noindexoderDisallow
Canonical URL – Duplicate-Content-Strafe vermeiden
Canonical URL sagt: „Es gibt mehrere URLs – das ist die Hauptversion.”
Typisch:
- Pagination:
/blog?page=1,/blog?page=2 - Filter:
/products?category=tech - Sortierung:
/products?sort=price
Verschiedene Ansichten derselben Seite – ohne Canonical wirkt es wie Duplicate Content.
In Next.js:
// app/blog/page.tsx
export const metadata: Metadata = {
alternates: {
canonical: 'https://yourdomain.com/blog',
},
}
Oder dynamisch:
// app/blog/[slug]/page.tsx
export async function generateMetadata({ params }: { params: { slug: string } }): Promise<Metadata> {
return {
alternates: {
canonical: `https://yourdomain.com/blog/${params.slug}`,
},
}
}
Mehrsprachige Seiten: alternates.languages für Google:
export const metadata: Metadata = {
alternates: {
canonical: 'https://yourdomain.com/blog/nextjs-seo',
languages: {
'en-US': 'https://yourdomain.com/en/blog/nextjs-seo',
'ja-JP': 'https://yourdomain.com/ja/blog/nextjs-seo',
},
},
}
Bildoptimierung – next/image + alt
Bild-SEO wird oft ignoriert – Google Bildersuche ist ein großer Kanal.
Zwei Schlüsselpunkte:
next/imagestatt<img>
import Image from 'next/image'
<Image
src="/cover.jpg"
alt="Next.js SEO Vollständiger Leitfaden Cover"
width={1200}
height={630}
priority // Above-the-fold: priorisiert laden
/>
next/image optimiert automatisch:
- Lazy Loading (außerhalb Viewport)
- WebP-Konvertierung
- Responsive Größen
- CLS-Vermeidung (Core Web Vitals)
altist Pflicht
Nicht optional – SEO und Barrierefreiheit (Screenreader lesen alt).
Gute alt-Texte:
- ✅ „Next.js Metadata API Konfigurations-Codebeispiel”
- ✅ „Blogartikel als Rich Result in Google-Suche”
Schlechte alt-Texte:
- ❌ „Bild”
- ❌ „screenshot.png”
- ❌ kein alt
Google Bildersuche rankt nach alt. Design-Asset-Site: 30 % Traffic aus Bildsuche durch sorgfältige alt-Texte.
Praxisbeispiel – vollständige SEO-Konfiguration für einen Blog
Theorie und Codefragmente zusammengeführt: So konfiguriert ein Tech-Blog mit Next.js 15 App Router SEO.
Projektstruktur
app/
├── layout.tsx # Root layout – globale Konfiguration
├── page.tsx # Startseite
├── about/page.tsx # Über-uns-Seite
├── blog/
│ ├── page.tsx # Blog-Liste
│ └── [slug]/
│ ├── page.tsx # Blog-Detail
│ └── opengraph-image.tsx # Dynamisches OG-Bild (optional)
├── sitemap.ts # Sitemap-Generierung
└── robots.ts # Robots.txt-Generierung
Vollständige Codebeispiele
1. Root Layout – globale SEO-Konfiguration
// app/layout.tsx
import { Metadata } from 'next'
import StructuredData from '@/components/StructuredData'
export const metadata: Metadata = {
metadataBase: new URL('https://yourdomain.com'), // Pflicht für relative Pfade
title: {
template: '%s | TechBlog', // Titelformat Unterseiten
default: 'TechBlog - Frontend-Entwicklungsblog',
},
description: 'Tech-Blog zu Next.js, React, TypeScript und Praxis',
keywords: ['Next.js', 'React', 'TypeScript', 'Frontend-Entwicklung', 'Tech-Blog'],
authors: [{ name: 'Max Mustermann', url: 'https://yourdomain.com/about' }],
openGraph: {
type: 'website',
siteName: 'TechBlog',
locale: 'de_DE',
},
twitter: {
card: 'summary_large_image',
site: '@yourusername',
},
}
// Organization-Strukturdaten (global, root layout)
const organizationData = {
'@context': 'https://schema.org',
'@type': 'Organization',
name: 'TechBlog',
url: 'https://yourdomain.com',
logo: 'https://yourdomain.com/logo.png',
sameAs: [
'https://twitter.com/yourusername',
'https://github.com/yourcompany',
],
}
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="de">
<body>
{children}
<StructuredData data={organizationData} />
</body>
</html>
)
}
2. Startseite – statische Metadaten
// app/page.tsx
import { Metadata } from 'next'
export const metadata: Metadata = {
title: {
absolute: 'TechBlog - Frontend-Entwicklungsblog', // ohne template
},
description: 'Next.js, React, TypeScript und Frontend-Praxis – Skills verbessern',
openGraph: {
title: 'TechBlog - Frontend-Entwicklungsblog',
description: 'Frontend-Praxis und Entwickler-Tipps',
url: 'https://yourdomain.com',
images: [
{
url: 'https://yourdomain.com/og-home.jpg',
width: 1200,
height: 630,
alt: 'TechBlog Startseiten-Cover',
},
],
},
}
export default function HomePage() {
return <div>Startseiten-Inhalt...</div>
}
3. Blog-Detail – dynamische Metadaten + Strukturdaten
// app/blog/[slug]/page.tsx
import { Metadata } from 'next'
import { notFound } from 'next/navigation'
import { getPostBySlug } from '@/lib/posts'
import StructuredData from '@/components/StructuredData'
export async function generateMetadata(
{ params }: { params: { slug: string } }
): Promise<Metadata> {
const post = await getPostBySlug(params.slug)
if (!post) return {}
return {
title: post.title, // wird zu „Artikeltitel | TechBlog"
description: post.excerpt,
keywords: post.tags,
authors: [{ name: post.author }],
openGraph: {
title: post.title,
description: post.excerpt,
url: `https://yourdomain.com/blog/${post.slug}`,
images: [post.coverImage],
type: 'article',
publishedTime: post.publishedAt,
authors: [post.author],
},
twitter: {
card: 'summary_large_image',
title: post.title,
description: post.excerpt,
images: [post.coverImage],
},
alternates: {
canonical: `https://yourdomain.com/blog/${post.slug}`,
},
}
}
export default async function BlogPostPage({ params }: { params: { slug: string } }) {
const post = await getPostBySlug(params.slug)
if (!post) notFound()
const articleData = {
'@context': 'https://schema.org',
'@type': 'BlogPosting',
headline: post.title,
description: post.excerpt,
image: post.coverImage,
datePublished: post.publishedAt,
dateModified: post.updatedAt || post.publishedAt,
author: {
'@type': 'Person',
name: post.author,
},
publisher: {
'@type': 'Organization',
name: 'TechBlog',
logo: {
'@type': 'ImageObject',
url: 'https://yourdomain.com/logo.png',
},
},
mainEntityOfPage: {
'@type': 'WebPage',
'@id': `https://yourdomain.com/blog/${post.slug}`,
},
}
return (
<>
<article>
<h1>{post.title}</h1>
<div dangerouslySetInnerHTML={{ __html: post.content }} />
</article>
<StructuredData data={articleData} />
</>
)
}
4. Sitemap und Robots
// app/sitemap.ts
import { getAllPosts } from '@/lib/posts'
export default async function sitemap() {
const posts = await getAllPosts()
const baseUrl = 'https://yourdomain.com'
const blogUrls = posts.map((post) => ({
url: `${baseUrl}/blog/${post.slug}`,
lastModified: new Date(post.updatedAt),
changeFrequency: 'weekly' as const,
priority: 0.7,
}))
return [
{
url: baseUrl,
lastModified: new Date(),
changeFrequency: 'daily' as const,
priority: 1,
},
{
url: `${baseUrl}/about`,
lastModified: new Date(),
changeFrequency: 'monthly' as const,
priority: 0.8,
},
...blogUrls,
]
}
// app/robots.ts
export default function robots() {
return {
rules: {
userAgent: '*',
allow: '/',
disallow: ['/api', '/admin'],
},
sitemap: 'https://yourdomain.com/sitemap.xml',
}
}
Validierungs-Checkliste nach Deployment
Vor dem Go-Live prüfen:
-
HTML-Quellcode
- F12 → Elements →
<head>prüfen <title>,<meta name="description">, OG-Tags korrekt?
- F12 → Elements →
-
Sitemap und robots testen
https://yourdomain.com/sitemap.xmlaufrufenhttps://yourdomain.com/robots.txtprüfen
-
Strukturierte Daten validieren
- Google Rich Results Test für mehrere Seiten
- Keine Fehler oder Warnungen
-
Social Sharing testen
- Facebook Sharing Debugger und Twitter Card Validator
- Bild, Titel, Beschreibung korrekt
-
Bei Suchmaschinen einreichen
- Sitemap in Google Search Console
- Sitemap in Bing Webmaster Tools
Damit ist die Next.js-SEO-Konfiguration vollständig.
Fazit
Wenn Sie bis hier gelesen haben, sollte klar sein: Next.js SSR bedeutet nicht automatisch SEO-freundlich – aber Next.js 15 macht die Konfiguration deutlich einfacher.
Kernpunkte:
- Metadata API: typsichere Meta-Tags – statisch via
metadata, dynamisch viagenerateMetadata - Strukturierte Daten (JSON-LD): Geheimwaffe für 20–30 % mehr Klicks – mit
<Script>einfach umsetzbar - Open Graph und Twitter Cards: steuern Social-Vorschau – 1200×630 ist der Universal-Standard
- sitemap.xml und robots.txt: per
.ts-Dateien automatisch – nicht vergessen in Search Console einzureichen - Bildoptimierung:
next/image+ sorgfältige alt-Texte – Bildsuche als Traffic-Kanal
Die Konfiguration wirkt aufwendig – das Verhältnis Aufwand zu Nutzen ist hoch. Viele starke Produkte scheitern am SEO und bleiben auf bezahlter Werbung angewiesen; korrekt konfigurierte Sites bekommen organischen Traffic fast kostenlos.
SEO ist keine Mystik, sondern Systematik. Checkliste abarbeiten, Tools nutzen, Wirkung zeigt sich – vielleicht nicht sofort, aber nach drei Monaten deutlich.
Warten Sie nicht, bis der Traffic weg ist. Öffnen Sie Ihr Projekt, investieren Sie einen halben Tag. Bei Fragen: Artikel bookmarken.
Wenn dieser Artikel geholfen hat, teilen Sie ihn – anderen Entwicklern ein paar Fallen ersparen.
Next.js SEO: Vollständiger Konfigurationsablauf
Vollständige SEO-Optimierung von der Metadata API über strukturierte Daten bis zu sitemap und robots.txt
⏱️ Estimated time: 4 hr
- 1
Step 1: Basis-Metadaten konfigurieren
Next.js 15 Metadata API verwenden:
• metadata-Objekt in layout.js oder page.js exportieren
• title, description, keywords konfigurieren
• Open Graph und Twitter Cards einrichten
Beispiel:
export const metadata = {
title: 'Seitentitel',
description: 'Seitenbeschreibung',
openGraph: {
title: 'OG-Titel',
description: 'OG-Beschreibung',
images: ['/og-image.jpg']
}
} - 2
Step 2: Dynamische Metadaten konfigurieren
Für dynamische Routen:
• generateMetadata-Funktion verwenden
• Metadaten anhand der Routenparameter generieren
• async-Funktionen zum Datenabruf unterstützt
Beispiel:
export async function generateMetadata({ params }) {
const post = await getPost(params.id)
return {
title: post.title,
description: post.description
}
} - 3
Step 3: Strukturierte Daten hinzufügen
JSON-LD-Format verwenden:
• script-Tag mit type application/ld+json und JSON-Inhalt (in der Implementierung mit Tags umschließen)
• Unterstützt Article, Product, FAQ usw.
• Schema.org-Vokabular verwenden
Beispiel (JSON-Body; auf der Seite per JSON.stringify in script einfügen):
{
"@context": "https://schema.org",
"@type": "Article",
"headline": "Artikeltitel"
}
In Next.js z. B. über next/script ausgeben. - 4
Step 4: sitemap und robots.txt konfigurieren
sitemap.ts erstellen:
• default-Funktion exportieren, die ein Sitemap-Array zurückgibt
• URL, lastModified und changeFrequency aller Seiten enthalten
• Dynamische Generierung unterstützt
robots.ts erstellen:
• Erlaubte/verbotene Crawler konfigurieren
• Sitemap-Pfad setzen
• Crawling-Regeln festlegen - 5
Step 5: Bild-SEO optimieren
Bildoptimierung:
• next/image-Komponente verwenden
• Aussagekräftige alt-Texte hinzufügen
• Bildgrößen konfigurieren (1200×630 für OG-Bilder)
• WebP/AVIF-Format nutzen
• Bild-Strukturdaten (ImageObject) ergänzen - 6
Step 6: Validieren und testen
Validierungstools:
• Google Rich Results Test: Strukturierte Daten prüfen
• Facebook Sharing Debugger: OG-Tags testen
• Twitter Card Validator: Twitter Cards testen
• Google Search Console: Sitemap einreichen und überwachen
Checkliste:
• Jede Seite hat einzigartigen title und description
• OG-Bildgröße korrekt (1200×630)
• Strukturierte Daten formatiert korrekt
• Sitemap in Search Console eingereicht
FAQ
Was hat SSR mit SEO zu tun?
Was ist der Unterschied zwischen Metadata API und Head-Komponente?
Wie konfiguriere ich Metadaten für dynamische Routen?
Beispiel:
export async function generateMetadata({ params }) {
const data = await getData(params.id)
return { title: data.title }
}
Welche Größe sollte ein Open-Graph-Bild haben?
Sind strukturierte Daten Pflicht?
Muss sitemap und robots.txt manuell erstellt werden?
Wie lange dauert es, bis SEO-Wirkung sichtbar wird?
Empfehlung:
1) Sitemap in Google Search Console einreichen
2) Google Rich Results Test nutzen
3) Search Console-Daten laufend überwachen
4) Inhalte regelmäßig aktualisieren
14 Min. Lesezeit · Veröffentlicht am: 19. Dez. 2025 · Aktualisiert am: 14. Juli 2026
Next.js Komplettleitfaden
Wenn du über die Suche hier gelandet bist, kommst du am schnellsten weiter, indem du zum vorherigen oder nächsten Beitrag dieser Serie springst.
Vorheriger
Next.js Core Web Vitals in der Praxis: LCP, FCP und CLS im Griff
Vollständiger Leitfaden zur Optimierung von LCP, FCP und CLS in Next.js – Lighthouse-Score auf 90+ bringen. Mit 10+ Codebeispielen, typischen Fallstricken und Praxis-Tipps.
Teil 27 von 51
Nächster
Next.js Sitemap und robots.txt: Leitfaden für schnelle Suchmaschinen-Indexierung
Vollständige Anleitung zu Sitemap und robots.txt in Next.js: drei Generierungsvarianten, typische Fehler, Integration mit Google Search Console – damit neue Websites schneller indexiert werden.
Teil 29 von 51
Ähnliche Beiträge
Next.js App Router Einstieg: Kernkonzepte und Grundlagen im Überblick

Next.js App Router Einstieg: Kernkonzepte und Grundlagen im Überblick
Next.js 15 in der Praxis: Wie ich an einem Wochenende ein produktionsreifes Blog-System aufgebaut habe


Kommentare
Melde dich mit GitHub an, um einen Kommentar zu hinterlassen