From ee020b1a9293f2b19e1438004ee4206a94d270b7 Mon Sep 17 00:00:00 2001 From: oratis Date: Sat, 27 Jun 2026 21:17:51 +0800 Subject: [PATCH] fix(seo): cache sitemap so Google can fetch it MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The sitemap was force-dynamic: with ~6.8k URLs × 15-locale hreflang it regenerates in ~13s and serializes to ~12MB on every request, so Google Search Console's fetcher times out ("Couldn't fetch", 0 URLs discovered). Switch to ISR (revalidate = 3600): serve a cached copy and regenerate at most hourly in the background, so crawlers always get a fast response. The existing try/catch already tolerates the DB-less Docker build, which now renders the static-routes fallback at build time; the first request after deploy fills in agents/skills and caches the full sitemap. Co-Authored-By: Claude Opus 4.8 --- src/app/sitemap.ts | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/app/sitemap.ts b/src/app/sitemap.ts index 6d7f10d8..f456e14c 100644 --- a/src/app/sitemap.ts +++ b/src/app/sitemap.ts @@ -5,10 +5,16 @@ import { routing } from "@/i18n/routing"; import { SCENARIOS } from "@/lib/scenarios"; import { getAllPosts } from "@/lib/blog"; -// Generated at request time against the live catalog (the app renders DB pages -// dynamically and the Docker build has no DB), so the sitemap stays fresh as -// agents/skills are imported. Crawlers hit this infrequently. -export const dynamic = "force-dynamic"; +// Cached (ISR) rather than force-dynamic: with ~6.8k URLs × 15-locale hreflang, +// regenerating per request takes ~13s and serializes to ~12MB, which makes +// Google's sitemap fetcher time out ("Couldn't fetch"). Instead we serve a +// cached copy and regenerate at most hourly in the background (stale-while- +// revalidate), so crawlers always get a fast response. The Docker build has no +// DB, so the build-time render falls back (via the try/catch below) to just the +// static routes; the first request after deploy fills in agents/skills and +// caches the full sitemap. Catalog changes propagate within the revalidate +// window — fine, since crawlers hit this infrequently. +export const revalidate = 3600; // hreflang alternates for a path: one entry per locale. The canonical `url` is // the unprefixed English URL; `alternates.languages` carries every locale so a