Differentiated lastModified: why one build timestamp on 2,100 URLs is worse than none
A sitemap claiming every page changed at 03:14 this morning is not a freshness signal. It is noise, and the trust you lose is not recoverable by fixing it later.
By Nabeel Sharafat · 2026-07-27 · 3 min read · 545 words
The failure
Your sitemap says 2,100 pages changed at 03:14 this morning. All of them. Every day.
That is not a freshness signal. It is a build timestamp, and Google's documentation is explicit that it uses lastmod only when the value is consistently and verifiably accurate.
The asymmetry is what makes this worth caring about: once a crawler learns the field carries no information for your site, fixing it does not immediately undo that. You become a site with a correct lastmod that nobody reads.
Check yours in one command
curl -s https://example.com/sitemap.xml \
| grep -o '<lastmod>[^<]*' | cut -c10-19 \
| sort | uniq -c | sort -rn | headIf one date covers most of your URLs, it is a build stamp. A healthy distribution has dates spread across hundreds of days, with clusters only where a bulk update genuinely happened.
For reference, the site this playbook describes has 2,100 URLs spread across 340 distinct days. The largest single-day cluster is 118 URLs, which is the programmatic tier's snapshot regeneration date, and that is correct: those pages genuinely did all change that day.
The fix is structural
Every entry's date has to come from the content it describes. Not from new Date(), not from the build, and not from a file's mtime, which changes on checkout.
The composer should make this impossible to get wrong by requiring an accessor per group:
export type RouteGroup<T> = {
name: string;
items: T[];
url: (item: T) => string;
lastModified: (item: T) => Date | string; // real date, never new Date()
priority: number;
changeFrequency: ChangeFrequency;
};Each group supplies its own: the frontmatter date for a chapter, the published date for a post, the snapshot generation timestamp for a programmatic tier, the last real edit for a static page.
Then test it
A sitemap regression should fail the build, not produce a warning in a log nobody reads:
export function sitemapIntegrity(entries: SitemapEntry[], siteUrl: string) {
// duplicates, off-origin URLs, trailing slashes, epoch dates, future dates,
// and the largest single-day cluster as a ratio of the total
}The clustering ratio is the one that catches this specific failure. Flag any single day exceeding a quarter of the total, which catches an accidental build stamp without flagging a legitimate bulk update.
Two adjacent things worth correcting
Future dates. A timezone bug that adds a day produces a sitemap full of lastmod values in the future, which some crawlers treat as invalid for the entire file. Parse bare YYYY-MM-DD strings as UTC explicitly; the ECMAScript spec interprets a date-only string as UTC and a date-time string without a zone as local, and mixing the two is how this happens.
priority and changefreq. Google ignores both. Most sitemap advice still treats priority as a ranking lever, which it has never been. It remains useful as internal documentation of which pages you consider most important, and a differentiated ladder is better than a flat 0.8 for that reason alone, but do not expect it to move anything.
The sitemaps and feeds chapter has the full composer and the integrity test, and the sitemap planner generates the priority ladder and the change frequency mapping for your own tiers.
Questions
Does Google use lastmod in sitemaps?
Google uses the lastmod value only when it is consistently and verifiably accurate. A sitemap where every URL carries the same build timestamp teaches the crawler that the field carries no information, after which it is ignored.
Do sitemap priority and changefreq matter?
Google ignores both priority and changefreq. They remain useful as internal documentation of which pages you consider most important, and other crawlers may read them, but they are not a Google ranking or crawl input.