import type { MetadataRoute } from "next"; import { bff } from "@/lib/bff"; /** * /sitemap.xml — one file for the whole public corpus (~33k URLs, well under * the 50,000-URL / 50 MB sitemap limits; the content is finite, so a single * file is correct for the foreseeable future). * * Why a single file (not generateSitemaps): Next's generateSitemaps() serves * shards at /sitemap/.xml and does NOT expose an index at /sitemap.xml — * but /sitemap.xml is exactly where robots.txt + search engines look. A plain * default export is served at /sitemap.xml. (If the URL count ever nears 50k, * switch to a sitemap index referencing sharded children.) * * How this stays cheap on the BFF: * • Two projection-only BFF calls (`/sitemap/content`, `/sitemap/users`) * return every URL in a single sweep each — no per-document fan-out. * • The fetches are cached 24h (revalidate) AND the rendered XML is * ISR-cached, so crawlers hit Next's cache, not the BFF (~one query/day). * Tag "sitemap" lets the BFF force-refresh on new signups / re-seeds. */ export const revalidate = 86400; // 24h const SITE_URL = process.env.NEXT_PUBLIC_SITE_URL || "https://prabhupadasays.org"; // Top-level hubs that aren't content leaves but should be indexed. const STATIC_PATHS = [ "/", "/about", "/books", "/letters", "/transcriptions", "/disciples", "/glossary", "/essays", "/magazines", "/journals", "/other-documents", ]; async function fetchUrls(path: string): Promise { try { const res = await bff<{ urls: string[] }>(path, { revalidate, tags: ["sitemap"], }); return res?.urls ?? []; } catch { return []; } } /** * All indexable paths: static hubs + book/division landing pages (derived * from the content leaves) + every content leaf + public profiles. Resilient — * a failed BFF call still yields the static hubs, so /sitemap.xml never 500s. */ async function allPaths(): Promise { const [content, users] = await Promise.all([ fetchUrls("/sitemap/content"), fetchUrls("/sitemap/users"), ]); const hubs = new Set(); for (const p of content) { const book = p.match(/^\/books\/([^/]+)/); if (book) hubs.add(`/books/${book[1]}`); const division = p.match(/^\/books\/(sb|cc)\/([^/]+)\/\d+\/\d+$/); if (division) hubs.add(`/books/${division[1]}/${division[2]}`); } return [...new Set([...STATIC_PATHS, ...hubs, ...content, ...users])]; } export default async function sitemap(): Promise { const paths = await allPaths(); return paths.map((path) => ({ url: `${SITE_URL}${path}`, changeFrequency: "weekly", priority: path === "/" ? 1 : STATIC_PATHS.includes(path) ? 0.8 : 0.7, })); }