I shipped a guide site whose sitemap.ts ended with the most natural line in the world:
{ url: `${SITE}/guides/cra-sbom-requirement`, lastModified: new Date() }
Every URL, every build. Which meant a commit that only touched brand tokens and a motion curve announced to Google that all four legal guides had been rewritten that afternoon. Nothing on those pages had changed in a week.
Why that's worse than no lastmod at all
Google's sitemap documentation says it uses <lastmod> "if it's consistently and verifiably (for example by comparing to the last modification of the page) accurate", and that the value "should reflect the date and time of the last significant update to the page."
A sitemap where nothing is ever older than the last deploy fails both halves. The crawler fetches a page it was told changed, diffs it against what it already has, finds nothing, and learns that this host's dates mean nothing. Then it stops reading them.
That's a cheap loss on an established domain. On a three-week-old host with no inbound links — where three of five guides were still unknown to Google — lastmod is close to the only recrawl signal you own.
One constant, three consumers
The date was already on the page twice: rendered as a visible "Last verified" line, and emitted as dateModified in the page's Article JSON-LD. The sitemap was inventing a third value instead of reading the one that existed.
So the page module exports it, and the sitemap imports it:
// guides/cra-sbom-requirement/page.tsx
export const UPDATED = "2026-07-29";
// sitemap.ts
import { UPDATED as SBOM_UPDATED } from './guides/cra-sbom-requirement/page';
export default function sitemap(): MetadataRoute.Sitemap {
return [
{ url: `${SITE}/guides/cra-sbom-requirement`,
lastModified: SBOM_UPDATED, changeFrequency: 'monthly', priority: 0.9 },
// static pages carry the date of their last real content change, bumped by hand
];
}
Next.js accepts an ISO string for lastModified, so no conversion is needed. Static pages that have no UPDATED get a hand-maintained constant with a comment explaining what change earned the bump. The landing page's says 2026-08-04 because a factual correction rewrote the FAQ, its JSON-LD and two components — not because something was deployed that day.
The off-by-one that only surfaced once the dates converged
Collapsing three values into one immediately exposed a bug nobody could have seen while they were independent. The visible date rendered like this:
new Date(updated).toLocaleDateString("en-US", { year: "numeric", month: "long", day: "numeric" })
new Date("2026-08-01") parses date-only strings as UTC midnight. Format that in any negative-offset zone and you get the previous day. Every guide had been showing a "Last verified" date one day behind the dateModified in its own Article schema — human-readable text contradicting structured data on the same page, in a way that only reproduces west of Greenwich. The fix is timeZone: "UTC" in the options bag, with a comment saying it's load-bearing so nobody tidies it away.
The sitemap bug was in sitemap.ts. The date bug was in the article renderer. You find the second one only after the first forces both surfaces to read from the same source.
The harder half: what licenses a bump
Once dates are honest, "should this move?" becomes a real decision. The site makes 34 dated claims about a regulation — deadlines, penalty routes, filing channels — and each lives in a register with the primary source it was fetched from, an asOf, a lastVerified, and a watch note.
The dangerous category is claims that assert something has not happened. One is flagged HIGHEST-DECAY CLAIM IN THIS REGISTER: whether the Commission has adopted an implementing act specifying the SBOM format. It can go false while the repo sits completely still, and if it does, an entire section of a guide is wrong that morning.
So re-verification happens on a schedule the content doesn't. Last check: source fetched, HTTP 200, still nothing adopted. lastVerified in the register moves. The page's UPDATED does not — and therefore neither does lastmod. Verifying is not updating.
The rule that came out of it: a stale-but-true date is worth more than a fresh lie. If you can't name the content change behind a bump, don't bump it.
That register and these guides are how we built PartsProof: https://partsproof.kynth.studio
Top comments (0)