Nikola Arsić

Next.js SEO: App crawlbar und indexierbar machen

Nikola ArsićNext.js
Next.js-SEO-Leitfaden

Sie bauen eine React-App, deployen sie und sehen zu, wie die Analytics flach bleiben. Googles Crawler treffen auf Ihre Seiten, sehen ein leeres <div id="root"></div> und ziehen weiter. Ihre Inhalte werden nie indexiert.

Next.js löst das, indem es vollständiges HTML auf dem Server rendert, bevor irgendetwas den Browser erreicht. Aber Server-Rendering allein reicht nicht - Sie brauchen korrekte Metadaten, strukturierte Daten und Performance, um tatsächlich zu ranken.

Zuerst: prüfen, was der Crawler bekommt

Bevor Sie irgendetwas ändern, sehen Sie sich die Serverantwort an, nicht das gerenderte DOM:

curl -s https://ihre-seite.de/produkt/x | grep -c "Produktbeschreibung"

Steht Ihr Inhalt nicht in dieser Ausgabe, hängt Ihre Indexierung davon ab, ob Google Ihr JavaScript rendert - was es tut, aber verzögert und ohne Garantie. Das ist die einzige Diagnose, die zählt, und die meisten überspringen sie.

Rendering-Strategien

SSR - bei jedem Request auf dem Server gerendert. Für Seiten, die pro Nutzer oder pro Minute anders aussehen: Suchergebnisse, Warenkorb, Kontostände.

SSG - zur Build-Zeit erzeugt. Am schnellsten, und für alles richtig, was sich selten ändert: Blogbeiträge, Landingpages, Dokumentation.

ISR - statisch erzeugt und nach Ablauf eines festgelegten Intervalls im Hintergrund neu erzeugt. Der Kompromiss für große Produktkataloge, die sich täglich ändern, aber nicht pro Request.

Clientseitiges Rendering - für SEO tot. Setzen Sie es hinter dem Login ein, wo Indexierung ohnehin nicht erwünscht ist.

Die Faustregel: Statisch, wo es geht. ISR, wo statisch nicht reicht. SSR nur, wenn der Inhalt wirklich pro Request anders ist.

Metadata API

Im App Router brauchen Sie kein Zusatzpaket. Metadaten werden pro Route exportiert:

export const metadata: Metadata = {
  metadataBase: new URL('https://ihre-seite.de'),
  title: 'Seitentitel',
  description: 'Beschreibung, die zum Klick einlädt.',
  alternates: { canonical: '/produkt/x' },
  openGraph: {
    type: 'website',
    url: 'https://ihre-seite.de/produkt/x',
    images: [{ url: '/og/produkt-x.png', width: 1200, height: 630 }],
  },
}

Für dynamische Routen gibt es generateMetadata. Die Funktion darf dieselben Daten laden wie die Seite - Next dedupliziert den Aufruf:

export async function generateMetadata({ params }): Promise<Metadata> {
  const { slug } = await params
  const produkt = await getProdukt(slug)
  if (!produkt) return {}
  return {
    title: `${produkt.name} kaufen`,
    description: produkt.kurzbeschreibung,
    alternates: { canonical: `/produkt/${slug}` },
  }
}

Beachten Sie: In Next 15 und neuer ist params ein Promise und muss abgewartet werden.

Brauchen Sie noch next-seo?

Kurz: im App Router nein. next-seo wurde für den Pages Router gebaut, wo Metadaten über next/head liefen. Die Metadata API deckt Titles, Descriptions, Canonicals, OpenGraph, Twitter Cards und alternates.languages für Hreflang nativ ab.

Wenn Sie noch auf dem Pages Router sind, bleibt next-seo sinnvoll. Bei einer Migration entfernen Sie es zusammen mit next/head.

Sitemap und robots

Beide als Dateikonvention, nicht als API-Route:

// app/sitemap.ts
export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
  const produkte = await getAlleProdukte()
  return produkte.map((p) => ({
    url: `https://ihre-seite.de/produkt/${p.slug}`,
    lastModified: p.aktualisiertAm,
  }))
}

Ein Hinweis, der mehr Websites betrifft, als man denkt: Setzen Sie lastModified nicht auf new Date(). Dann behauptet jede URL bei jedem Abruf, sie habe sich heute geändert, und Google lernt, Ihr lastmod insgesamt zu ignorieren. Lassen Sie das Feld weg, wenn Sie kein echtes Datum haben.

Strukturierte Daten

<script
  type="application/ld+json"
  dangerouslySetInnerHTML={{ __html: JSON.stringify(schema) }}
/>

JSON.stringify ist wichtig. Ein von Hand zusammengesetzter JSON-String bricht beim ersten Apostroph in einer Beschreibung, und wenn Sie versehentlich ein React-Element hineinreichen, gibt er [object Object] aus - ohne Fehlermeldung, weshalb der Fehler lange unentdeckt bleibt.

Core Web Vitals

LCP - next/image mit priority für das Bild im sichtbaren Bereich (Above the Fold), Schriften über next/font selbst hosten statt sie von fremden Servern zu laden.

CLS - immer Breite und Höhe angeben, Platz für Banner reservieren, Layoutverschiebungen durch nachladende Schriften vermeiden.

INP - Client-Bundles klein halten. Prüfen Sie, wie viele Ihrer Komponenten 'use client' tragen, obwohl sie es nicht brauchen; das ist in App-Router- Projekten der häufigste Grund für zu viel JavaScript.

Die kurze Checkliste

  • Inhalt steht in der Serverantwort, per curl geprüft
  • Canonical auf jeder Route
  • metadataBase gesetzt, sonst sind OG-URLs relativ
  • Sitemap ohne erfundenes lastmod
  • JSON-LD über JSON.stringify, validiert im Rich Results Test
  • hreflang nur für Sprachen, die die Seite tatsächlich hat
  • Bilder mit next/image, Schriften mit next/font

Wird Ihre Next.js-Seite gecrawlt, aber nicht indexiert? Genau diese Diagnose mache ich für Agenturen und Produktteams, samt der Umsetzung danach - beschrieben unter technisches SEO für Next.js und Shops. Ein Beispiel aus der Praxis ist die Fallstudie Ading GmbH: eine Wiener Firmenseite, die ich von einem gehackten WordPress auf Next.js neu gebaut habe - die durchschnittliche Google-Position ging von 40 auf 22.