Most XML sitemaps contain a <lastmod> value that is a lie. Not a malicious one — a build artifact. The sitemap is generated at deploy time, every URL gets stamped with the moment the build ran, and a site that changed three articles yesterday tells Google that all 40,000 of its URLs were modified simultaneously at 04:17 UTC.
Google's sitemap documentation is unusually blunt about the consequence: it uses the lastmod value if it is consistently and verifiably accurate. That conditional is the whole mechanism. lastmod is not a field you fill in; it is a claim you make repeatedly, which Google can check against what it actually finds when it fetches the page.
What Google says it does with each sitemap field
Sitemaps.org defines more elements than Google honors. The gap matters because teams spend engineering time tuning fields that are discarded.
| Element | Google's documented treatment |
|---|---|
<loc> | Used. This is the discovery payload. |
<lastmod> | Used, if consistently and verifiably accurate. |
<changefreq> | Ignored. |
<priority> | Ignored. |
xhtml:link hreflang annotations | Used as a valid way to declare hreflang. |
| Image and video extensions | Used for those verticals' discovery. |
Also documented: 50,000 URLs and 50MB uncompressed per sitemap file, with sitemap index files to go beyond that. And lastmod must be a W3C Datetime — either 2024-11-03 or a full timestamp with a timezone offset. A malformed date is treated as absent, which means a sitemap full of 11/03/2024 values is functionally a list of URLs with no change signal at all.
One more piece of the landscape: Google announced in 2023 that it was deprecating the sitemap ping endpoint, on the grounds that it was mostly abused and added little. The remaining supported channels are a Sitemap: line in robots.txt and submission in Search Console. Bing and Yandex support IndexNow for push notification; Google has not publicly adopted it.
Why the scheduler cares about your dates at all
Google documents that crawling is scheduled, not continuous, and that it tries to recrawl pages at roughly the rate they change. What it does not publish is the ranking function inside that scheduler. The reasonable inference — consistent with how any recrawl system has to work — is that each URL carries a learned change-rate estimate built from the crawler's own fetch history: how often the fetched bytes differed, how much they differed, and whether the change touched anything that mattered.
lastmod is an external hint offered into that estimate. It is cheap for you to assert and cheap for Google to verify, because the crawler eventually fetches the page anyway and can compare. A site whose asserted dates line up with observed changes gets a useful shortcut: it can say "this one page changed today" and have that treated as information. A site that stamps every URL on every deploy is asserting 40,000 changes and delivering three. After enough rounds, the signal carries no information, and the sensible engineering response is to fall back on the crawler's own observations.
That is why the failure mode is not a penalty. It is silence. Your lastmod stops doing anything, and nothing in Search Console tells you.
What counts as a change worth stamping
Google's documentation draws the line at the primary content of the page. A new paragraph, a revised price, a corrected fact, a substantially rewritten section: those reset the date. Changes to navigation, footer, sidebar modules, ad slots, related-post widgets, or a rotating testimonial do not.
This matters more than it sounds, because those are exactly the elements that change on every deploy on a component-based site. A template tweak to the header recompiles every page and touches every file's mtime. If your sitemap generator reads filesystem modification times, that template tweak just rewrote your entire change history.
Borderline cases and how to decide:
- Comments or user reviews added. Stamp it if the UGC is a meaningful part of what the page offers (a product page where reviews are the substance). Otherwise don't.
- Automatic "updated on" date bumps with no edit. Never stamp. This is the same lie in a different field, and it degrades the visible date in results too.
- Typo fixes and link swaps. Don't stamp. Use judgment: would a reader who already read the page get value from reading it again?
- Programmatic pages regenerated from a data feed. Stamp only when the underlying record changed, not when the feed refreshed.
How to generate dates you can defend
The fix is to derive lastmod from content, not from the build. Hash the rendered primary content, store the hash and the date it last changed, and only advance the date when the hash moves.
// pseudo-code, runs at build time
const body = extractPrimaryContent(page); // main content only:
// no nav, footer, sidebar, ads
const hash = sha256(normalizeWhitespace(body));
const prev = store.get(page.url); // persisted across builds
if (!prev || prev.hash !== hash) {
store.set(page.url, { hash, lastmod: new Date().toISOString() });
}
emit(`<url><loc>${page.url}</loc>` +
`<lastmod>${store.get(page.url).lastmod}</lastmod></url>`);
Two implementation notes. The store must survive builds — a JSON file in the repo, a small table, or an object in blob storage; ephemeral CI containers will reset everything and stamp the whole site. And normalizeWhitespace should also strip anything genuinely volatile inside the content region: view counters, "3 min read" recalculations, timestamps rendered from now().
If the editorial CMS already records a true "content last edited" timestamp distinct from "record last saved," use that instead and skip the hashing.
How to tell whether Google trusts your dates
There is no report for this, so measure it in your logs.
- Verify the crawler first. Confirm Googlebot by reverse DNS or Google's published IP ranges before counting anything.
- Build a change log. Every time your generator advances a
lastmod, append the URL and timestamp. - Measure lag. For each changed URL, find the first verified Googlebot fetch after the change. Track the median and the 90th percentile.
- Hold a control. Compare against a matched set of URLs at similar crawl depth and internal link volume that did not change. If your dates are being used, the changed cohort should be re-fetched materially sooner.
- Watch the response. Fetches that return
304immediately after you claimed a change are the contradiction that erodes trust. Your conditional-request logic and yourlastmodlogic must agree on what "changed" means.
Run this for several weeks. Recrawl behavior is noisy at the single-URL level and only readable in aggregate.
Where lastmod will not help you
lastmod influences recrawl priority for URLs Google already knows and wants. It does not force indexing, it does not rescue thin or duplicate pages, and it does not speed up first discovery of a brand-new URL much beyond what the <loc> entry already does — for a new URL there is no change history to accelerate. Google's own documentation calls sitemaps a hint, not a command.
It also will not compensate for a slow server. If origin response time is throttling crawl rate, an accurate sitemap just reorders a queue that is already too short.
What to do this week
- Fetch your sitemap and check whether more than a handful of URLs share the same
lastmodtimestamp. If they cluster on deploy times, your dates are build artifacts. - Confirm the format parses as W3C Datetime. Malformed dates are silently ignored.
- Delete
<changefreq>and<priority>from the generator. They are dead weight. - Move
lastmodderivation to a content hash with a persisted store, restricted to primary content. - Add the
Sitemap:directive to robots.txt if it isn't there, and stop calling any ping endpoint. - Start the log measurement now, before the change, so you have a baseline to compare against.