Files
anouma/app/sitemap.ts
T

80 lines
3.1 KiB
TypeScript

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<MetadataRoute.Sitemap> {
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;
}