← Zurück zur Übersicht

Warum Next.js für SEO besser ist als reines React

React hat ein fundamentales SEO-Problem: Es rendert standardmäßig im Browser (Client-Side Rendering, CSR). Das bedeutet: Wenn Google deine Seite crawlt, sieht es zunächst ein leeres

— und muss den JavaScript-Code erst ausführen, um den Inhalt zu sehen.

Google kann JavaScript rendern — aber mit Einschränkungen:

Merksatz: „Googlebot kann JavaScript. Aber er ist langsam, ungeduldig und rendert in einer Warteschlange. Was er sofort lesen kann, indexiert er sofort."

Next.js löst dieses Problem durch Server-Side Rendering (SSR) und Static Site Generation (SSG): Der HTML-Code wird bereits auf dem Server oder zur Build-Zeit generiert und als vollständiges HTML an den Browser (und an Googlebot) ausgeliefert. Kein Warten auf JavaScript-Execution.

| Aspekt | Reines React (CSR) | Next.js (SSR/SSG) |

|--------|-------------------|-------------------|

| Erster HTML-Response | Leeres

+ JS-Bundle | Vollständiges HTML |

| Googlebot-Indexierung | Verzögert (JavaScript Rendering Queue) | Sofort |

| Core Web Vitals (LCP) | Schlecht (JS muss erst laden) | Gut (HTML direkt da) |

| Social Media Previews | Oft leer (kein SSR) | Korrekt (Meta-Tags im HTML) |

| Time to First Byte | Schnell (nur statische Datei) | Schnell (SSG) / Mittel (SSR) |

Rendering-Strategien und ihre SEO-Auswirkungen

Next.js bietet vier Rendering-Strategien. Jede hat unterschiedliche SEO-Implikationen:

Static Site Generation (SSG) — der SEO-Champion

Seiten werden zur Build-Zeit generiert. Das Ergebnis ist pures HTML, das vom CDN ausgeliefert wird. Perfekt für Content, der sich selten ändert.

// app/blog/[slug]/page.tsx
export async function generateStaticParams() {
  const posts = await getAllPosts()
  return posts.map((post) => ({ slug: post.slug }))
}

export default async function BlogPost({ params }: { params: { slug: string } }) {
  const post = await getPost(params.slug)
  return <article>{post.content}</article>
}

SEO-Vorteil: Schnellste Ladezeit (direkt vom CDN), beste Core Web Vitals, sofort crawlbar.

Nachteil: Bei Inhaltsänderungen muss neu gebaut werden — oder ISR nutzen.

Server-Side Rendering (SSR) — für dynamische Inhalte

Seiten werden bei jedem Request auf dem Server generiert. Sinnvoll für personalisierte oder häufig wechselnde Inhalte.

// app/produkt/[id]/page.tsx
// In Next.js 14+ App Router ist SSR der Default für dynamische Routen
export default async function Produkt({ params }: { params: { id: string } }) {
  const produkt = await getProdukt(params.id)
  return <ProduktSeite data={produkt} />
}

SEO-Vorteil: Immer aktueller Inhalt, vollständiges HTML für Crawlers.

Nachteil: Höhere Server-Last, langsamere Time to First Byte als SSG.

Incremental Static Regeneration (ISR) — das Beste aus beiden Welten

Seiten werden statisch generiert, aber in definierten Intervallen im Hintergrund aktualisiert. Der Nutzer sieht die gecachte Version, während der Server die neue Version baut.

// app/blog/[slug]/page.tsx
export const revalidate = 3600 // Alle 60 Minuten revalidieren

export default async function BlogPost({ params }: { params: { slug: string } }) {
  const post = await getPost(params.slug)
  return <article>{post.content}</article>
}

SEO-Vorteil: CDN-Geschwindigkeit + aktuelle Inhalte. Perfekt für Blogs, Produktseiten, Landingpages.

Client-Side Rendering (CSR) — der SEO-Killer

Inhalte werden erst im Browser gerendert. Für SEO-relevante Seiten nicht verwenden.

Wann CSR OK ist: Dashboard-Bereiche hinter Login, interaktive Tools, User-spezifische Inhalte die nicht indexiert werden sollen.

Merksatz: „SSG für alles was sich selten ändert. ISR für alles was sich regelmäßig ändert. SSR für alles was sich bei jedem Request ändert. CSR für alles was Google nie sehen soll."

Meta-Tags und Open Graph in Next.js richtig setzen

