TechnologieWeb

Automatisch aktuelle Sitemaps im Next.js-Portfolio

netcomputing.de Redaktion26. August 202610 Min. Lesezeit

Zusammenfassung

Wer Dutzende Next.js-Websites pflegt, verliert leicht den Überblick über veraltete, gelöschte oder auf noindex gesetzte Adressen in der Sitemap. Next.js erzeugt die Sitemap über die Dateikonvention sitemap.ts direkt aus der echten Datenquelle, etwa einer JSON-Datei oder einem CMS, statt aus einer von Hand gepflegten XML-Datei. Bei mehr als 50.000 Adressen teilt generateSitemaps die Ausgabe automatisch in mehrere Teildateien auf. So steht jede neue, geänderte oder entfernte Seite ohne manuellen Eingriff korrekt in der Sitemap.

Eine Sitemap ist eine XML-Datei, die jede Adresse einer Website mit ihrem letzten Änderungsdatum an Suchmaschinen meldet. Ein Crawler, das Programm, mit dem Suchmaschinen Webseiten systematisch abrufen, nutzt diese Liste, um neue und geänderte Seiten schneller zu finden. Bei einer einzelnen Website lässt sich diese Datei notfalls von Hand pflegen. Betreibt eine Organisation dagegen Dutzende eigenständiger Next.js-Websites gleichzeitig, von denen jede laufend Artikel, Produktseiten oder Landingpages hinzufügt, ändert und wieder entfernt, wird eine handgepflegte Sitemap zur stillen Fehlerquelle. Sie listet gelöschte Seiten, die inzwischen einen Fehler 404 zurückgeben, oder sie fehlt bei neuen Seiten, wodurch diese langsamer gefunden werden. Über ein ganzes Portfolio aus mehreren eigenständigen Websites hinweg lässt sich das von Hand nicht mehr zuverlässig prüfen.

Next.js löst genau dieses Problem mit einer Dateikonvention. Statt einer von Hand geschriebenen sitemap.xml exportiert eine Datei namens sitemap.ts im App Router, dem seit Next.js 13 aktuellen Routing-System, eine Funktion. Diese Funktion erzeugt bei jedem Build oder bei jeder Anfrage die aktuelle URL-Liste direkt aus der echten Datenquelle, etwa aus einer JSON-Datei, einer Datenbank oder einem Headless-CMS. Jede Next.js-Website im App Router kann ihre Sitemap auf diese Weise an die eigene Inhaltsquelle koppeln, so wie es auch netcomputing.de für die eigene Sitemap tut. Für Websites mit mehr als 50.000 Adressen kommt zusätzlich die Funktion generateSitemaps zum Einsatz. Sie teilt die Ausgabe automatisch in mehrere Teildateien auf, die jeweils innerhalb der Protokollgrenze bleiben.

Systemvoraussetzungen

Eine dynamische Sitemap braucht wenige, aber genau passende Bausteine. Die folgende Tabelle nennt die aktuellen Versionen und wofür jeder Baustein steht.

KomponenteVersion oder AnforderungZweck
Next.jsab Version 13.3 (sitemap.ts eingeführt), empfohlen Version 16.x im App RouterStellt die Dateikonvention sitemap.ts und die Funktion generateSitemaps bereit
Node.jsmindestens Version 20.9 LTS, Pflicht seit Next.js 16Laufzeitumgebung für den Build und für serverseitig ausgeführte Sitemap-Generierung
TypeScriptmindestens Version 5.1, Next.js-16-MindestanforderungTypsicherheit für die Rückgabe vom Typ MetadataRoute.Sitemap
DatenquelleJSON-Datei, Datenbank oder Headless-CMS mit einem echten Änderungsdatum je EintragLiefert die URL-Liste und die lastModified-Werte für die Sitemap
Sitemap-FormatXML nach dem Protokoll von sitemaps.org, je Datei maximal 50.000 URLs oder 50 MBGrenze, ab der ein Sitemap-Index mit mehreren Teildateien nötig wird
Search-Console-Zugangverifizierte Property plus Dienstkonto mit der OAuth-Berechtigung webmastersErmöglicht die programmatische Meldung einer neuen oder geänderten Sitemap über die Search Console API

Systemarchitektur

Eine Next.js-Website mit dynamischer Sitemap hat zwei Bausteine, die zusammenspielen: die Datenquelle, aus der die Liste aller Seiten stammt, und die Datei sitemap.ts, die diese Liste bei jedem Build oder jeder Anfrage in das von Suchmaschinen erwartete XML-Format übersetzt. Bei sehr vielen Seiten kommt ein dritter Baustein hinzu, der Sitemap-Index: eine Datei, die auf mehrere kleinere Sitemap-Dateien verweist, statt selbst Adressen aufzulisten. Das folgende Schaubild zeigt den Weg von einer inhaltlichen Änderung bis zum Crawler.

