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:

  1. A prebuild script walks the data layer and every static route file, asks git log -1 --format=%cs for each, and writes the dates to a committed JSON manifest.
  2. 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.
  3. One guard makes it safe on Vercel: build containers use a shallow clone, where git log returns the deploy commit's date for everything — build-time now() 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.

Call (251) 370-0292Start a project