Die Metadata API (Next.js 14+ App Router)

Next.js hat mit dem App Router eine elegante Metadata API eingeführt. Statische und dynamische Meta-Tags lassen sich direkt in der Page-Komponente definieren:

Statische Metadata:

// app/ueber-uns/page.tsx
import type { Metadata } from 'next'

export const metadata: Metadata = {
  title: 'Über uns | while.chat',
  description: 'Wir machen SEO, Marketing und Webentwicklung für KMU im DACH-Raum.',
  openGraph: {
    title: 'Über uns | while.chat',
    description: 'SEO, Marketing und Webentwicklung für KMU.',
    url: 'https://while.chat/ueber-uns',
    type: 'website',
    images: [{ url: 'https://while.chat/images/ueber-uns-og.jpg', width: 1200, height: 630 }],
  },
  twitter: {
    card: 'summary_large_image',
  },
  alternates: {
    canonical: 'https://while.chat/ueber-uns',
  },
}

Dynamische Metadata:

// app/blog/[slug]/page.tsx
import type { Metadata } from 'next'

export async function generateMetadata({ params }: { params: { slug: string } }): Promise<Metadata> {
  const post = await getPost(params.slug)

  return {
    title: `${post.title} | while.chat`,
    description: post.excerpt,
    openGraph: {
      title: post.title,
      description: post.excerpt,
      url: `https://while.chat/blog/${params.slug}`,
      type: 'article',
      publishedTime: post.publishedAt,
      images: [{ url: post.ogImage }],
    },
    alternates: {
      canonical: `https://while.chat/blog/${params.slug}`,
    },
  }
}

Wichtig: Jede Seite braucht einen einzigartigen title und eine einzigartige description. Die Metadata API sorgt automatisch dafür, dass diese ins des HTML gerendert werden — ohne manuelles -Management.

Title-Tag Best Practices

  • Maximal 60 Zeichen (Google kürzt sonst ab)
  • Fokus-Keyword möglichst am Anfang
  • Brand-Name am Ende mit Separator: Titel | Brand
  • Jede Seite ein eigener Title — kein Duplicate

Meta-Description Best Practices

  • 150–160 Zeichen
  • Enthält das Fokus-Keyword (wird von Google gefettet)
  • Call-to-Action oder Nutzenversprechen
  • Kein Duplicate über mehrere Seiten

Technisches SEO in Next.js: Sitemap, Robots, Canonical

XML-Sitemap automatisch generieren

Next.js unterstützt seit Version 13.3 eine native Sitemap-Generierung:

// app/sitemap.ts
import { MetadataRoute } from 'next'

export default async function sitemap(): MetadataRoute.Sitemap {
  const posts = await getAllPosts()

  const blogEntries = posts.map((post) => ({
    url: `https://while.chat/blog/${post.slug}`,
    lastModified: post.updatedAt,
    changeFrequency: 'weekly' as const,
    priority: 0.8,
  }))

  return [
    {
      url: 'https://while.chat',
      lastModified: new Date(),
      changeFrequency: 'daily',
      priority: 1,
    },
    {
      url: 'https://while.chat/ueber-uns',
      lastModified: new Date(),
      changeFrequency: 'monthly',
      priority: 0.5,
    },
    ...blogEntries,
  ]
}

Die Sitemap wird automatisch unter /sitemap.xml ausgeliefert. Bei großen Seiten mit 50.000+ URLs kannst du generateSitemaps() nutzen, um mehrere Sitemaps zu erzeugen.

robots.txt konfigurieren

// app/robots.ts
import { MetadataRoute } from 'next'

export default function robots(): MetadataRoute.Robots {
  return {
    rules: {
      userAgent: '*',
      allow: '/',
      disallow: ['/api/', '/admin/', '/dashboard/'],
    },
    sitemap: 'https://while.chat/sitemap.xml',
  }
}

Canonical URLs setzen

Canonical URLs sind essenziell, um Duplicate Content zu vermeiden — besonders bei Seiten mit URL-Parametern (Filter, Sortierung, Paginierung).

// In der Metadata API:
export const metadata: Metadata = {
  alternates: {
    canonical: 'https://while.chat/blog/next-js-seo',
  },
}

Regel: Jede indexierbare Seite braucht eine Canonical URL. Die Canonical zeigt immer auf die „Hauptversion" der Seite — ohne Tracking-Parameter, ohne Session-IDs.

Structured Data (JSON-LD) in Next.js einbinden

