Next.js Sitemap und robots.txt: Leitfaden für schnelle Suchmaschinen-Indexierung

Die Website ist live – Sie suchen Ihren Namen bei Google und finden … nichts.
Mehrfach aktualisiert, andere Keywords probiert – leer. In der Google Search Console steht bei der eingereichten Sitemap „Abruf nicht möglich“. Ohne Suchtraffic bleibt selbst gute Inhalte unsichtbar.
Das kenne ich. Beim ersten Next.js-Projekt habe ich die Sitemap nach einem Tutorial eingerichtet – Google hat sie nicht abgerufen. Eine Woche lang ausprobiert, bis klar wurde: Die robots.txt hatte die gesamte Website gesperrt. Ein klassischer Fehler, der sich im Nachhinein peinlich anfühlt.
Dieser Artikel bündelt Erfahrung, Doku und getestete Konfiguration: Was Sitemap und robots.txt leisten, drei Generierungswege, typische Fallen und ein reales Missgeschick inklusive Recovery. Wenn Ihre Site nicht indexiert wird, die Sitemap Fehler meldet oder dynamische Routen fehlen – hier sollten Sie schneller zum Ziel kommen.
Warum Sitemap und robots.txt?
Aufgabe der Sitemap
Die Sitemap ist die „Landkarte“ für Suchmaschinen: welche URLs existieren, wie oft sie sich ändern, welche wichtiger sind. Ohne Sitemap entdecken Crawler Seiten vor allem über Links – tiefe oder dynamische URLs können monatelang fehlen.
Branchenangaben: Mit Sitemap steigt die Indexierungsgeschwindigkeit um etwa 40 %. Bei neuen Websites kann das Indexierung innerhalb einer Woche vs. erst nach einem Monat bedeuten.
Aufgabe der robots.txt
robots.txt sagt Crawlern, was sie dürfen und was nicht – Admin, APIs, Build-Artefakte brauchen meist keine Indexierung. So bleibt Crawl-Budget für relevante Seiten.
Wichtig: Keine robots.txt ist oft besser als eine falsche. Viele wollten nur ein Verzeichnis sperren und blockierten versehentlich / – die Site verschwindet aus Google. Immer testen, testen, testen.
Drei Wege zur Sitemap in Next.js
Seit dem App Router (Next.js 13+) gibt es mehrere Ansätze – von einfach bis flexibel.
Methode 1: Native sitemap.ts im App Router
Einsatz: Next.js 13+, überschaubare Seitenzahl (Dutzende bis Hunderte)
Offiziell empfohlen, ohne Extra-Paket. Datei app/sitemap.ts:
// app/sitemap.ts
import { MetadataRoute } from 'next'
export default function sitemap(): MetadataRoute.Sitemap {
return [
{
url: 'https://yourdomain.com',
lastModified: new Date(),
changeFrequency: 'yearly',
priority: 1,
},
{
url: 'https://yourdomain.com/about',
lastModified: new Date(),
changeFrequency: 'monthly',
priority: 0.8,
},
{
url: 'https://yourdomain.com/blog',
lastModified: new Date(),
changeFrequency: 'weekly',
priority: 0.5,
},
]
}
Nach dem Deployment: https://yourdomain.com/sitemap.xml.
Vorteile:
- Offiziell unterstützt, stabil
- Keine Zusatzabhängigkeit
- TypeScript-Typen für MetadataRoute.Sitemap
Nachteile:
- Statische URLs manuell pflegen
- Dynamische URLs per Code aus Datenquelle
Methode 2: Paket next-sitemap
Einsatz: Automatisierung, mehrere Umgebungen, sehr viele URLs
Beliebtestes Community-Tool mit vielen Optionen.
Installation:
npm install next-sitemap
Konfiguration next-sitemap.config.js:
/** @type {import('next-sitemap').IConfig} */
module.exports = {
siteUrl: process.env.SITE_URL || 'https://yourdomain.com',
generateRobotsTxt: true, // robots.txt automatisch
sitemapSize: 50000, // max. 50.000 URLs pro Sitemap
exclude: ['/admin/*', '/api/*', '/secret'],
robotsTxtOptions: {
policies: [
{
userAgent: '*',
allow: '/',
disallow: ['/admin', '/api'],
},
],
additionalSitemaps: [
'https://yourdomain.com/server-sitemap.xml',
],
},
}
package.json:
{
"scripts": {
"build": "next build",
"postbuild": "next-sitemap"
}
}
Nach npm run build entstehen Sitemap und ggf. robots.txt automatisch.
Vorteile:
- Mehrere Sitemaps, starke Konfiguration
- robots.txt inklusive
- Dynamische Routen, Multi-Env
Nachteile:
- Zusätzliche Dependency
- Etwas mehr Setup
Methode 3: API-Route / Route Handler
Einsatz: Maximale Kontrolle oder Echtzeit-Sitemap
// app/sitemap.xml/route.ts
import { NextResponse } from 'next/server'
export async function GET() {
const posts = await fetchAllPosts()
const sitemap = `<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<url>
<loc>https://yourdomain.com</loc>
<lastmod>${new Date().toISOString()}</lastmod>
<priority>1.0</priority>
</url>
${posts.map(post => `
<url>
<loc>https://yourdomain.com/blog/${post.slug}</loc>
<lastmod>${post.updatedAt}</lastmod>
<priority>0.7</priority>
</url>
`).join('')}
</urlset>`
return new NextResponse(sitemap, {
status: 200,
headers: {
'Content-Type': 'application/xml',
'Cache-Control': 'public, s-maxage=3600, stale-while-revalidate',
},
})
}
Vorteile: Volle Kontrolle, Live-Daten, eigene Logik
Nachteile: XML selbst bauen, Performance und Cache selbst managen
Vergleich
| Methode | Einsatz | Aufwand | Flexibilität | Empfehlung |
|---|---|---|---|---|
| App Router nativ | Kleine statische Sites | ⭐ | ⭐⭐ | ⭐⭐⭐⭐ |
| next-sitemap | Mittelgroße bis große Projekte | ⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ |
| API-Route | Maximale Anpassung | ⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐ |
In eigenen Projekten nutze ich oft next-sitemap – einmal konfiguriert, wenig Wartung.
Dynamische Routen: Blog-Sitemap
Typisch: Blogposts, Produkte, Profile – wie kommen sie in die Sitemap?
Mit nativer sitemap.ts
// app/sitemap.ts
import { MetadataRoute } from 'next'
import { getAllPosts } from '@/lib/posts'
export default async function sitemap(): MetadataRoute.Sitemap {
const staticPages = [
{
url: 'https://yourdomain.com',
lastModified: new Date(),
changeFrequency: 'yearly' as const,
priority: 1,
},
{
url: 'https://yourdomain.com/about',
lastModified: new Date(),
changeFrequency: 'monthly' as const,
priority: 0.8,
},
]
const posts = await getAllPosts()
const postPages = posts.map(post => ({
url: `https://yourdomain.com/blog/${post.slug}`,
lastModified: new Date(post.updatedAt),
changeFrequency: 'weekly' as const,
priority: 0.7,
}))
return [...staticPages, ...postPages]
}
export const revalidate = 3600 // ISR: stündlich neu generieren
Hinweise:
changeFrequencyundprioritymitas constrevalidatefür ISRlastModifiedidealerweise aus echtemupdatedAt
Mehr als 50.000 URLs
Google limitiert 50.000 URLs pro Sitemap. Darüber: mehrere Sitemaps mit generateSitemaps:
// app/sitemap.ts
import { MetadataRoute } from 'next'
export async function generateSitemaps() {
const totalPosts = await getTotalPostsCount()
const sitemapsCount = Math.ceil(totalPosts / 50000)
return Array.from({ length: sitemapsCount }, (_, i) => ({
id: i,
}))
}
export default async function sitemap({
id,
}: {
id: number
}): Promise<MetadataRoute.Sitemap> {
const start = id * 50000
const end = start + 50000
const posts = await getPosts(start, end)
return posts.map(post => ({
url: `https://yourdomain.com/blog/${post.slug}`,
lastModified: new Date(post.updatedAt),
priority: 0.7,
}))
}
Ergebnis u. a. sitemap/0.xml, sitemap/1.xml … und ein Index unter sitemap.xml.
robots.txt im Detail
Minimalbeispiel
# Alle Crawler, alles erlaubt
User-agent: *
Allow: /
Sitemap: https://yourdomain.com/sitemap.xml
Praxisnahe Variante
User-agent: *
Allow: /
Disallow: /_next/
Disallow: /api/
Disallow: /admin/
Disallow: /dashboard/
Disallow: /*.json$
Disallow: /*.xml$
Disallow: /*?*
Sitemap: https://yourdomain.com/sitemap.xml
Erläuterung:
/_next/– Build-Assets von Next.js/api/– APIs nicht indexieren/admin/,/dashboard/– geschützte Bereiche/*.json$– JSON-Dateien meist irrelevant- Sitemap-Zeile – Crawler finden die Sitemap-URL
Dynamisch mit robots.ts
// app/robots.ts
import { MetadataRoute } from 'next'
export default function robots(): MetadataRoute.Robots {
const baseUrl = 'https://yourdomain.com'
if (process.env.NODE_ENV === 'development') {
return {
rules: {
userAgent: '*',
disallow: '/',
},
}
}
return {
rules: [
{
userAgent: '*',
allow: '/',
disallow: [
'/_next/',
'/api/',
'/admin/',
'/dashboard/',
],
},
{
userAgent: 'GPTBot',
disallow: ['/'],
},
],
sitemap: `${baseUrl}/sitemap.xml`,
}
}
Umgebungen trennen: Dev und Preview sollten nicht indexiert werden – sonst landen Testinhalte in Google oder Production wird versehentlich gesperrt.
// app/robots.ts
import { MetadataRoute } from 'next'
export default function robots(): MetadataRoute.Robots {
const baseUrl = process.env.NEXT_PUBLIC_SITE_URL || 'https://yourdomain.com'
const isProduction = process.env.NODE_ENV === 'production'
const isDeployPreview = process.env.NEXT_PUBLIC_VERCEL_ENV === 'preview'
if (!isProduction || isDeployPreview) {
return {
rules: {
userAgent: '*',
disallow: '/',
},
}
}
return {
rules: [
{
userAgent: '*',
allow: '/',
disallow: [
'/_next/',
'/api/',
'/admin/',
'/dashboard/',
'/*.json$',
],
},
{
userAgent: 'GPTBot',
disallow: ['/'],
},
],
sitemap: `${baseUrl}/sitemap.xml`,
}
}
Typische Fehler
Fehler 1: Gesamte Site gesperrt
# ❌ Falsch
User-agent: *
Disallow: /
# ✅ Richtig
User-agent: *
Allow: /
Disallow: /admin/
Fehler 2: Keine Sitemap-Zeile
Ohne Sitemap: https://yourdomain.com/sitemap.xml wissen Crawler die Sitemap-URL nicht.
Fehler 3: Pfad ohne führenden Slash
# ❌
Disallow: _next/
# ✅
Disallow: /_next/
Fehler 4: Bilder pauschal sperren
# ❌ oft unnötig
Disallow: /*.jpg$
Disallow: /*.png$
Bilder können Ranking und Rich Results unterstützen – nur sperren, wenn bewusst gewollt.
Tests:
https://yourdomain.com/robots.txtaufrufen- robots.txt-Tester in der Search Console
- Einzel-URL prüfen, ob Crawling erlaubt ist
Google Search Console
Nach Sitemap und robots.txt die Property verbinden und die Sitemap einreichen.
Property hinzufügen
- Google Search Console öffnen
- Property hinzufügen
- Domain oder URL-Präfix wählen
DNS-Verifizierung (empfohlen): TXT-Eintrag beim Registrar, kurz warten, in der Console verifizieren.
Alternativ: Verifizierungs-HTML in public/, z. B. google1234567890abcdef.html.
Sitemap einreichen
Nach Verifizierung:
- Menü Sitemaps
- URL eingeben:
sitemap.xml - Einreichen
Zeitrahmen: Kein Sofort-Crawl; oft 1–7 Tage bis erste Verarbeitung; Status in der Console beobachten.
Fehlerbehebung
„Sitemap konnte nicht gelesen werden“
Ursache 1: Middleware blockiert Googlebot
JWT- oder Session-Middleware kann Bots wie normale Nutzer behandeln.
// middleware.ts
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
export function middleware(request: NextRequest) {
const { pathname } = request.nextUrl
const userAgent = request.headers.get('user-agent') || ''
const isBot = /googlebot|bingbot|slurp|duckduckbot|baiduspider|yandexbot/i.test(
userAgent
)
if (
pathname === '/robots.txt' ||
pathname === '/sitemap.xml' ||
pathname.startsWith('/sitemap-')
) {
return NextResponse.next()
}
if (isBot) {
return NextResponse.next()
}
const token = request.cookies.get('session-token')
if (!token && pathname.startsWith('/dashboard')) {
return NextResponse.redirect(new URL('/login', request.url))
}
return NextResponse.next()
}
export const config = {
matcher: ['/((?!_next/static|_next/image|favicon.ico).*)'],
}
So bleiben Sitemap und robots.txt für Bots erreichbar; Nutzer-Auth bleibt unverändert.
Ursache 2: Cache
Fehlgeschlagene Abrufe können in der Console hängen bleiben.
- Query-Parameter:
sitemap.xml?v=20231220 - Einige Tage warten
- Alte Einreichung löschen und neu einreichen
Ursache 3: Ungültiges XML
<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<url>
<loc>https://yourdomain.com</loc>
<lastmod>2024-12-20</lastmod>
</url>
</urlset>
- Deklaration:
<?xml, nicht<xml lastmodim ISO-8601-Format
Prüfen mit einem Online-XML-Validator.
„Eingereicht, aber nicht indexiert“
Mögliche Gründe:
- robots.txt blockiert die URL
- Dünner oder doppelter Inhalt
- Neue Domain ohne Vertrauen
- Keine externen Signale/Links
Maßnahmen:
- URL-Prüfung in der Search Console
- Kein unbeabsichtigtes
noindex - Substanzielle Texte (oft > 300 Wörter sinnvoll)
- Qualitativ hochwertige Verlinkung
Monitoring
Regelmäßig prüfen:
- Abdeckung / Seitenindexierung
- Indexierte Seiten
- Sitemap-Status
Für fehlende URLs: URL-Prüfung und Indexierung anfordern.
Praxis: Was schiefging
Ein E-Commerce-Projekt: drei Monate online, in Google keine sichtbaren Seiten.
robots.txt:
User-agent: *
Disallow: /
Drei Monate vollständig gesperrt – ein temporärer Entwickler hatte Test-Schutz auch in Production deployed.
Recovery: falsche robots.txt entfernt, korrekte Sitemap, Search Console – nach etwa einer Woche erste Treffer.
Seitdem: vor jedem Deploy Sitemap und robots.txt prüfen, getrennte Konfiguration pro Umgebung.
Ein zweites Mal: JWT-Middleware blockierte Googlebot – Console meldete „Sitemap nicht abrufbar“, obwohl das XML in Ordnung war.
SEO wirkt einfach; ein Zeichen in robots.txt oder eine Middleware-Regel kann Monate kosten.
Checkliste und Performance
Vor dem Go-Live
-
/sitemap.xmlerreichbar und gültig -
/robots.txterreichbar, keine Blockade wichtiger Pfade - Sitemap-Zeile in robots.txt
- Alle wichtigen URLs in der Sitemap
- Dynamische Inhalte in sitemap.ts eingebunden
- Search Console verifiziert
- Sitemap eingereicht
- Middleware lässt Bots zu Sitemap/robots.txt
Caching
// app/sitemap.xml/route.ts
export const revalidate = 3600
export async function GET() {
return new NextResponse(sitemap, {
headers: {
'Content-Type': 'application/xml',
'Cache-Control': 'public, s-maxage=3600, stale-while-revalidate',
},
})
}
Strategie:
- < 1.000 Seiten: vollständiger Neuaufbau oft ausreichend
- 1.000–10.000: ISR mit
revalidate -
10.000: mehrere Sitemaps, inkrementelle Updates
CDN (z. B. Cloudflare): Sitemap und robots.txt cachen, bei Inhaltsänderung Cache purgen.
Zusammenfassung
- Sitemap-Methode wählen: klein →
sitemap.ts; groß →next-sitemap; Spezialfälle → Route Handler - robots.txt: unnötige Pfade sperren, Sitemap-URL nennen, Nicht-Production blockieren
- Search Console: verifizieren, einreichen, Status beobachten
- Typische Stolpersteine: Middleware, XML, Cache-Altlasten
Sitemap und robots.txt sind keine Raketenwissenschaft – aber fehleranfällig. Mit dieser Checkliste sollten neue Sites schneller indexiert werden.
Haben Sie andere Erfahrungen oder Fragen? Teilen Sie sie gern in den Kommentaren.
Next.js: Vollständiger Ablauf für Sitemap und robots.txt
SEO-Konfiguration von der Dateierstellung bis zur Einreichung in Google Search Console
⏱️ Estimated time: 1 hr
- 1
Step 1: Dynamische Sitemap erstellen
Datei app/sitemap.ts anlegen:
• default-Funktion exportieren, die ein Sitemap-Array zurückgibt
• Jeder Eintrag mit url, lastModified, changeFrequency, priority
• async-Funktionen für dynamische Daten unterstützt
Beispiel:
export default async function sitemap() {
const posts = await getPosts()
return [
{
url: 'https://example.com',
lastModified: new Date(),
changeFrequency: 'yearly',
priority: 1,
},
...posts.map(post => ({
url: `https://example.com/posts/${post.id}`,
lastModified: post.updatedAt,
changeFrequency: 'weekly',
priority: 0.8,
}))
]
} - 2
Step 2: robots.txt erstellen
Datei app/robots.ts anlegen:
• default-Funktion exportieren, die die Robots-Konfiguration zurückgibt
• Erlaubte/verbotene Crawler festlegen
• Sitemap-Pfad setzen
Beispiel:
export default function robots() {
return {
rules: {
userAgent: '*',
allow: '/',
disallow: ['/api/', '/admin/'],
},
sitemap: 'https://example.com/sitemap.xml',
}
}
Wichtig: Nicht versehentlich die gesamte Website per disallow sperren - 3
Step 3: Generierte Dateien prüfen
Erzeugte Dateien kontrollieren:
• /sitemap.xml im Browser aufrufen
• /robots.txt prüfen
• Sicherstellen, dass alle Seiten in der Sitemap enthalten sind
• Prüfen, dass robots.txt die Website nicht fälschlich blockiert
Validierung:
• Google Search Console
• Online-XML-Validator
• Direktaufruf im Browser zur Formatprüfung - 4
Step 4: In Google Search Console einreichen
Schritte:
1. Google Search Console-Konto anlegen
2. Property hinzufügen (Eigentum verifizieren)
3. sitemap.xml-URL einreichen
4. Prüfen, ob robots.txt Crawling erlaubt
Verifizierung:
• HTML-Datei hochladen
• HTML-Meta-Tag
• DNS-TXT-Eintrag
• Google Analytics-Verknüpfung - 5
Step 5: Dynamische Seiten behandeln
Dynamische Routen:
• In sitemap.ts alle dynamischen Seitendaten laden
• Für jede Seite eine URL erzeugen
• Korrektes lastModified setzen
Beispiel:
const products = await getAllProducts()
const productUrls = products.map(product => ({
url: `https://example.com/products/${product.id}`,
lastModified: product.updatedAt,
changeFrequency: 'weekly' as const,
priority: 0.8,
})) - 6
Step 6: Überwachen und optimieren
Laufend überwachen:
• Google Search Console regelmäßig prüfen
• Status der Sitemap-Einreichung beobachten
• Auswirkungen von robots.txt auf Crawling prüfen
• Indexierungsfortschritt verfolgen
Optimierung:
• Sitemap bei neuen Seiten zeitnah aktualisieren
• Sinnvolle changeFrequency setzen
• Wichtigen Seiten höhere priority geben
• robots.txt-Konfiguration regelmäßig kontrollieren
FAQ
Ist eine Sitemap Pflicht?
Wie erzeuge ich eine Sitemap für dynamische Routen?
Was passiert bei einer fehlerhaften robots.txt?
Wie lange dauert es, bis Google die Sitemap crawlt?
Wie validiere ich die Sitemap?
Kann robots.txt bestimmte Crawler blockieren?
Muss die Sitemap alle Seiten enthalten?
8 Min. Lesezeit · Veröffentlicht am: 20. Dez. 2025 · Aktualisiert am: 14. Juli 2026
Next.js Komplettleitfaden
Wenn du über die Suche hier gelandet bist, kommst du am schnellsten weiter, indem du zum vorherigen oder nächsten Beitrag dieser Serie springst.
Vorheriger
Next.js SEO: Vollständiger Leitfaden – Metadata API und strukturierte Daten in der Praxis
Vollständiger Leitfaden zur SEO-Konfiguration mit der Next.js 15 Metadata API: strukturierte Daten, Open Graph, Twitter Cards – mit Praxiscode und typischen Fallstricken, damit Ihr Traffic wächst
Teil 28 von 51
Nächster
Next.js TypeScript-Konfiguration im Detail: tsconfig optimieren und Typsicherheit in der Praxis
Praxisnahe Tipps zur TypeScript-Konfiguration in Next.js: strict mode, typsichere Routen, typisierte Umgebungsvariablen – so beseitigen Sie any-Typen und verbessern die Developer Experience.
Teil 30 von 51
Ähnliche Beiträge
Next.js App Router Einstieg: Kernkonzepte und Grundlagen im Überblick

Next.js App Router Einstieg: Kernkonzepte und Grundlagen im Überblick
Next.js 15 in der Praxis: Wie ich an einem Wochenende ein produktionsreifes Blog-System aufgebaut habe


Kommentare
Melde dich mit GitHub an, um einen Kommentar zu hinterlassen