Next.js SEO: Rendering-Modi und ihre Folgen

Next.js SEO: Rendering-Modi und ihre Folgen

Bei Next.js entscheidet nicht die Meta-Description und nicht das Alt-Attribut darüber, ob eine Seite überhaupt in den Suchindex kommt, sondern die Rendering-Strategie, die für die jeweilige Route gewählt wurde. Next.js bietet mehrere Wege, eine Seite auszuliefern, und jede Route kann sich für einen eigenen entscheiden. Wird die falsche Strategie für eine wichtige Seite gewählt, sieht der Crawler beim ersten Zugriff möglicherweise nur ein leeres Gerüst, während der eigentliche Inhalt erst nachträglich per JavaScript nachgeladen wird. Das ist eine folgenreichere Entscheidung als jede Optimierung, die später auf der fertigen Seite vorgenommen wird.

Dieser Artikel geht nicht noch einmal von Grund auf der Frage nach, warum JavaScript-Rendering für Suchmaschinen überhaupt ein Thema ist – das ist an anderer Stelle behandelt. Hier geht es um die Next.js-spezifischen Entscheidungen: welche Rendering-Modi es gibt und was sie für die Sichtbarkeit konkret bedeuten, warum Ratschläge für den App Router und den Pages Router nicht austauschbar sind, wie Metadaten pro Route erzeugt werden, welche Fehler in der Praxis immer wieder auftauchen, was Bilder mit Core Web Vitals zu tun haben, was bei mehrsprachigem Routing zu beachten ist, und wie man am Ende kontrolliert, was tatsächlich ausgeliefert wird.

Die Rendering-Modi in der Praxis: Was der Crawler tatsächlich sieht

Next.js kennt im Kern vier Auslieferungsarten, und der Unterschied zwischen ihnen ist für SEO nicht akademisch, sondern die Frage, was beim ersten Request im HTML steht. Bei der statischen Generierung (Static Site Generation, SSG) wird die Seite beim Build erzeugt und als fertiges HTML-Dokument ausgeliefert. Der Crawler bekommt exakt den Inhalt, der auch im Browser zu sehen ist, ohne dass ein Skript ausgeführt werden muss. Das ist der sicherste Fall für Sichtbarkeit, hat aber einen Preis: Inhalte, die sich nach dem letzten Build geändert haben, existieren im ausgelieferten HTML noch nicht.

Server-seitiges Rendering (SSR) erzeugt das HTML bei jedem Request neu auf dem Server. Der Crawler sieht auch hier vollständigen Inhalt beim ersten Zugriff, und Änderungen an den zugrunde liegenden Daten wirken sich sofort aus, ohne dass ein neuer Build nötig wäre. Der Preis dafür ist Rechenzeit pro Request und damit potenziell eine langsamere Antwortzeit, was wiederum ein eigener Rankingfaktor ist.

Incremental Static Regeneration (ISR) versucht, beides zu verbinden: Seiten werden statisch ausgeliefert, aber im Hintergrund nach einem festgelegten Intervall oder Trigger neu generiert, ohne dass dafür ein kompletter Build nötig wird. Für SEO ist das häufig der praktikabelste Kompromiss bei großen, sich ändernden Seitenbeständen wie Produktkatalogen oder Preislisten – vorausgesetzt, das Revalidierungsintervall passt tatsächlich zur Änderungsfrequenz der Daten. Ein zu lang gewähltes Intervall bedeutet, dass der Crawler veraltete Preise oder Verfügbarkeiten indexiert.

Client-seitiges Rendering (CSR) liefert beim ersten Request ein weitgehend leeres HTML-Gerüst aus; der eigentliche Inhalt entsteht erst, nachdem der Browser das JavaScript-Bundle geladen und ausgeführt hat. Für SEO ist das die riskanteste Variante, denn der Inhalt existiert für den Crawler erst nach einem zusätzlichen Rendering-Schritt. Googlebot kann JavaScript ausführen, tut das aber in einer zweiten, separaten Rendering-Welle, die zeitversetzt und mit eigenen Ressourcengrenzen läuft. Jede Route, deren Inhalt nur nach Ausführung von Client-JavaScript existiert, ist eine Route, bei der man den Crawler bittet, zusätzliche Arbeit zu leisten – und je mehr Arbeit verlangt wird, desto größer das Risiko, dass Inhalt verzögert oder unvollständig erfasst wird.

  • SSG eignet sich für Inhalte, die sich selten ändern – Blogartikel, Landingpages, Dokumentationsseiten.
  • SSR ist sinnvoll, wenn Inhalt personalisiert ist oder in Echtzeit aktuell sein muss.
  • ISR ist der Mittelweg für große, aber nicht sekündlich wechselnde Datenmengen wie Kataloge.
  • CSR sollte für suchrelevanten Inhalt möglichst vermieden werden; für reine Funktionalität hinter der Anmeldung ist es unproblematisch.

