/* * A second sitemap for the URLs Next owns. * * /sitemap.xml is proxied from FastAPI (app/(frontend)/sitemap.xml), which * knows nothing about Payload — the backend and frontend ship as separate * images. Rather than teach it, the Next-owned URLs get their own sitemap and * robots.txt lists both. */ import { getCachedPayload } from '@/lib/payload'; import { absoluteUrl } from '@/lib/site'; /* * Dynamic, not ISR. * * This route has no dynamic params, so Next prerenders it at build time — and * CI builds the image with no database reachable, which fails the build. It is * a single indexed query against Postgres on the same Docker network, so * rendering per request is cheap, and it means a newly published post appears * here immediately rather than waiting on a revalidation. */ export const dynamic = 'force-dynamic'; export async function GET() { const payload = await getCachedPayload(); const { docs } = await payload.find({ collection: 'posts', where: { _status: { equals: 'published' } }, sort: '-publishedAt', limit: 500, depth: 0, }); const urls: Array<{ loc: string; lastmod: string | null }> = [ { loc: absoluteUrl('/about'), lastmod: null }, { loc: absoluteUrl('/blog'), lastmod: null }, ...docs.map((post) => ({ loc: absoluteUrl(`/blog/${post.slug}`), lastmod: new Date(String(post.updatedAt ?? post.publishedAt)).toISOString(), })), ]; const xml = ` ${urls.map(({ loc, lastmod }) => ` ${loc}${lastmod ? `${lastmod}` : ''}`, ).join('\n')} `; return new Response(xml, { headers: { 'Content-Type': 'application/xml; charset=utf-8' }, }); }