Nikola Arsić

Next.js SEO: Ihre App vollständig crawlbar und indexierbar machen

Nikola ArsićNext.js
Next.js SEO

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. Schnellstmöglich, und für alles richtig, was sich selten ändert: Blogbeiträge, Landingpages, Dokumentation.

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

Client-seitig - für SEO tot. Verwenden Sie es hinter dem Login, 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 generateMetadata, das dieselben Daten holen darf 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 Dateikonventionen, nicht als API-Routen:

// 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 - stillschweigend, und deshalb bleibt es kaputt.

Core Web Vitals

LCP - next/image mit priority für das Bild über der Falz, 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, keine Layoutverschiebung durch nachladende Schriften.

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

Wenn Sie eine Next.js-Seite haben, die gecrawlt aber nicht indexiert wird - das ist die Diagnose, die ich für Agenturen und Produktteams mache, samt der Umsetzung danach.