Skip to content
Crixaa
← All posts

lastmod and changefreq have to agree

The Crixaa team4 min read

Two of the fields in a sitemap entry describe the same thing from different angles. changefreq says how often a page changes, lastmod says when it last did — and nothing in any generator we have used checks that the two stories agree.

Two fields, one story

An entry is small:

<url>
  <loc>https://crixaa.com/pricing</loc>
  <lastmod>2026-07-31</lastmod>
  <changefreq>monthly</changefreq>
  <priority>0.8</priority>
</url>

Both fields are hints. No crawler is obliged to believe either, which is usually where the argument stops: they are only hints, so who cares if they are sloppy.

It cuts the other way too. A crawler is free to check the hints against each other, and against what it saw the last three times it fetched the page. weekly sitting next to a lastmod that has read the same date for fourteen months is not a stale entry — it is two statements that cannot both be true, in a file whose only job is to be believed. The cheap response is to stop weighting the file, for the whole site.

The publish date never moves

Here is the trap almost every blog walks into.

Posts have a date in their frontmatter. That date means published, and it should never change: readers judge relevance by it, the index sorts on it, and quietly rewriting it because you fixed a sentence is its own small dishonesty.

Which is exactly why it is the wrong thing to feed into lastmod. Edit a post and the file changes while the date does not, so the sitemap reports the original publication day forever — most wrongly for the posts you maintain most carefully.

The fix is a second, optional field:

---
date: "2026-08-04"
updated: "2026-08-17"
---

and preferring it wherever it exists:

lastModified: utc(post.updated ?? post.date)

Optional is load-bearing. Require updated on every post and it becomes a field people fill in by copying date at creation time and never look at again — which puts you back where you started.

Build time is not a modification date

The tempting alternative is new Date(). Always fresh, never stale, no maintenance.

What it actually emits is: every page on this site changed today. Deploy tomorrow to fix a footer link and it says the same thing again. It is false the moment it is written, and false in a repeating, mechanical way — the kind easiest to recognise from outside.

File mtimes look more principled — the file genuinely was modified, so the date is real. They are not an option either. CI checks the repository out fresh on every build, so every file is a few seconds old and you have reinvented the build timestamp with extra steps. Deriving it from git history is closer to honest, but it counts a formatting pass or a dependency bump as a content change.

So we type the dates in

The unglamorous option is a hand-maintained date per page:

const ROUTES = [
  { path: '', changeFrequency: 'weekly', lastModified: '2026-07-30', priority: 1 },
  { path: '/pricing', changeFrequency: 'monthly', lastModified: '2026-07-31', priority: 0.8 },
]

Bump the date on a row when you change that page, and leave every other row alone. A one-line comment saying what changed is worth the keystrokes: the next person to read the row is usually you, six weeks later, unable to tell a current date from a forgotten one.

People do forget. The point is the shape of that error — one page reporting a lastmod slightly older than the truth, so a crawler that re-fetches finds more than it expected rather than less. Automating it badly gets every entry wrong in the same direction, every day, which costs the file's credibility rather than one page's freshness.

Pick the cadence you live up to

Half of a contradiction is fixed by correcting the other half. If a page claims weekly and changes twice a year, the date is not what is wrong.

PageCadenceWhy
/blog, /templatesweeklyNew entries land often, so the index really does change
/pricing, /featuresmonthlyMoves when the product does, in bursts
/terms, /privacyyearlyMoves when a lawyer says so

The test is not what you hope the cadence will become. Look at the last three real changes to the page and choose the word that describes them.

Metadata you cannot keep honest

The general rule this is an instance of: a field you cannot keep true is worth less than a field you leave out. Omitting changefreq costs you a hint. Emitting a wrong one costs you the hint and some of the credibility of everything sitting next to it, because you have now supplied evidence about how carefully this file is maintained.

priority is the same field with a different name — the one most often set by pasting 0.8 down the whole list. If every page is important, none of them is.

So before you ship a sitemap, open it and argue with three entries. Does the date match the last time you actually touched that page? Does the cadence match the three changes before that? If you cannot answer for a field, delete the field.

Try it on your own document

Design a template in the browser and generate a real PDF — free, no card.