import type { MetadataRoute } from "next"; import { getAllEvents, getOffers, getPosts } from "@/lib/payload/content"; import { getSiteUrl, isSiteIndexable } from "@/lib/seo/config"; // Reads live CMS content on every request — must not be statically // prerendered at build time (no DB is available during `next build`; see // AGENTS.md / the same convention used by every other CMS-backed route). export const dynamic = "force-dynamic"; // Static, always-public routes that aren't backed by a dynamic [slug] // collection. Deliberately excludes: /login, /registrieren, /konto*, // /admin*, /calendar/*, /auth/*, /termine/*/beitreten, /termine/*/call, // /api/* — none of those are public content (see app/robots.ts, which // mirrors this same exclusion list). const staticRoutes: { path: string; changeFrequency: MetadataRoute.Sitemap[number]["changeFrequency"]; priority: number }[] = [ { path: "", changeFrequency: "weekly", priority: 1 }, { path: "/ueber-mich", changeFrequency: "monthly", priority: 0.7 }, { path: "/angebote", changeFrequency: "monthly", priority: 0.9 }, { path: "/angebote/singkreise", changeFrequency: "monthly", priority: 0.6 }, { path: "/aktuelles", changeFrequency: "weekly", priority: 0.7 }, { path: "/termine", changeFrequency: "weekly", priority: 0.7 }, { path: "/termin-buchen", changeFrequency: "monthly", priority: 0.8 }, { path: "/kontakt", changeFrequency: "yearly", priority: 0.5 }, { path: "/impressionen", changeFrequency: "monthly", priority: 0.3 }, { path: "/impressum", changeFrequency: "yearly", priority: 0.1 }, { path: "/datenschutz", changeFrequency: "yearly", priority: 0.1 }, ]; export default async function sitemap(): Promise { if (!(await isSiteIndexable())) return []; const siteUrl = await getSiteUrl(); const now = new Date(); const entries: MetadataRoute.Sitemap = staticRoutes.map((route) => ({ url: `${siteUrl}${route.path}`, lastModified: now, changeFrequency: route.changeFrequency, priority: route.priority, })); // getOffers/getAllEvents/getPosts already only return published, // publicly-readable documents (overrideAccess: false — see // lib/payload/content.ts's header comment). const [offers, events, posts] = await Promise.all([getOffers(), getAllEvents(), getPosts()]); for (const offer of offers) { if (!offer.slug || offer.visibility === "private") continue; entries.push({ url: `${siteUrl}/angebote/${offer.slug}`, lastModified: new Date(offer.updatedAt), changeFrequency: "monthly", priority: 0.8, }); } for (const event of events) { // Private single-session bookings must never appear in the sitemap. if (!event.slug || event.isPrivateBooking) continue; entries.push({ url: `${siteUrl}/termine/${event.slug}`, lastModified: new Date(event.updatedAt), changeFrequency: "weekly", priority: 0.6, }); } for (const post of posts) { if (!post.slug) continue; entries.push({ url: `${siteUrl}/aktuelles/${post.slug}`, lastModified: new Date(post.updatedAt), changeFrequency: "monthly", priority: 0.6, }); } return entries; }