Google ignores your sitemap lastmod, and that is your fault
- SEO
- Build tooling
Most static site generators write the sitemap like this, and it is wrong:
const today = new Date().toISOString().slice(0, 10);
// ...per URL
`<lastmod>${today}</lastmod>`Every page in your sitemap now claims it changed today, every time you deploy. Fix a typo in the footer, redeploy, and you have told Google that all 30 pages are new.
Why that is worse than omitting it
Google has been explicit that it uses lastmod only when it is consistently accurate, and discounts the field across the whole site once it is not. So the choice is not between a lastmod and no lastmod. It is between a signal and a liability.
The genuinely updated page is the one that loses. It is now indistinguishable from the thirty that did not change.
Deriving an honest one
The question lastmod answers is when the content of this page last changed. In a repository, that is the last commit that touched the files the page renders from. Each route declares its sources:
{
path: '/services/penetration-testing',
sources: ['src/content/services.js', 'src/pages/services/ServiceDetail.jsx'],
}Then one git log pass builds a file to date map, and each route takes the most recent date among its sources. git log is newest first, so the first time you see a path is its latest change:
const log = execFileSync('git', ['log', '--format=%cs', '--name-only'], {
encoding: 'utf8',
});
const dates = new Map();
let date = null;
for (const line of log.split('\n')) {
const entry = line.trim();
if (!entry) continue;
if (/^\d{4}-\d{2}-\d{2}$/.test(entry)) date = entry;
else if (!dates.has(entry)) dates.set(entry, date);
}Three things that will catch you
Uncommitted edits must not count. The sitemap describes what is deployed, and what is deployed is what was committed. Reading mtime from the filesystem gives you the checkout date, which is meaningless.
Your CI probably does a shallow clone. actions/checkout defaults to depth 1, so git log sees exactly one commit and every other page gets no date at all. You need fetch-depth: 0, and without it this entire exercise is a no-op in the only place it matters.
And key the map consistently. We stored paths relative to the client directory while checking membership with the repo-relative path, so the guard never matched, every older commit overwrote the newer date, and the oldest won. The industry pages looked correct because their component was new and appeared in one commit only. The service pages gave it away.
If git is unavailable, omit lastmod rather than falling back to the build date. No date is a neutral signal. A wrong one is not.
While you are in there: changefreq and priority have been ignored by Google since 2023. They are harmless, they are just not doing anything.
More from us
Want this standard applied to your systems?
We write these up because they are the same decisions we make on client work. Tell us what you are building and we will tell you what we would do.
A reply from a developer. Not a sales pipeline. The person who reads your message is the person who would do the work.
An honest answer on fit. If an off-the-shelf product solves your problem better than a custom build, we will tell you which one.
No obligation. A first conversation costs nothing and commits you to nothing. Discovery output is yours either way.