flowchart TD
    A[Redakteurin aendert einen Artikel in der Datenquelle] --> B[Build oder eingehende Anfrage ruft sitemap.ts auf]
    B --> C{Sind es mehr als 50000 Adressen}
    C -->|Nein| D[Eine sitemap.ts liest die Datenquelle direkt aus]
    C -->|Ja| E[generateSitemaps liefert eine Liste von IDs]
    E --> F[sitemap.ts laeuft einmal je ID mit eigenem Adressbereich]
    D --> G[Eintraege vom Typ MetadataRoute.Sitemap entstehen]
    F --> G
    G --> H[Next.js rendert die XML-Datei unter sitemap.xml oder sitemap-0.xml]
    H --> I[robots.ts traegt die Adresse jeder Datei im Feld sitemap ein]
    I --> J[Crawler liest robots.txt und ruft die Sitemap-Dateien ab]
    J --> K[Crawler vergleicht lastModified mit dem letzten Abruf]
    H --> L[Search Console API meldet die Aenderung zusaetzlich aktiv]
    L --> J
    K --> M[Nur Adressen mit Status 200 und ohne noindex werden indexiert]

Beim Build oder bei einer Anfrage ruft Next.js die Standardfunktion aus sitemap.ts auf. Übersteigt die Zahl der Seiten 50.000, ruft Next.js stattdessen generateSitemaps auf und führt die Sitemap-Funktion einmal je zurückgegebener ID mit einem eigenen Adressbereich aus. In beiden Fällen entstehen Einträge vom Typ MetadataRoute.Sitemap, jeder mit mindestens einer URL und optional einem lastModified-Datum, einer changeFrequency und einer priority. Ein Suchmaschinen-Crawler findet den Verweis auf die Sitemap über die Zeile Sitemap in der robots.txt und vergleicht bei jedem Besuch das gemeldete lastModified mit dem Stand des letzten Abrufs, um zu entscheiden, welche Seiten eine erneute Prüfung lohnen. Aufgenommen wird am Ende aber nur, was auch tatsächlich aufgenommen werden darf: Eine Seite mit einem 404-Fehler, einer noindex-Anweisung oder einem Verweis auf eine andere kanonische, also als Original festgelegte Adresse bleibt draußen, unabhängig davon, was in der Sitemap steht.

Schritt-für-Schritt-Umsetzung

Sitemap aus der echten Datenquelle erzeugen

Die Datei sitemap.ts liegt im Wurzelverzeichnis des app-Ordners, direkt neben layout.tsx. Sie exportiert eine Funktion, die ein Array vom Typ MetadataRoute.Sitemap zurückgibt, wahlweise synchron oder als async-Funktion. Jeder Eintrag braucht mindestens das Feld url, alle anderen Felder sind optional. Das folgende Beispiel liest die Artikel aus einer Datenquelle aus, filtert unveröffentlichte und auf noindex gesetzte Einträge heraus und setzt lastModified nur, wenn ein echtes Änderungsdatum vorliegt.

import type { MetadataRoute } from 'next';
import { getAllArticles } from '@/lib/articles';

const BASE_URL = 'https://beispiel-portfolio.de';

export default function sitemap(): MetadataRoute.Sitemap {
  const articles = getAllArticles();

  const articleEntries: MetadataRoute.Sitemap = articles
    .filter((article) => article.status === 'published' && !article.noindex)
    .map((article) => ({
      url: `${BASE_URL}/${article.slug}`,
      lastModified: article.updatedAt ? new Date(article.updatedAt) : undefined,
      changeFrequency: 'monthly',
      priority: 0.7,
    }));

  const staticEntries: MetadataRoute.Sitemap = [
    { url: BASE_URL, changeFrequency: 'daily', priority: 1.0 },
  ];

  return [...staticEntries, ...articleEntries];
}

Fehlt bei einem Artikel das Feld updatedAt, bleibt lastModified in diesem Eintrag leer, statt mit new Date() ein falsches, tagesaktuelles Datum vorzutäuschen. Das ist mehr als Kosmetik: Google übernimmt den Wert aus lastModified laut eigener Dokumentation nur, wenn er sich nachweislich mit der tatsächlichen letzten Änderung der Seite deckt.

Aktualisierungsrhythmus der Sitemap festlegen

