Design wechseln

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

Easton editorial illustration: monorepo project desk

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

MethodeEinsatzAufwandFlexibilitätEmpfehlung
App Router nativKleine statische Sites⭐⭐⭐⭐⭐⭐
next-sitemapMittelgroße bis große Projekte⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐
API-RouteMaximale 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:

  1. changeFrequency und priority mit as const
  2. revalidate für ISR
  3. lastModified idealerweise aus echtem updatedAt

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:

  1. /_next/ – Build-Assets von Next.js
  2. /api/ – APIs nicht indexieren
  3. /admin/, /dashboard/ – geschützte Bereiche
  4. /*.json$ – JSON-Dateien meist irrelevant
  5. 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:

  1. https://yourdomain.com/robots.txt aufrufen
  2. robots.txt-Tester in der Search Console
  3. 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

  1. Google Search Console öffnen
  2. Property hinzufügen
  3. 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:

  1. Menü Sitemaps
  2. URL eingeben: sitemap.xml
  3. 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.

  1. Query-Parameter: sitemap.xml?v=20231220
  2. Einige Tage warten
  3. 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
  • lastmod im ISO-8601-Format

Prüfen mit einem Online-XML-Validator.

„Eingereicht, aber nicht indexiert“

Mögliche Gründe:

  1. robots.txt blockiert die URL
  2. Dünner oder doppelter Inhalt
  3. Neue Domain ohne Vertrauen
  4. Keine externen Signale/Links

Maßnahmen:

  1. URL-Prüfung in der Search Console
  2. Kein unbeabsichtigtes noindex
  3. Substanzielle Texte (oft > 300 Wörter sinnvoll)
  4. Qualitativ hochwertige Verlinkung

Monitoring

Regelmäßig prüfen:

  1. Abdeckung / Seitenindexierung
  2. Indexierte Seiten
  3. 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.xml erreichbar und gültig
  • /robots.txt erreichbar, 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

  1. Sitemap-Methode wählen: klein → sitemap.ts; groß → next-sitemap; Spezialfälle → Route Handler
  2. robots.txt: unnötige Pfade sperren, Sitemap-URL nennen, Nicht-Production blockieren
  3. Search Console: verifizieren, einreichen, Status beobachten
  4. 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. 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/$&#123;post.id&#125;`,
    lastModified: post.updatedAt,
    changeFrequency: 'weekly',
    priority: 0.8,
    }))
    ]
    }
  2. 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. 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. 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. 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/$&#123;product.id&#125;`,
    lastModified: product.updatedAt,
    changeFrequency: 'weekly' as const,
    priority: 0.8,
    }))
  6. 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?
Nein, aber dringend empfohlen. Mit Sitemap steigt die Indexierungsgeschwindigkeit um etwa 40 %. Bei neuen Websites kann das der Unterschied zwischen Indexierung innerhalb einer Woche und erst nach einem Monat sein. Besonders dynamisch erzeugte Seiten werden ohne Sitemap oft monatelang nicht gefunden.
Wie erzeuge ich eine Sitemap für dynamische Routen?
In sitemap.ts per async-Funktion alle dynamischen Seiten laden und pro Seite einen URL-Eintrag erzeugen. Beispiel: const posts = await getPosts(); return posts.map(post => ({ url: `/posts/$&#123;post.id&#125;`, ... })). Alle indexierungswürdigen dynamischen Seiten einschließen.
Was passiert bei einer fehlerhaften robots.txt?
Disallow für die gesamte Site blockiert Suchmaschinen vollständig – oft monatelang keine Indexierung. Häufigster Fehler: Nur Pfade wie /api/ oder /admin/ sperren, nicht die Root.
Wie lange dauert es, bis Google die Sitemap crawlt?
In der Regel einige Tage bis Wochen. Google crawlt Sitemaps periodisch; neue Sites brauchen oft länger. Empfehlung: 1) Sitemap in Search Console einreichen; 2) Funktion „URL prüfen“ / Indexierung anfordern nutzen; 3) Inhalte aktuell halten; 4) Geduld.
Wie validiere ich die Sitemap?
1) /sitemap.xml im Browser öffnen; 2) Online-XML-Validator; 3) Einreichung in Search Console und Status prüfen; 4) Alle wichtigen Seiten enthalten? 5) Absolute URLs verwenden.
Kann robots.txt bestimmte Crawler blockieren?
Ja. In robots.ts können unterschiedliche userAgent-Regeln gesetzt werden. Beispiel: rules: [{ userAgent: 'Googlebot', allow: '/' }, { userAgent: 'Baiduspider', disallow: '/' }] – Google erlauben, Baidu sperren.
Muss die Sitemap alle Seiten enthalten?
Nein, aber alle wichtigen indexierbaren Seiten sollten drin sein: dynamische Inhalte, tiefe Seiten, die Crawler selten finden. Nicht nötig: 404, Login, Admin-Backend usw.

8 Min. Lesezeit · Veröffentlicht am: 20. Dez. 2025 · Aktualisiert am: 14. Juli 2026

Kommentare

Melde dich mit GitHub an, um einen Kommentar zu hinterlassen

Easton BlogEaston Blog