Structured Data hilft Google, den Inhalt deiner Seite zu verstehen — und ermöglicht Rich Snippets in den Suchergebnissen (FAQ-Dropdowns, How-To-Schritte, Bewertungssterne, Breadcrumbs).

// app/blog/[slug]/page.tsx
export default async function BlogPost({ params }: { params: { slug: string } }) {
  const post = await getPost(params.slug)

  const jsonLd = {
    '@context': 'https://schema.org',
    '@type': 'Article',
    headline: post.title,
    description: post.excerpt,
    author: { '@type': 'Organization', name: 'while.chat' },
    datePublished: post.publishedAt,
    dateModified: post.updatedAt,
    image: post.ogImage,
    mainEntityOfPage: `https://while.chat/blog/${params.slug}`,
  }

  return (
    <>
      <script
        type="application/ld+json"
        dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
      />
      <article>{post.content}</article>
    </>
  )
}

Die wichtigsten Schema-Typen für Content-Websites:

| Schema-Typ | Wofür | Rich-Snippet-Effekt |

|------------|-------|---------------------|

| Article | Blog-Posts, Artikel | Datum, Autor in den SERPs |

| FAQPage | FAQ-Sektionen | Expandierbare Fragen in Google |

| HowTo | Anleitungen, Tutorials | Schritte als Liste in Google |

| BreadcrumbList | Navigation / Breadcrumbs | Breadcrumb-Pfad in den SERPs |

| LocalBusiness | Lokale Unternehmen | Knowledge Panel, Maps |

| Organization | Unternehmensdaten | Logo, Social-Links im Panel |

Merksatz: „Structured Data ist kein Ranking-Faktor. Aber es ist ein CTR-Faktor — und CTR beeinflusst Rankings indirekt."

Core Web Vitals in Next.js optimieren

LCP (Largest Contentful Paint) — das Hauptbild schneller laden

LCP misst, wie schnell das größte sichtbare Element geladen wird. Ziel: unter 2,5 Sekunden.

Next.js Hebel:

  • next/image verwenden statt : Automatische Lazy-Loading, WebP/AVIF-Konvertierung, responsive Sizes
  • priority-Prop für Above-the-Fold-Bilder setzen (deaktiviert Lazy Loading)
  • next/font für Schriften: Eliminiert Layout Shift durch Font-Loading
import Image from 'next/image'

// Hero-Bild: priority = true für sofortiges Laden
<Image
  src="/hero.jpg"
  alt="Next.js SEO Guide Hero"
  width={1200}
  height={630}
  priority
  sizes="100vw"
/>

CLS (Cumulative Layout Shift) — Sprünge vermeiden

CLS misst visuelle Stabilität. Ziel: unter 0,1.

Next.js Hebel:

  • width und height bei allen Images definieren (reserviert Platz)
  • next/font mit display: swap und adjustFontFallback
  • Keine dynamischen Inhalte über dem Fold einblenden (Ads, Banner)
import { Inter } from 'next/font/google'

const inter = Inter({
  subsets: ['latin'],
  display: 'swap',        // Zeigt Fallback-Font bis Custom-Font geladen
  adjustFontFallback: true // Minimiert Layout Shift beim Font-Swap
})

INP (Interaction to Next Paint) — Interaktionen schnell machen

INP misst die Reaktionszeit auf Nutzer-Interaktionen. Ziel: unter 200ms.

Next.js Hebel:

  • Schwere Client-Komponenten mit React.lazy() und Suspense splitten
  • 'use client' nur dort wo nötig — alles andere als Server Component belassen
  • Third-Party Scripts mit next/script und strategy="lazyOnload" laden
import Script from 'next/script'

// Analytics erst nach vollständigem Laden
<Script
  src="https://www.googletagmanager.com/gtag/js?id=G-XXXXXXX"
  strategy="lazyOnload"
/>

Die 10 häufigsten Next.js SEO-Fehler

| # | Fehler | Lösung |

|---|--------|--------|