Next.js behandelt sitemap.ts intern als speziellen Route Handler, eine Datei, die direkt auf eine HTTP-Anfrage antwortet statt eine Seite zu rendern, und cached das Ergebnis standardmäßig. Ohne weitere Einstellung zeigt die ausgelieferte Sitemap deshalb den Stand des letzten Builds, bis ein neuer Build läuft. Bei Websites mit häufigen redaktionellen Änderungen zwischen zwei Deployments reicht das oft nicht. Zwei Route-Segment-Optionen, dieselben, die auch für Seiten und Layouts gelten, schaffen Abhilfe: export const dynamic = 'force-dynamic'; berechnet die Sitemap bei jeder Anfrage neu, und export const revalidate = 3600; erneuert sie stattdessen in einem festen Rhythmus, hier einmal pro Stunde. Für ein Portfolio mit mehreren Deployments pro Tag ist ein fester revalidate-Wert meist der bessere Kompromiss zwischen Aktualität und Serverlast, weil er nicht jeden einzelnen Crawler-Besuch neu gegen die Datenquelle rechnet.

Sitemap-Index für sehr große URL-Mengen aufbauen

Für einen Produktkatalog mit weit mehr als 50.000 Adressen reicht eine einzelne sitemap.ts nicht mehr aus, weil eine Datei laut Sitemap-Protokoll auf 50.000 URLs und 50 Megabyte begrenzt ist. In diesem Fall exportiert dieselbe Datei zusätzlich eine Funktion namens generateSitemaps, die eine Liste von IDs zurückgibt. Next.js ruft die Standardfunktion aus sitemap.ts danach einmal je ID auf und übergibt die ID als Promise, die sich mit await auflösen lässt.

import type { MetadataRoute } from 'next';
import { getProductCount, getProductsInRange } from '@/lib/catalog';

const BASE_URL = 'https://beispiel-shop.de';
const CHUNK_SIZE = 50000;

export async function generateSitemaps() {
  const total = await getProductCount();
  const sitemapCount = Math.ceil(total / CHUNK_SIZE);
  return Array.from({ length: sitemapCount }, (_, index) => ({ id: index }));
}

export default async function sitemap({
  id,
}: {
  id: Promise<string>;
}): Promise<MetadataRoute.Sitemap> {
  const resolvedId = Number(await id);
  const start = resolvedId * CHUNK_SIZE;
  const products = await getProductsInRange(start, start + CHUNK_SIZE);

  return products.map((product) => ({
    url: `${BASE_URL}/produkt/${product.slug}`,
    lastModified: product.updatedAt,
  }));
}

Jede so erzeugte Teildatei ist unter einer eigenen Adresse erreichbar, etwa /produkt/sitemap/0.xml und /produkt/sitemap/1.xml. Next.js baut daraus keinen übergeordneten Sitemap-Index von sich aus zusammen. Damit ein Crawler alle Teildateien findet, gehören ihre Adressen in das Feld sitemap von robots.ts, das laut Next.js-Dokumentation sowohl eine einzelne Zeichenkette als auch ein Array mehrerer Sitemap-Adressen entgegennimmt.

Häufige Fehlerquellen

  • Sitemap bleibt trotz neuer Inhalte unverändert. Ohne dynamic- oder revalidate-Einstellung liefert Next.js den zwischengespeicherten Stand des letzten Builds aus. Ein revalidate-Wert oder force-dynamic in derselben Datei behebt das.
  • Sitemap listet Seiten, die 404 zurückgeben oder auf noindex stehen. Google führt beide Fälle als eigene Status im Page-Indexing-Bericht der Search Console, weil eine Sitemap-Adresse, die nicht indexiert werden darf, ein widersprüchliches Signal sendet. Die Datenquelle sollte solche Einträge schon in der Abfrage herausfiltern, bevor sitemap.ts sie überhaupt sieht.
  • lastModified wird bei jedem Build künstlich auf heute gesetzt. Ein new Date()-Fallback für jeden Eintrag täuscht eine ständige Änderung vor, die es nicht gibt. Google vertraut dem Feld nur, wenn es die tatsächliche letzte Änderung wiedergibt. Fehlt ein echtes Datum, bleibt lastModified besser ganz leer.
  • Mehr als 50.000 URLs in einer einzigen sitemap.ts. Das Sitemap-Protokoll erlaubt je Datei nur 50.000 Adressen oder 50 Megabyte. Eine größere Liste gehört über generateSitemaps in mehrere Teildateien aufgeteilt.
  • Teildateien aus generateSitemaps sind erreichbar, aber nirgends eingetragen. Next.js erzeugt die einzelnen Sitemap-Dateien, verlinkt sie aber nicht automatisch untereinander. Ohne einen Eintrag im sitemap-Array von robots.ts findet ein Crawler nur die erste Teildatei, wenn überhaupt.
  • Verlass auf den alten Ping-Mechanismus statt auf die Search Console. Ein direkter Aufruf einer Ping-Adresse bei jeder Änderung gilt nicht mehr als verlässlicher Weg, Suchmaschinen zu benachrichtigen. Die programmatische Einreichung über die Search Console API oder ein korrekt gepflegter Eintrag in robots.txt ersetzen ihn.

Häufig gestellte Fragen

Was unterscheidet eine dynamisch generierte sitemap.ts von einer klassischen sitemap.xml?