App Router oder Pages Router: Warum die Unterscheidung bei jeder Anleitung mitgelesen werden muss

Next.js hat mit dem App Router (Verzeichnis app/) ein zweites Routing- und Rendering-Modell neben dem ursprünglichen Pages Router (Verzeichnis pages/) eingeführt, und beide unterscheiden sich fundamental darin, wie Rendering und Metadaten funktionieren. Das ist wahrscheinlich der größte Grund, warum sich Next.js-SEO-Ratschläge im Netz widersprechen: Ein Beitrag, der getMetadata, getServerSideProps oder eine _document.js-Datei erklärt, beschreibt den Pages Router. Ein Beitrag, der von einer generateMetadata-Funktion oder von Server Components spricht, beschreibt den App Router. Wird die Anleitung des einen Modells auf ein Projekt im anderen Modell angewendet, funktioniert im besten Fall nichts, im schlechteren Fall entstehen stillschweigend falsche Metadaten oder ein Rendering-Verhalten, das nicht der eigentlichen Absicht entspricht.

Im Pages Router bestimmt die verwendete Datenfunktion die Rendering-Art: getStaticProps erzeugt SSG, mit einem optionalen revalidate-Wert auch ISR, getServerSideProps erzeugt SSR bei jedem Request, und eine Seite ohne beide Funktionen wird weitgehend clientseitig gerendert. Im App Router ist die Standardannahme umgekehrt: Server Components werden von Haus aus auf dem Server gerendert, und ob eine Route statisch oder dynamisch behandelt wird, ergibt sich aus dem verwendeten Code – etwa aus dynamischen Funktionsaufrufen, aus Cookie- oder Header-Zugriffen, oder aus einer expliziten Konfiguration wie dynamic oder revalidate auf Routenebene.

Für die praktische Arbeit bedeutet das: Bevor eine Empfehlung übernommen wird, lohnt sich ein kurzer Check, ob sie zum eigenen Router-Modell passt – insbesondere bei älteren Beiträgen und Forenantworten, die vor der breiten Verfügbarkeit des App Routers entstanden sind. Ein Projekt, das schrittweise vom Pages Router auf den App Router migriert, kann beide Modelle parallel im selben Codebase haben; dann gilt die jeweilige Logik pro Verzeichnis, nicht projektweit einheitlich.

Metadaten pro Route: Title, Description, Canonical und Open Graph

Next.js erzeugt Title, Description, Canonical-Tag und Open-Graph-Daten nicht global, sondern pro Route, was bei großen, templategetriebenen Seitenbeständen der eigentliche Hebel ist. Im App Router geschieht das über ein exportiertes metadata-Objekt oder, für Fälle, die von externen Daten abhängen, über eine asynchrone generateMetadata-Funktion, die vor dem Rendering der Seite ausgeführt wird und Zugriff auf die Routen-Parameter hat. Im Pages Router übernimmt diese Aufgabe typischerweise die next/head-Komponente innerhalb jeder Seite oder eine zentrale _app.js/_document.js-Struktur für gemeinsame Standardwerte.

Für templategetriebene Seiten – etwa Produktdetailseiten oder Blogartikel, die alle dieselbe Komponente nutzen – ist dynamische Metadatengenerierung keine Kür, sondern Voraussetzung. Ein Title, der aus dem tatsächlichen Datensatz gebaut wird, zum Beispiel Produktname plus Kategorie plus Marke, verhindert, dass Hunderte oder Tausende Seiten denselben generischen Titel tragen, was sowohl die Klickrate als auch das Rankingpotenzial jeder einzelnen Seite verwässert. Dasselbe gilt für den Canonical-Tag: Er muss aus der tatsächlichen, kanonischen URL der jeweiligen Ressource gebaut werden, nicht aus einer starren Vorlage, sonst kann bei parametrisierten oder facettierten URLs versehentlich auf die falsche Variante verwiesen werden.

Open-Graph-Bilder lassen sich in Next.js sowohl statisch hinterlegen als auch dynamisch generieren, bis hin zu Bildern, die zur Laufzeit aus Routen-Daten zusammengesetzt werden. Für das Teilen in sozialen Netzwerken ist das relevant, wirkt sich aber auch indirekt auf SEO aus, weil ein aussagekräftiges Vorschaubild die Klickrate aus sozialen Kanälen und Messengern erhöht – ein Signal, das die Suchmaschine zwar nicht direkt misst, das aber den Traffic beeinflusst, den sie sehr wohl beobachtet.

Die immer wiederkehrenden Fehler