| 1 | Keine Metadata API nutzen, leerer </code> | <code>generateMetadata</code> für jede Route |</p> <p>| 2 | Alle Seiten als CSR (Client Components) | Server Components als Default |</p> <p>| 3 | Keine Sitemap | <code>app/sitemap.ts</code> anlegen |</p> <p>| 4 | Fehlende Canonical URLs | <code>alternates.canonical</code> in Metadata |</p> <p>| 5 | <code><img></code> statt <code>next/image</code> | Image-Component mit <code>priority</code> für ATF |</p> <p>| 6 | Custom Fonts ohne <code>next/font</code> | <code>next/font</code> für Zero-CLS-Fonts |</p> <p>| 7 | robots.txt vergessen | <code>app/robots.ts</code> anlegen |</p> <p>| 8 | JSON-LD fehlt | <code><script type="application/ld+json"></code> einbauen |</p> <p>| 9 | Third-Party Scripts blockieren Rendering | <code>next/script</code> mit <code>lazyOnload</code> |</p> <p>| 10 | Kein <code>alt</code>-Text bei Bildern | Beschreibendes <code>alt</code> für jedes Image |</p> <div class="faq-section"> <p class="faq-label">Häufige Fragen</p> <p class="faq-title">FAQ</p> <div class="faq-item"> <div class="faq-q" role="button" tabindex="0" aria-expanded="false">Kann Google JavaScript-Seiten indexieren?<span class="faq-chevron">↓</span></div> <div class="faq-a"><div class="faq-a-inner">Ja, aber mit Einschränkungen. Googlebot nutzt eine relativ aktuelle Chrome-Version zum Rendern, aber JavaScript-Rendering läuft in einer Warteschlange und kann Tage dauern. SSR/SSG-Seiten werden sofort indexiert, CSR-Seiten mit Verzögerung. Für SEO-relevante Inhalte immer SSR oder SSG verwenden.</div></div> </div><div class="faq-item"> <div class="faq-q" role="button" tabindex="0" aria-expanded="false">Was ist besser für SEO: SSG oder SSR?<span class="faq-chevron">↓</span></div> <div class="faq-a"><div class="faq-a-inner">SSG ist in den meisten Fällen besser: schnellere Ladezeiten, bessere Core Web Vitals, CDN-Caching. SSR ist nur dann sinnvoll, wenn sich Inhalte bei jedem Request ändern müssen (z. B. personalisierte Preise). Für Content-Websites und Blogs ist ISR (Incremental Static Regeneration) der Sweet Spot.</div></div> </div><div class="faq-item"> <div class="faq-q" role="button" tabindex="0" aria-expanded="false">Brauche ich ein SEO-Plugin für Next.js?<span class="faq-chevron">↓</span></div> <div class="faq-a"><div class="faq-a-inner">Nein. Die eingebaute Metadata API, <code>app/sitemap.ts</code> und <code>app/robots.ts</code> decken die Grundlagen ab. Für JSON-LD kann das Package <code>next-seo</code> oder <code>schema-dts</code> hilfreich sein, ist aber nicht zwingend nötig. Die meisten „SEO-Plugins" für Next.js wrappen nur die eingebauten Features.</div></div> </div><div class="faq-item"> <div class="faq-q" role="button" tabindex="0" aria-expanded="false">Wie teste ich, ob meine Next.js-Seite SEO-ready ist?<span class="faq-chevron">↓</span></div> <div class="faq-a"><div class="faq-a-inner">Vier Tools reichen: (1) Google Search Console — Indexierung und Crawl-Errors, (2) Google Rich Results Test — Structured Data validieren, (3) PageSpeed Insights — Core Web Vitals prüfen, (4) <code>curl -A Googlebot URL</code> im Terminal — prüfen ob HTML vollständig ausgeliefert wird.</div></div> </div><div class="faq-item"> <div class="faq-q" role="button" tabindex="0" aria-expanded="false">Funktioniert Next.js SEO auch mit dem Pages Router?<span class="faq-chevron">↓</span></div> <div class="faq-a"><div class="faq-a-inner">Ja, aber die Metadata API ist nur im App Router verfügbar. Im Pages Router nutzt du <code>next/head</code> für Meta-Tags und <code>getStaticProps</code>/<code>getServerSideProps</code> für SSG/SSR. Für neue Projekte: App Router ist die Zukunft.</div></div> </div> </div> <div class="sources"> <h2>Quellen & Vertiefung</h2> <ul><li>Erlhofer, S. (2024). <em>Suchmaschinen-Optimierung: Das umfassende Handbuch.</em> 11. Aufl., Rheinwerk Computing. — Kap. 8.12.4 (Core Web Vitals), Kap. 10 (Onpage-Optimierung)</li> <li>Deges, F. (2023). <em>Grundlagen des E-Commerce.</em> 2. Aufl., Springer Gabler. — Kap. 10.1.3 (Conversion Rate)</li> <li>Next.js Docs — Metadata API: <a href="https://nextjs.org/docs/app/building-your-application/optimizing/metadata" target="_blank" rel="noopener">https://nextjs.org/docs/app/building-your-application/optimizing/metadata</a></li> <li>Next.js Docs — Sitemap: <a href="https://nextjs.org/docs/app/api-reference/file-conventions/metadata/sitemap" target="_blank" rel="noopener">https://nextjs.org/docs/app/api-reference/file-conventions/metadata/sitemap</a></li> <li>Next.js Docs — Image Optimization: <a href="https://nextjs.org/docs/app/building-your-application/optimizing/images" target="_blank" rel="noopener">https://nextjs.org/docs/app/building-your-application/optimizing/images</a></li> <li>Google Search Central — JavaScript SEO: <a href="https://developers.google.com/search/docs/crawling-indexing/javascript/javascript-seo-basics" target="_blank" rel="noopener">https://developers.google.com/search/docs/crawling-indexing/javascript/javascript-seo-basics</a></li> <li>Google — Core Web Vitals: <a href="https://web.dev/vitals/" target="_blank" rel="noopener">https://web.dev/vitals/</a></li> <li>Google — Rich Results Test: <a href="https://search.google.com/test/rich-results" target="_blank" rel="noopener">https://search.google.com/test/rich-results</a></li> <li>Vercel Blog — Next.js SEO: <a href="https://vercel.com/blog/nextjs-seo" target="_blank" rel="noopener">https://vercel.com/blog/nextjs-seo</a></li> <li>Schema.org — Structured Data: <a href="https://schema.org/" target="_blank" rel="noopener">https://schema.org/</a></li></ul> </div> </article> </main> <footer class="footer"> <div class="footer-brand">while.chat</div> <p>Verstehen. Anwenden. Wachsen. — erstellt in Bochum</p> <div class="footer-links"> <a href="/datenschutz">Datenschutz</a> <a href="/impressum">Impressum</a> </div> <div class="footer-social"> <a href="https://www.facebook.com/profile.php?id=61577505480981" target="_blank" rel="noopener noreferrer" aria-label="Facebook"><svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M24 12.073c0-6.627-5.373-12-12-12s-12 5.373-12 12c0 5.99 4.388 10.954 10.125 11.854v-8.385H7.078v-3.47h3.047V9.43c0-3.007 1.792-4.669 4.533-4.669 1.312 0 2.686.235 2.686.235v2.953H15.83c-1.491 0-1.956.925-1.956 1.874v2.25h3.328l-.532 3.47h-2.796v8.385C19.612 23.027 24 18.062 24 12.073z"/></svg></a> </div> </footer> <script>window.va=window.va||function(){(window.vaq=window.vaq||[]).push(arguments)};</script> <script defer src="/_vercel/insights/script.js"></script> <script defer src="/_vercel/speed-insights/script.js"></script> <script> var nav=document.getElementById('nav'); if(nav)window.addEventListener('scroll',function(){nav.classList.toggle('scrolled',window.scrollY>40)},{passive:true}); var bar=document.getElementById('progress-bar'); if(bar)window.addEventListener('scroll',function(){var t=document.documentElement.scrollHeight-window.innerHeight;bar.style.width=(window.scrollY/t*100)+'%'},{passive:true}); var burger=document.getElementById('burger'),overlay=document.getElementById('nav-overlay'); if(burger&&overlay){burger.addEventListener('click',function(){var o=burger.classList.toggle('open');overlay.classList.toggle('open');burger.setAttribute('aria-expanded',o);overlay.setAttribute('aria-hidden',!o);document.body.style.overflow=o?'hidden':''});overlay.querySelectorAll('a').forEach(function(a){a.addEventListener('click',function(){burger.classList.remove('open');overlay.classList.remove('open');burger.setAttribute('aria-expanded','false');overlay.setAttribute('aria-hidden','true');document.body.style.overflow=''})})} document.querySelectorAll('.faq-q').forEach(function(q){ q.addEventListener('click',function(){ var item=q.parentElement; var wasOpen=item.classList.contains('open'); document.querySelectorAll('.faq-item').forEach(function(i){i.classList.remove('open');i.querySelector('.faq-q').setAttribute('aria-expanded','false')}); if(!wasOpen){item.classList.add('open');q.setAttribute('aria-expanded','true')} }); }); </script> <script defer src="/components/cookie-consent.js"></script> </body> </html>