Design wechseln

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

Easton editorial illustration: hydration gauge console

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 Suchergebnissen
  • description: 150–160 Zeichen – ideal für Google-Snippets
  • openGraph.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:

  1. Organization – Unternehmen (Startseite)
  2. BlogPosting – Blogartikel (pro Artikel)
  3. Product – Produkte (E-Commerce: Preis, Bewertung, Lager)
  4. FAQPage – FAQ (direkt in Suchergebnissen aufklappbar)
  5. 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:

  1. Google Rich Results Test (https://search.google.com/test/rich-results)

    • URL eingeben – Google zeigt mögliche Rich Results
    • Fehler werden konkret benannt
  2. 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 rechts
  • summary_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:

  1. 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
  2. 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
  3. 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:

  1. In Google Search Console einreichen (https://search.google.com/search-console)
  2. 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 noindex oder Disallow

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:

  1. next/image statt <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)
  1. alt ist 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:

  1. HTML-Quellcode

    • F12 → Elements → <head> prüfen
    • <title>, <meta name="description">, OG-Tags korrekt?
  2. Sitemap und robots testen

    • https://yourdomain.com/sitemap.xml aufrufen
    • https://yourdomain.com/robots.txt prüfen
  3. Strukturierte Daten validieren

    • Google Rich Results Test für mehrere Seiten
    • Keine Fehler oder Warnungen
  4. Social Sharing testen

    • Facebook Sharing Debugger und Twitter Card Validator
    • Bild, Titel, Beschreibung korrekt
  5. 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 via generateMetadata
  • 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. 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. 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. 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. 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. 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. 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?
SSR (serverseitiges Rendering) erzeugt HTML auf dem Server – SEO erfordert aber zusätzlich korrekte Meta-Tags, strukturierte Daten und Open Graph. SSR ist nicht gleichbedeutend mit SEO-freundlich; die Metadata API muss aktiv konfiguriert werden, damit Suchmaschinen Inhalte richtig verstehen und indexieren.
Was ist der Unterschied zwischen Metadata API und Head-Komponente?
Die Metadata API ist der empfohlene Weg in Next.js 15: typsicher, unterstützt statische und dynamische Metadaten und vermeidet doppelte Tags automatisch. Die Head-Komponente ist der React-Weg und erfordert manuelles Management – fehleranfälliger. Neue Projekte sollten die Metadata API nutzen.
Wie konfiguriere ich Metadaten für dynamische Routen?
generateMetadata verwenden, params entgegennehmen, async Daten abrufen und ein metadata-Objekt zurückgeben.

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?
Empfohlen: 1200×630 Pixel – ein Universalformat für Facebook, Twitter und LinkedIn. Dateigröße unter 1 MB halten, Format JPEG oder PNG.
Sind strukturierte Daten Pflicht?
Nein, aber dringend empfohlen. Sie ermöglichen Rich Results (Bewertungen, Preise, FAQ usw.) und steigern die Klickrate. Google, Bing und andere unterstützen JSON-LD.
Muss sitemap und robots.txt manuell erstellt werden?
Nein. Next.js unterstützt sitemap.ts und robots.ts – daraus werden sitemap.xml und robots.txt automatisch generiert. sitemap.ts kann alle URLs dynamisch erzeugen, robots.ts Crawling-Regeln festlegen.
Wie lange dauert es, bis SEO-Wirkung sichtbar wird?
In der Regel 1–3 Monate. Suchmaschinen brauchen Zeit zum Crawlen und Indexieren.

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

Kommentare

Melde dich mit GitHub an, um einen Kommentar zu hinterlassen

Easton BlogEaston Blog