Sitemap lastmod from git dates
July 1, 2026 · programmatic SEO · sitemaps · Next.js
Most Next.js sitemaps do this:
export default function sitemap() {
const lastModified = new Date();
return routes.map((url) => ({ url, lastModified }));
}
This site did it too, until today. It looks harmless. It is a lie told to Google on every deploy.
The problem
new Date() runs at build time. If the site redeploys — a content drip, a dependency bump, a one-line copy fix — every URL in the sitemap now claims it changed today. A crawler that acts on that signal recrawls pages that didn't change, finds nothing new, and learns to discount your lastmod entirely. Google has said plainly that a lastmod that doesn't match observable reality gets ignored. So the field you're populating to help crawl scheduling ends up doing nothing, at best.
The lazy alternative — one hand-bumped constant for the whole site — is honest but blind. A real edit to one page never surfaces as a change signal.
The pattern
Each page's lastmod should be the last date the source composing that page actually changed. Git already knows this. So:
- A prebuild script walks the data layer and every static route file, asks
git log -1 --format=%csfor each, and writes the dates to a committed JSON manifest. - The sitemap maps each route to the date of the source that composes it — the page file for static routes, the data registry for data-driven routes, frontmatter dates for posts.
- One guard makes it safe on Vercel: build containers use a shallow clone, where
git logreturns the deploy commit's date for everything — build-timenow()in disguise. The script detects a shallow repository and refuses to regenerate, so the committed manifest stays the source of truth at build time.
The result is per-page dates that are truthful, stable across redeploys, and update automatically the moment a source file actually changes.
Where it runs
I first shipped this on a 345-page HVAC platform I manage, where a weekday content drip redeploys the site automatically — the exact scenario where build-time now() degrades into "everything changed every day." The same pattern now runs on this site. If your sitemap sets one shared lastModified from new Date(), that's the highest-signal ten-line fix available to you this week.