From d23664bd34b002588e8ddec25412e94d4eefec64 Mon Sep 17 00:00:00 2001 From: Florent Tapponnier Date: Sun, 21 Jun 2026 13:59:09 +0200 Subject: [PATCH] fix(sitemap): pin to deploy time, not request time MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Sitemap runs on force-dynamic (to bypass Next's 2 MB Data Cache limit on the rendered output), which means `new Date()` at module init re-evaluates on every Google crawl. Effect: every URL had a freshly updated each visit, Google flagged the signal as unreliable sitewide and stopped using lastmod for recrawl prioritisation (confirmed behaviour, documented by Gary Illyes publicly). Fix: bake a build-time timestamp via next.config env injection, read it from process.env.NEXT_PUBLIC_BUILD_TIME in sitemap.ts. Stable per deploy, fresh per deploy. Per-bench still uses live bench.lastRunAt as before — that path was already correct. --- next.config.ts | 11 +++++++++++ src/app/sitemap.ts | 10 +++++++++- 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/next.config.ts b/next.config.ts index 31d3ca9b..e51c52f0 100644 --- a/next.config.ts +++ b/next.config.ts @@ -53,6 +53,17 @@ const nextConfig: NextConfig = { // pin it so a future Next minor that flips defaults can't silently // split bench rankings between the two surface URLs. trailingSlash: false, + // Inject a build-time timestamp so the sitemap can emit a stable + // per deploy instead of `new Date()` at request time. The + // sitemap runs on force-dynamic (to bypass Next's 2 MB Data Cache + // limit), which means `new Date()` at module init evaluates anew on + // every Google crawl. Result: every URL in the sitemap got a freshly + // updated lastmod each visit, Google flagged the signal as unreliable + // and stopped using it to prioritise recrawls. This baking pins the + // value at build time so it changes only when a new deploy ships. + env: { + NEXT_PUBLIC_BUILD_TIME: new Date().toISOString(), + }, turbopack: { root: __dirname, }, diff --git a/src/app/sitemap.ts b/src/app/sitemap.ts index 403a05ba..aed5a6df 100644 --- a/src/app/sitemap.ts +++ b/src/app/sitemap.ts @@ -32,7 +32,15 @@ import type { Answer } from "@/lib/answers"; // succeeds; the next ISR refresh recovers automatically. export const dynamic = "force-dynamic"; -const BUILD_TIME = new Date(); +// Stable per deploy, not per request. process.env.NEXT_PUBLIC_BUILD_TIME +// is injected in next.config.ts at build time. Falling back to new Date() +// keeps local dev working. Critical: must NOT be `new Date()` at request +// time because the sitemap runs on force-dynamic (see header comment) so +// every Google crawl would otherwise see a fresh lastmod on every URL +// and Google would discard the signal as unreliable sitewide. +const BUILD_TIME = process.env.NEXT_PUBLIC_BUILD_TIME + ? new Date(process.env.NEXT_PUBLIC_BUILD_TIME) + : new Date(); // Vercel's build container doesn't preserve git-checkout mtimes — every // file gets reset to the build-system default (Oct 20 2018), which leaks // into the sitemap as `2018-10-20T...` for editorial