diff --git a/src/app/sitemap.xml/route.ts b/src/app/sitemap.xml/route.ts index 69348443..70935422 100644 --- a/src/app/sitemap.xml/route.ts +++ b/src/app/sitemap.xml/route.ts @@ -15,11 +15,15 @@ import { buildSitemap } from "@/lib/sitemap-builder"; // serialized to XML here and shipped with a real edge cache header. export const runtime = "nodejs"; export const dynamic = "force-dynamic"; -// Hard platform ceiling so Vercel never holds the connection open longer -// than this when the SRH/Redis fan-out runs over budget. The internal -// buildSitemap() timeout is 20 s; this gives 40 s of headroom and still -// replies well within the smoke-test's 90 s limit. -export const maxDuration = 60; +// 300 s: the aggregate blob is 4.25 MB — larger than Next.js Data Cache's +// 2 MB limit, so unstable_cache can never store it and every request must +// re-fetch from the network. The /api/aggregate ISR proxy responds in +// 18-65 s on a cold cache. With maxDuration=60 the process received +// SIGKILL before the fetch resolved, discarding the buffered response and +// returning 500. 300 s gives the full build time to complete on cold +// starts; the internal JS timeout (240 s) fires first and falls back to +// the static sitemap if the full build hangs. +export const maxDuration = 300; function escapeXml(s: string): string { return s diff --git a/src/lib/sitemap-builder.ts b/src/lib/sitemap-builder.ts index 84664741..957b39f8 100644 --- a/src/lib/sitemap-builder.ts +++ b/src/lib/sitemap-builder.ts @@ -577,14 +577,13 @@ async function buildFullSitemap(): Promise { export async function buildSitemap(): Promise { try { - // 55 s: the Vercel maxDuration on the route is 60 s. With /api/aggregate - // ISR-cached (revalidate=60 on that route) warm requests return in <1 ms - // and the full sitemap completes in ~10 s. Cold-start requests (first hit - // after deployment) may take 18-49 s for the aggregate; 55 s gives enough - // headroom while leaving 5 s for buildStaticFallback() before Vercel kills - // the function. + // 240 s: maxDuration on the route is 300 s. The aggregate blob (4.25 MB) + // exceeds Next.js Data Cache's 2 MB cap, so every sitemap request + // re-fetches from /api/aggregate whose FETCH_TIMEOUT_MS is 65 s. A 240 s + // budget covers cold aggregate + HL-builder Prometheus sweeps with 60 s of + // headroom for buildStaticFallback() before the 300 s SIGKILL ceiling. const timeout = new Promise((_, reject) => - setTimeout(() => reject(new Error("sitemap build timeout")), 55_000), + setTimeout(() => reject(new Error("sitemap build timeout")), 240_000), ); // Attach a no-op .catch() to prevent unhandled-rejection crashes if // buildFullSitemap() rejects AFTER the race has already settled (via