# Prompt-to-PR: Sitemap und robots.txt hinzufügen

> SOP zum Hinzufügen einer dynamischen XML-Sitemap und robots.txt zu einem Next.js- oder Astro-Projekt – korrekte lastmod-, priority- und Crawl-Regeln für die Produktions-SEO.

**Type:** Playbook  
**Tools:** Cursor, Claude Code, Codex, Windsurf  
**Stack:** Next.js, Astro, TypeScript  
**Difficulty:** easy  
**Updated:** 2026-06-08

---

Sitemaps und `robots.txt` sind die ersten SEO-Grundlagen, die ein Agent berührt, und sie sind häufig falsch – falsches `lastmod`-Format, fehlende `Sitemap:`-Direktive in robots oder versehentlich eingeschlossene blockierte Seiten. Dieses Playbook macht es richtig.

## 1. Anforderung

Erstellen Sie eine XML-Sitemap, die alle öffentlichen Routen abdeckt (statische + dynamische Inhalte aus der Datenbank) und eine `robots.txt`, die Admin-/API-Pfade blockiert und auf die Sitemap verweist. Funktioniert sowohl für Next.js App Router als auch Astro; wählen Sie den richtigen Ansatz für Ihr Framework.

## 2. Erste Eingabeaufforderung

```txt title="First Prompt"
Add a sitemap.xml and robots.txt to this project. Use the correct approach
for the framework detected below.

### If Next.js 14+:
1. Create `src/app/sitemap.ts` using the Next.js `MetadataRoute.Sitemap`
   return type. Include:
   - All static routes: /, /pricing, /blog, /about (hardcoded is fine).
   - All dynamic blog posts: fetch slugs from the DB using the existing
     query helper, return lastModified from the post's updatedAt field.
   - Use `process.env.NEXT_PUBLIC_APP_URL` as the base URL.
   - Correct W3C datetime format for lastModified (ISO 8601).
2. Create `src/app/robots.ts` using MetadataRoute.Robots.
   - Allow: all routes.
   - Disallow: /admin, /api, /dashboard.
   - Add `sitemap: process.env.NEXT_PUBLIC_APP_URL + "/sitemap.xml"`.

### If Astro:
1. Add `@astrojs/sitemap` integration. In astro.config.ts, add
   `sitemap({ filter: (page) => !page.includes("/admin") })` and set
   `site: process.env.SITE_URL`.
2. Create `public/robots.txt`:
   User-agent: *
   Disallow: /admin
   Disallow: /api
   Sitemap: <SITE_URL>/sitemap-index.xml

Do not create a custom sitemap endpoint if the integration handles it.
Do not block / or any public content pages.
```

## 3. Erwartete Dateiänderungen

```txt
### Next.js
src/app/sitemap.ts             (new — dynamic MetadataRoute.Sitemap)
src/app/robots.ts              (new — MetadataRoute.Robots)

### Astro
astro.config.ts                (add sitemap integration + filter)
public/robots.txt              (new)
.env.example                   (SITE_URL added if missing)
```

## 4. Überprüfungs-Checkliste

- Die Basis-URL stammt aus einer Umgebungsvariablen – nicht fest codiert als `http://localhost:3000`.
- `lastModified` ist ein JavaScript-`Date`-Objekt (Next.js konvertiert in ISO 8601) oder bereits ein gültiger ISO-String – nicht `"undefined"` oder fehlend.
- `/admin`, `/api` und `/dashboard` stehen in der Disallow-Liste.
- Die `Sitemap:`-Direktive in `robots.txt` verwendet eine absolute URL.
- Dynamische Routen (Blogbeiträge) werden über eine DB-Abfrage eingeschlossen, nicht nur statische Routen.
- Die Sitemap enthält keine 404-, Weiterleitungs- oder Noindex-Seiten.
- `bun run build` und dann `curl /sitemap.xml` gibt gültiges XML zurück (mit `xmllint` überprüfen).

## 5. Testbefehle

```bash
bun run build && bun run start
# or for Astro:
bun run build && bun run preview

# Validate sitemap XML
curl -s http://localhost:3000/sitemap.xml | xmllint --format - | head -40

# Confirm robots.txt
curl http://localhost:3000/robots.txt

# Confirm admin is disallowed and sitemap directive is present
grep -E "Disallow|Sitemap" <(curl -s http://localhost:3000/robots.txt)

# Google Rich Results / URL Inspection simulation
curl -A "Googlebot" http://localhost:3000/sitemap.xml -I
```

## 6. Häufige Fehler

- **`lastModified` ist `"undefined"`** – das `updatedAt`-Feld des Beitrags ist null. Absicherung: `lastModified: post.updatedAt ?? post.createdAt ?? new Date()`.
- **Sitemap gibt 404 zurück** – `src/app/sitemap.ts` fehlt oder befindet sich außerhalb des `app`-Verzeichnisses.
- **Alle Routen disallowed** – der Agent fügt versehentlich `Disallow: /` hinzu. Bestätigen Sie, dass nur Admin-/API-Pfade blockiert sind.
- **`Sitemap:`-Direktive mit relativer URL** – Google ignoriert sie. Muss absolut sein: `https://example.com/sitemap.xml`.
- **Nur statische Sitemap** – der Agent codiert Blog-Slugs fest, anstatt die Datenbank abzufragen. Bestätigen Sie, dass die Sitemap-Funktion `async` ist und echte Daten abruft.

## 7. Korrektureingabeaufforderung

```txt title="Fix Prompt"
The sitemap.xml at /sitemap.xml includes every blog post with
lastModified "undefined" (rendered as the string).

Fix in src/app/sitemap.ts:
  const posts = await getBlogPosts();
  return posts.map((post) => ({
    url: `${BASE_URL}/blog/${post.slug}`,
    lastModified: post.updatedAt ?? post.createdAt ?? new Date(),
    changeFrequency: "weekly",
    priority: 0.8,
  }));

Ensure getBlogPosts() returns rows that include updatedAt and createdAt.
```

## 8. PR-Beschreibung

```md title="PR description"
## SEO: Add dynamic sitemap.xml and robots.txt

**Next.js**: `src/app/sitemap.ts` + `src/app/robots.ts` using built-in
`MetadataRoute` types. Sitemap includes static routes + all published blog
posts with correct `lastModified` timestamps from the DB.

**robots.txt** disallows `/admin`, `/api`, `/dashboard`; includes absolute
`Sitemap:` directive pointing to the generated `/sitemap.xml`.

Base URL read from `NEXT_PUBLIC_APP_URL` — no localhost URLs in production.
```