Bestimmte Fehler tauchen in Next.js-Projekten so regelmäßig auf, dass sie sich fast als Muster beschreiben lassen.

  • Soft 404: Eine Route für einen nicht existierenden Datensatz – ein gelöschtes Produkt, eine falsche ID – rendert eine „Nicht gefunden“-Ansicht, liefert aber weiterhin den Statuscode 200 aus, weil niemand explizit die notFound()-Funktion aufgerufen oder einen 404-Status gesetzt hat. Für den Crawler sieht das wie eine gültige, indexierbare Seite mit dünnem oder leerem Inhalt aus. Im App Router löst notFound() das korrekt auf, im Pages Router ist es das { notFound: true }-Rückgabeobjekt in getStaticProps oder getServerSideProps.
  • Ein Title, der beim clientseitigen Routenwechsel nicht aktualisiert wird: Wechselt der Nutzer per Client-Navigation zwischen Seiten, ohne dass ein vollständiges Neuladen stattfindet, kann der Title-Tag hängen bleiben, wenn die Metadaten-Logik nicht korrekt an den Routenwechsel gekoppelt ist. Für den Nutzer ist das nur ein Detail im Browser-Tab; für die Suchmaschine ist der Title-Tag eines der stärksten Relevanzsignale, und ein falscher Titel im tatsächlich gerenderten HTML wird entsprechend ernst genommen.
  • Hydration-Probleme: Stimmt das serverseitig gerenderte HTML nicht exakt mit dem überein, was React beim ersten clientseitigen Rendering erwartet, entstehen Hydration-Fehler. Im besten Fall bleibt es bei einer Konsolenwarnung, im schlechteren Fall werden Teile des Inhalts nach der Hydration neu aufgebaut oder verschwinden kurzzeitig, was Layout-Verschiebungen und damit einen schlechteren Cumulative-Layout-Shift-Wert erzeugt.
  • Redirects, die im Client statt auf dem Server umgesetzt werden: Ein useEffect-Hook, der nach dem Laden per router.push() umleitet, sorgt dafür, dass der Crawler zunächst die ursprüngliche, eigentlich veraltete Seite sieht und die Weiterleitung im ausgelieferten HTML gar nicht auftaucht. Eine echte 301- oder 308-Weiterleitung gehört in die next.config.js-Konfiguration oder in eine Middleware, nicht in eine einzelne Komponente.
  • Sitemaps, die bei statisch generierten Seiten veralten: Eine zum Build-Zeitpunkt erzeugte Sitemap kennt nur die zu diesem Zeitpunkt existierenden Routen. Kommen danach neue Seiten hinzu, ohne dass ein neuer Build oder eine dynamische Sitemap-Route – sitemap.ts im App Router beziehungsweise ein API-Handler im Pages Router – das nachträgt, fehlen diese Seiten in der Sitemap, bis der nächste Build läuft. Bei seltenen Deployments kann das Tage oder Wochen dauern.

Bilder und Core Web Vitals: Die Komponente hilft, ersetzt aber keine Entscheidung

Die next/image-Komponente übernimmt einen großen Teil der Bildoptimierung automatisch: passende Formate, responsive Größen über das srcset-Attribut, verzögertes Laden für Bilder außerhalb des sichtbaren Bereichs. Das löst aber nicht automatisch das Problem, das für Core Web Vitals am meisten zählt – den Largest Contentful Paint (LCP). Das größte sichtbare Element beim ersten Laden, oft ein Hero-Bild oder ein großes Produktbild, muss explizit als priority markiert werden, damit es nicht dem verzögerten Standardverhalten unterliegt, das für Bilder außerhalb des ersten Viewports sinnvoll ist, für das LCP-Bild aber kontraproduktiv wirkt.

Ebenso wichtig sind die width- und height-Angaben, die next/image benötigt, um den Platz für ein Bild bereits vor dem eigentlichen Laden zu reservieren. Fehlen sie oder werden falsche Seitenverhältnisse angegeben, verschiebt sich beim Laden weiterhin der Seiteninhalt – exakt das, was Cumulative Layout Shift misst und was next/image eigentlich verhindern soll. Die Komponente ist ein Werkzeug, keine Garantie; sie muss pro Bild richtig konfiguriert werden, insbesondere für das eine Bild, das über den LCP-Wert einer Seite entscheidet.

Internationalisiertes Routing: Für deutschsprachige Projekte oft der entscheidende Punkt

Next.js unterstützt mehrsprachiges Routing über Sprachpräfixe in der URL, etwa /de/ und /en/, über domainbasierte Zuordnung oder über eine Kombination aus beidem, konfiguriert über die i18n-Einstellung im Pages Router beziehungsweise über eine eigene Routing-Struktur im App Router. Für ein Publikum, das überwiegend auf Deutsch sucht, ist diese Konfiguration oft wichtiger als für ein rein englischsprachiges Projekt, weil hier häufiger mehrere Sprachversionen, teils auch mehrere deutschsprachige Länder wie Deutschland, Österreich und die Schweiz, parallel bedient werden müssen.

