Notifio is a desktop rental alert app, and its 38 page site publishes a sitemap. Three weeks ago I ran a check on a sister project and found that all 69 of its hand-written lastmod dates agreed with git, with nothing in the repo enforcing it. I ran the same check here expecting the same shrug, and got the opposite answer for a reason that had not occurred to me.
curl -s https://notifio.app/sitemap.xml | grep -o '<lastmod>[^<]*' | sort | uniq -c 32 2026-09-14 4 2026-10-18 1 2026-10-03 1 2026-07-21 Four distinct dates for 38 URLs. That is because only ten of them are typed at all:
const STATIC_ROUTES: MetadataRoute.Sitemap = [ { url: `${APP_URL}/`, lastModified: new Date("2026-10-18"), ... }, { url: `${APP_URL}/pricing`, lastModified: new Date("2026-10-18"), ... }, { url: `${APP_URL}/download`,lastModified: new Date("2026-09-14"), ... }, { url: `${APP_URL}/help`, lastModified: new Date("2026-07-21"), ... }, ... ]; The other 28 come from three map() calls over the catalogues that generate them, each with one date literal shared by every page it produces:
15 /alerts/[site] new Date("2026-09-14") 5 /compare/[target] new Date("2026-09-14") 8 /guides/[slug] and /for/[x] new Date("2026-09-14") That design is deliberate and I still like it: adding a site is one edit in one file, and the page, the hub grid, the footer column and the sitemap all read the same array, so a site cannot be half added. The date is the one field in that row that a shared literal cannot possibly keep true.
Nine of the ten typed dates are honest
URL sitemap git date of its own page file / 2026-10-18 2026-10-18 match /pricing 2026-10-18 2026-10-18 match /terms 2026-10-18 2026-10-18 match /privacy 2026-10-18 2026-10-18 match /about 2026-10-03 2026-10-03 match /download 2026-09-14 2026-09-14 match /alerts 2026-09-14 2026-09-14 match /compare 2026-09-14 2026-09-14 match /guides 2026-09-14 2026-09-14 match /help 2026-07-21 2026-09-14 89 days stale Nine out of ten, by hand, with nothing checking. The outlier is the interesting one, because 21 July is not a number somebody invented:
app/help/FaqAccordion.tsx 2026-07-21 app/help/ContactForm.tsx 2026-07-21 app/help/page.tsx 2026-09-14 app/help/faq-data.ts 2026-09-14 It is the date the two components on that page were written. It was true the day it was typed, the page changed twice afterwards, and the literal stayed. That is the whole failure mode of a hand-maintained date: not carelessness, decay.
The part that makes all 38 wrong
A page is not its own file. The thing a crawler fetches is the output of the route file plus everything it imports, and the shared half of that graph has its own history:
app/layout.tsx 2026-10-18 components/pricing/Price.tsx 2026-10-18 components/Footer.tsx 2026-10-03 lib/publisher.ts 2026-10-03 components/Nav.tsx 2026-09-14 app/globals.css 2026-07-25 layout.tsx wraps every page on the site, and on 18 October it changed:
-import { Space_Grotesk, DM_Sans } from "next/font/google"; +import localFont from "next/font/local"; Two font families moved from Google's hosted CSS to two .woff2 files in the repo. Which means the <head> of every page on the site now names files that did not exist before that commit, and you can read them off the live HTML today:
/_next/static/media/space_grotesk_latin-s.p.2cyn07wtgehh0.woff2 /_next/static/media/dm_sans_latin-s.p.2_mbdogr7ni8i.woff2 Present on 35 of 38 pages, which is itself a measurement with a story, and one I have written up separately. The point here is that the real last-modified date of every page on this site, by the only definition a crawler can act on, which is "did the bytes you served me change", is 18 October 2026.
4 entries say 2026-10-18 correct 1 entry says 2026-10-03 15 days early 32 entries say 2026-09-14 34 days early 1 entry says 2026-07-21 89 days early And the CDN agrees with git rather than with the sitemap. Every cached copy of these pages was rendered on 18 October, eight hours after that commit went out, so the site is internally consistent about when it last changed everywhere except in the one file whose job is to say so.
Why a frozen date is worse than no date
Google's position on lastmod is that it is used when it is consistently accurate and ignored when it is not, which makes it the rare SEO field with a credibility score attached. The failure here is not that 34 dates are a few weeks early. It is that 28 of them are a constant: the next time I fix a sentence in a guide, nothing moves, because the literal that sets the date does not live near the content it describes.
Three ways out, in increasing order of how much I like them:
- Stop emitting
lastmod. Legitimate, and it throws away a real signal to avoid maintaining it. - Read git at build time,
git log -1 --format=%cI -- <file>, per route. Accurate about files, still blind to the shared layout, and it needs full history in the build, which Vercel does not give you by default. - Put an
updatedfield on each entry in the catalogues that already generate these pages, and derive the sitemap from it. The date then sits one line away from the sentence it is about, which is the only place anybody will ever remember to change it. The shared layout stays unrepresented, and that is fine: a font swap is not news to a crawler, whatever the bytes say.
Option 3 is the one going in. The measurement that earned it was not the 89 day gap, it was the four dates that were exactly right: every single one of them belonged to a page I had edited the same week I typed its date. Hand-maintained metadata is accurate for as long as you are still looking at it.