Eine dynamisch generierte sitemap.ts unterscheidet sich von einer klassischen, von Hand gepflegten sitemap.xml dadurch, dass sie ihre Einträge bei jedem Build oder jeder Anfrage direkt aus der echten Datenquelle berechnet, etwa aus einer Artikeltabelle oder einem Content-Management-System. Neue, geänderte oder gelöschte Seiten stehen dadurch automatisch mit dem passenden lastModified-Datum in der Sitemap, ohne dass jemand die Datei manuell nachträgt. Next.js erzeugt aus der von sitemap.ts zurückgegebenen Liste vom Typ MetadataRoute.Sitemap selbst das passende XML nach dem Sitemap-Protokoll.

Wie viele Sitemap-Dateien braucht eine Website mit mehr als 50.000 Seiten?

Eine Website mit mehr als 50.000 Seiten braucht mindestens zwei Sitemap-Dateien, weil das Sitemap-Protokoll von sitemaps.org eine einzelne Datei auf 50.000 Adressen und 50 Megabyte begrenzt; dieselbe Grenze nennt Next.js in der eigenen Dokumentation zu generateSitemaps. Die Funktion generateSitemaps übernimmt die Aufteilung: Sie gibt eine Liste von IDs zurück, für die Next.js die Sitemap-Funktion jeweils einmal mit einem eigenen Adressbereich ausführt, und stellt jede Teildatei unter einer eigenen Adresse wie /produkt/sitemap/0.xml bereit. Einen übergeordneten Sitemap-Index baut Next.js dabei nicht von sich aus zusammen, die Adressen der Teildateien gehören stattdessen in das sitemap-Feld von robots.ts, das mehrere Werte als Array entgegennimmt.

Wird eine Next.js-Sitemap bei jeder Anfrage neu erzeugt oder zwischengespeichert?

Eine Next.js-Sitemap wird standardmäßig zwischengespeichert, weil sitemap.ts intern als spezieller Route Handler läuft und Next.js solche Handler cached, solange sie keine Request-time API wie cookies() oder headers() und keine abweichende Cache-Konfiguration verwenden. Wer die Sitemap bei jeder Anfrage neu berechnen will, setzt in derselben Datei export const dynamic = 'force-dynamic'. Wer stattdessen einen festen Rhythmus bevorzugt, etwa einmal pro Stunde, setzt export const revalidate = 3600. Ohne eine dieser beiden Einstellungen zeigt die Sitemap so lange den Stand des letzten Builds, bis ein neuer Build läuft.

Wie erfährt Google von einer neuen oder geänderten Sitemap, ohne dass jemand sie manuell einreicht?

Google erfährt von einer geänderten Sitemap heute vor allem über zwei automatisierbare Wege: den Eintrag der Sitemap-Adresse in robots.txt, den ein Crawler bei jedem Besuch ohnehin prüft, und die programmatische Einreichung über die Sitemaps-Methode der Search Console API, die eine Sitemap-Adresse für eine verifizierte Property per PUT-Anfrage meldet. Der frühere Weg, eine eigene Ping-Adresse bei jeder Änderung direkt aufzurufen, gilt nicht mehr als verlässlicher Mechanismus. Für ein Portfolio aus vielen Websites lässt sich die Einreichung über die Search Console API in denselben Automatisierungslauf einbauen, der auch den Build und das Deployment auslöst, sodass jede neue Sitemap unmittelbar nach dem Go-Live gemeldet wird.

Quellen

  1. sitemap.xml (Dateikonvention für sitemap.ts/js), Next.js Dokumentation (Vercel), abgerufen am 2026-08-26
  2. generateSitemaps (Funktionsreferenz), Next.js Dokumentation (Vercel), abgerufen am 2026-08-26
  3. robots.txt (Dateikonvention, Feld sitemap als String oder Array), Next.js Dokumentation (Vercel), abgerufen am 2026-08-26
  4. Upgrading to Version 16 (Node.js-, TypeScript-Mindestversionen, asynchrone id in sitemap), Next.js Dokumentation (Vercel), abgerufen am 2026-08-26
  5. Sitemaps XML Protocol (Grenze von 50.000 URLs und 50 MB je Datei), sitemaps.org, abgerufen am 2026-08-26
  6. Sitemap erstellen und einreichen (kanonische URLs, Genauigkeit von lastmod), Google Search Central, abgerufen am 2026-08-26
  7. Page-Indexing-Bericht (Status 'URL marked noindex' und 'Not found (404)'), Google Search Console Hilfe, abgerufen am 2026-08-26
  8. Sitemaps: submit (Search Console API), Google for Developers, abgerufen am 2026-08-26

Der Content wurde mit Hilfe von KI erstellt, vor allem in der Recherche und Vorformulierung. Prüfung und Abnahme durch unsere Redaktion.