Der SEO-kritische Teil ist dabei weniger das Routing selbst als die korrekte Verknüpfung der Sprachversionen über hreflang-Angaben, die auf die jeweils passende alternative Sprachversion derselben Seite verweisen – inklusive einer x-default-Variante für Nutzer, deren Sprache keiner der explizit unterstützten Varianten entspricht. Werden hreflang-Verweise falsch gesetzt oder nicht gegenseitig bestätigt, etwa wenn Seite A auf Seite B verweist, B aber nicht zurück auf A, ignoriert die Suchmaschine die Angaben im Zweifel komplett. Bei automatischer Spracherkennung über Browser-Einstellungen oder Standort ist zusätzlich wichtig, dass eine Weiterleitung den Crawler nicht daran hindert, jede Sprachversion einzeln zu erreichen und zu indexieren – eine serverseitige Zwangsumleitung kann sonst dazu führen, dass am Ende nur eine einzige Sprachversion überhaupt gecrawlt wird.

Kontrollieren, was wirklich ausgeliefert wird

Da all diese Entscheidungen – Rendering-Modus, Metadaten, Redirects, Sitemap – letztlich in HTML münden, das der Crawler tatsächlich empfängt, lohnt sich vor jedem Vertrauen in die eigene Konfiguration ein direkter Blick auf das ausgelieferte Ergebnis. Das eigene Framework-Wissen ist kein Ersatz für die Kontrolle des tatsächlichen Outputs, weil Konfigurationsfehler, veraltete Deployments oder das unerwartete Zusammenspiel mehrerer Einstellungen genau dort sichtbar werden, wo die Theorie versagt.

Der einfachste Test ist ein Rohabruf der Seite ohne JavaScript-Ausführung, etwa mit curl oder einem vergleichbaren Kommandozeilenwerkzeug, gefolgt von einer Suche im zurückgegebenen HTML nach dem eigentlichen Textinhalt, dem Title-Tag, dem Canonical-Link und den Open-Graph-Angaben. Taucht der erwartete Inhalt dort nicht auf, weiß man mit Sicherheit, dass er clientseitig nachgeladen wird, unabhängig davon, was im Browser mit aktiviertem JavaScript zu sehen ist. Ergänzend zeigen die Google Search Console über den URL-Prüfungsbericht mit gerendertem HTML sowie die Netzwerk- und Elemente-Ansicht der Browser-Entwicklertools, ob das, was letztlich indexiert wird, tatsächlich dem entspricht, was beabsichtigt war. Diese Kontrolle ersetzt keine der vorherigen Entscheidungen, aber sie ist der einzige Weg, zuverlässig zu bestätigen, dass sie auch so umgesetzt wurden wie geplant.

Häufige Fragen

Ist der App Router grundsätzlich besser für SEO als der Pages Router?

Nicht grundsätzlich – beide können vollständiges HTML für Crawler ausliefern, wenn Server-Rendering korrekt konfiguriert ist. Der App Router macht Server-Rendering zur Standardannahme, was bestimmte Fehler seltener macht, ersetzt aber keine bewusste Entscheidung pro Route.

Muss ich für SEO immer statische Generierung statt SSR verwenden?

Nein. SSG ist vorteilhaft, wenn sich Inhalte selten ändern, weil der Crawler dann garantiert aktuelles, fertiges HTML sieht. Ändern sich Daten häufig oder pro Nutzer, liefert SSR ebenso vollständiges HTML, nur eben bei jedem Request neu erzeugt.

Reicht next/image aus, um gute Core-Web-Vitals-Werte zu erreichen?

Die Komponente hilft bei Format, Größe und Ladeverhalten, entscheidet aber nicht automatisch, welches Bild priorisiert geladen wird. Das größte sichtbare Bild einer Seite muss weiterhin bewusst markiert und mit korrekten Abmessungen versehen werden.

Wie erkenne ich einen Soft-404-Fehler in einer Next.js-Anwendung?

Ein Rohabruf der URL zeigt eine „Nicht gefunden“-Meldung im HTML, während der HTTP-Statuscode trotzdem 200 lautet statt der erwarteten 404. Das lässt sich mit curl -I oder einem einfachen Header-Check schnell prüfen.

Warum taucht eine neue Seite nicht in der Sitemap auf?

Bei einer rein statisch zum Build-Zeitpunkt erzeugten Sitemap fehlen Seiten, die danach hinzugekommen sind, bis zum nächsten Build. Eine dynamische Sitemap-Route, die zur Laufzeit generiert wird, vermeidet dieses Problem.

Alle Artikel