⚠️ Security Consideration
Not a security concern. This refactor only adjusts sitemap metadata used by crawlers.
Current State
apps/web/src/app/sitemap.ts declares lastModified: new Date() for:
- The three static routes (
/, /docs, /blog).
- Every doc page in
pages.map(...).
Only blog posts use a real timestamp — page.data.date from MDX frontmatter.
Problems with the current implementation:
- Every request to
/sitemap.xml claims every URL was last modified at the moment the sitemap was generated. Search engines (notably Google) treat this as an unfresh signal and may crawl more aggressively than necessary.
- The home,
/docs, and per-doc lastModified values are noise; they do not reflect when the underlying content actually changed.
- The three static routes have the same value, which makes the sitemap harder to inspect for staleness.
Located in: apps/web/src/app/sitemap.ts
Proposed State
After this change:
- Static routes (
/, /docs, /blog) — lastModified is the maximum of: (a) the latest MDX file mtime under apps/web/content/, (b) the build timestamp (last-resort fallback).
- Doc pages —
lastModified reads the mtime of the corresponding apps/web/content/docs/{slug}.mdx file (via fs.statSync or equivalent), falling back to the directory's most recent mtime for index/parent pages.
- Blog pages — unchanged; still
page.data.date.
changeFrequency values are reviewed:
/ → weekly if any content changed in the last 7 days, else monthly.
/docs and per-doc pages → monthly (matches actual cadence).
/blog and per-post pages → keep weekly and monthly.
- Sitemap is built statically at
next build time so the values are stable for a deployment.
Expected improvements:
- Crawl-budget signals are honest; Google won't re-fetch unchanged URLs based on stale
lastModified.
- The sitemap documents real publication cadence, useful for SEO tooling audits.
- Per-doc lastModified gives downstream tools a meaningful "updated at" signal.
Motivation
This refactoring is needed because:
- The current implementation makes the sitemap dishonest, which is a low-grade SEO footgun (crawl-budget waste, dilutes real change signals).
- Per-doc timestamps are a useful primitive for future features (changelog page, "last updated" badges).
Triggers for this work:
Risks
Potential risks:
- Risk 1: Reading file mtimes during
next build requires the FS to be readable from the build context (correct on Vercel; could surprise a custom CI). Mitigation: gate the FS read behind a try/catch and fall back to new Date() if the stat fails.
- Risk 2:
lastModified values changing after deploy could trigger a temporary crawl spike. Mitigation: ship in a low-traffic window; values are still bounded between deploys (static at build time).
- Risk 3:
changeFrequency is an advisory hint, not a directive; Google ignores it. Not a risk, just confirming we are not over-relying on it.
Migration Plan
Migration approach:
- Implement
lastModified derivation in a small helper file (e.g. apps/web/src/lib/sitemap-mtimes.ts) so the logic is unit-testable.
- Wire the helper into
sitemap.ts.
- Build and visually verify
/sitemap.xml against git log --format=%ad for a sample of files.
- Roll back by reverting the helper import; behavior returns to today's
new Date() everywhere.
Rollback plan: revert the helper and the sitemap.ts edit. No on-disk state changes.
Backward Compatibility
Scope
Files/Folders affected:
apps/web/src/app/sitemap.ts (primary)
apps/web/src/lib/ (new helper, optional)
Out of scope:
- Changing the OG image or
images: array.
- Touching
robots.ts or proxy.ts.
Component(s) Affected
- Multiple Components (specifically
apps/web/src/app/sitemap.ts and apps/web/src/lib/)
Priority
- p2: Medium - Normal priority
Estimated Effort
Test Coverage Requirements
(Fumadocs/sitemap does not currently have a test fixture; a small Vitest under apps/web/ for the helper is sufficient.)
Testing Approach
Testing strategy:
- Unit tests:
getContentLastModified(slug) returns expected mtime for a known fixture path.
- Integration: build the site and assert
/sitemap.xml includes the expected ISO 8601 timestamps for a sample of URLs.
Verification steps:
pnpm --filter web test (if tests are added)
pnpm --filter web build then grep lastModified .next/server/app/sitemap.xml.body or equivalent
- Spot-check three URLs (home, one doc, one blog) against
stat output.
Related Issues / Pull Requests
- Sibling issue:
[Chore]: disallow /llms-full.txt in robots.ts (same audit batch).
- Sibling issue:
[BUG]: JSON-LD assemblyVersion / datePublished / operatingSystem are wrong (same audit batch).
- Sibling issue:
[Chore]: metadataBase env name + OG image parity + baseUrl canonical (same audit batch).
Relevant Documentation
docs/internal/tasks/v1.4.x-seo-cleanup.md — full audit and the batch this refactor belongs to.
Pre-Submission Checklist
Not a security concern. This refactor only adjusts sitemap metadata used by crawlers.
Current State
apps/web/src/app/sitemap.tsdeclareslastModified: new Date()for:/,/docs,/blog).pages.map(...).Only blog posts use a real timestamp —
page.data.datefrom MDX frontmatter.Problems with the current implementation:
/sitemap.xmlclaims every URL was last modified at the moment the sitemap was generated. Search engines (notably Google) treat this as an unfresh signal and may crawl more aggressively than necessary./docs, and per-doclastModifiedvalues are noise; they do not reflect when the underlying content actually changed.Located in:
apps/web/src/app/sitemap.tsProposed State
After this change:
/,/docs,/blog) —lastModifiedis the maximum of: (a) the latest MDX file mtime underapps/web/content/, (b) the build timestamp (last-resort fallback).lastModifiedreads the mtime of the correspondingapps/web/content/docs/{slug}.mdxfile (viafs.statSyncor equivalent), falling back to the directory's most recent mtime for index/parent pages.page.data.date.changeFrequencyvalues are reviewed:/→weeklyif any content changed in the last 7 days, elsemonthly./docsand per-doc pages →monthly(matches actual cadence)./blogand per-post pages → keepweeklyandmonthly.next buildtime so the values are stable for a deployment.Expected improvements:
lastModified.Motivation
This refactoring is needed because:
Triggers for this work:
Risks
Potential risks:
next buildrequires the FS to be readable from the build context (correct on Vercel; could surprise a custom CI). Mitigation: gate the FS read behind a try/catch and fall back tonew Date()if the stat fails.lastModifiedvalues changing after deploy could trigger a temporary crawl spike. Mitigation: ship in a low-traffic window; values are still bounded between deploys (static at build time).changeFrequencyis an advisory hint, not a directive; Google ignores it. Not a risk, just confirming we are not over-relying on it.Migration Plan
Migration approach:
lastModifiedderivation in a small helper file (e.g.apps/web/src/lib/sitemap-mtimes.ts) so the logic is unit-testable.sitemap.ts./sitemap.xmlagainstgit log --format=%adfor a sample of files.new Date()everywhere.Rollback plan: revert the helper and the
sitemap.tsedit. No on-disk state changes.Backward Compatibility
Scope
Files/Folders affected:
apps/web/src/app/sitemap.ts(primary)apps/web/src/lib/(new helper, optional)Out of scope:
images:array.robots.tsorproxy.ts.Component(s) Affected
apps/web/src/app/sitemap.tsandapps/web/src/lib/)Priority
Estimated Effort
Test Coverage Requirements
(Fumadocs/sitemap does not currently have a test fixture; a small Vitest under
apps/web/for the helper is sufficient.)Testing Approach
Testing strategy:
getContentLastModified(slug)returns expected mtime for a known fixture path./sitemap.xmlincludes the expected ISO 8601 timestamps for a sample of URLs.Verification steps:
pnpm --filter web test(if tests are added)pnpm --filter web buildthengrep lastModified .next/server/app/sitemap.xml.bodyor equivalentstatoutput.Related Issues / Pull Requests
[Chore]: disallow /llms-full.txt in robots.ts(same audit batch).[BUG]: JSON-LD assemblyVersion / datePublished / operatingSystem are wrong(same audit batch).[Chore]: metadataBase env name + OG image parity + baseUrl canonical(same audit batch).Relevant Documentation
docs/internal/tasks/v1.4.x-seo-cleanup.md— full audit and the batch this refactor belongs to.Pre-Submission Checklist