seo: report real content dates as sitemap lastmod - #2684
Conversation
Every sitemap URL carried lastModified: BUILD_DATE, so each deploy told crawlers all 709 pages had just changed. That is noise, and it costs the signal on pages that genuinely did change. Content-backed URLs now report the generated_at of the exact file that serves them. 684 of 709 URLs get a real date across 21 distinct values; the remaining 25 (hand-built pages and index pages with no single backing file) keep BUILD_DATE as the fallback. contentGeneratedAt() coerces the frontmatter value to a Date. Note the type/runtime mismatch it guards: ContentFrontmatter declares generated_at as a string, but gray-matter runs js-yaml, which parses unquoted YAML timestamps into Date objects — so both shapes have to work. The lookups read through the cache the has*Content() guards already populate, so they add no file reads. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Code-analysis diffPainscore total: 7158.83 → 7156.73 (-2.1) 🆕 New findings (5)
✅ Resolved (5)
📈 Painscore deltas (top movers)
|
🧪 UI test report — ✅ all greenSuites
📊 Coverage (unit)
⏱ 10 slowest test cases
|
Deploy Preview evidence — real
|
Defect (T5)
src/app/sitemap.tssetlastModifiedtoBUILD_DATEfor every URL:SitemapEntry.lastModifiedexisted but was dead — nothing populated it. So every deployrepublished all 709 URLs claiming they had just changed. A
lastmodthat always equals thedeploy timestamp is noise: crawlers learn nothing from it, and the pages that genuinely did
change lose the signal they should have had.
Fix
Content-backed URLs now report the
generated_atof the exact file that serves them.contentGeneratedAt()insrc/lib/content.ts— coerces a content file'sgenerated_atfrontmatter to a
Date, returningundefinedwhen missing or unparseable.sitemap.ts(pageDate/corridorDate/singletonDate) pairthe right reader with that coercion. The readers were not all imported before; this adds
readPageContent,readCorridorContent,readSingletonContent.pages.push()call sites now pass a real date. Hand-built pages (homepage,/lp/card,/careers,/exchange, legal) and index pages with no single backing file keepBUILD_DATEvia the existing?? BUILD_DATEfallback — untouched.The lookups read through the same cache the
has*Content()guards already populate, so theyadd no extra file reads.
One subtlety worth reviewing
ContentFrontmatterdeclaresgenerated_at?: string, but that is wrong at runtime:gray-matter runs js-yaml, which parses unquoted YAML timestamps into JS
Dateobjects. Provenagainst the real submodule:
A naive
new Date(frontmatter.generated_at)would still work, but the helper handlesDate,quoted string, missing and unparseable inputs explicitly so this cannot regress silently.
I deliberately did not correct the declared type:
generated_atis consumed as JSON-LDdatePublishedin 12 page files that expect a string, so retyping it belongs in its own PR.Flagging for a follow-up.
Verification
Local
next buildis impossible on this box (earlyoom SIGTERMs it under memory contention fromother agents), so this is unit-level proof plus the Deploy Preview below.
1. The real
generateSitemap()run against the pinned content submodule (2a1c5937), via tsx:The 25 fallbacks are exactly the intended set: 6 static entries + 3 non-default-locale landings
/help,/stories,/content,/blog).2. Sitemap
lastmodcross-checked againstgenerated_aton disk:Static pages keep
BUILD_DATEas intended:3. Gates
4. Deploy Preview — confirmed on the built artifact. Full output in the
evidence comment.
The build stamped
BUILD_DATEat2026-08-12T14:26:32.623Z; all 6 sampled content URLs report adifferent, correct date:
CI is green across
typecheck,eslint,format,unit,e2e,analyze,reportandDeploy-Preview.Caveats
tsc --noEmitcould not complete locally — earlyoom killed it (exit 143,mem avail: 855 of 11960 MiB) across repeated attempts while other agents held the box. Thescoped typecheck above covers both changed files and everything they import, and CI's
full typecheck passed (1m3s), so this gap is closed.
lastmodis only as honest asgenerated_at. A content edit that does not refresh that fieldwill now show a stale date rather than a falsely fresh one — the better failure direction, but
it does move the burden onto the content pipeline.
origin/dev@6e14a490d, which is 7 commits past the SHA in the plan(
ad5b61b6). That drift is entirely transaction-details and i18n test work; it does not touchsitemap.ts,lib/content.ts, or the content submodule pin.src/content(separate pipeline).🤖 Generated with Claude Code