Sitemap lastmod 永远是今天,Google 不再信任

sitemap 里每个 URL 的 `lastmod` 都是今天。Googlebot 抓取率不升反降,因为 Google 已经把这个字段当作不可靠信号忽略掉了。

你每周部署一次。sitemap 生成器在 build time 用 new Date().toISOString()lastmod 盖戳。Google 看到的结果是:你站点上每个页面昨天都变过 —— 那就等于没一个真变过。这就是 sitemap 被忽略的教科书签名。Googlebot 对真正更新过的页面的重抓速率反而下降,因为 Google 没办法再用 lastmod 来排优先级。最坏的情况是”已发现 — 当前未编入索引”越堆越多。修法是让 lastmod 来自真实的内容变更时间,而不是 build time。

常见原因

按命中率排序。

1. lastmod 在 build time 用 new Date() 算出来

sitemap 插件或自研生成器每次 build 跑一次 new Date().toISOString(),把这个值塞给每一条。每周 build 一次 → 每个 URL 都”每周变一次”。

怎么判断:抓在线 sitemap,grep lastmod,所有值都在几秒内,并且匹配你上次部署的时间。

2. lastmod 取自文件 mtime,但 build 步骤会把每个文件重写一遍

build 前的某一步(格式化、转换、压缩)会原地重写所有内容文件,mtime 全部更新。生成器拿 mtime 当 lastmod。每次构建每个文件都看起来刚改过。

怎么判断ls -lt src/content/ 显示所有文件都在最近一次 build 窗口内被改过,包括你几个月没碰过的。

3. lastmod 取自 CI 任务的时间戳

CI 用 --depth=1 或在某层 Docker 里 checkout,mtime 被重置。生成器拿不到 git commit 时间,fallback 到 “now”。

怎么判断:所有 lastmod 都落在一个 30 秒窗口内,每次部署是不同的统一值。

4. 源里 lastmod 是对的,被 CDN / 边缘 Worker 改写了

你从 frontmatter 正确生成了 lastmod,但边缘 Worker 重写 sitemap 响应(加缓存破坏字段、规范化格式)时把值覆盖了。

怎么判断:对比直连源站和走 CDN 拿到的 sitemap。lastmod 不一致就是它。

5. lastmod 取整到日,但每个条目落在同一天

生成器把时间戳”规范化”到 UTC 0 点。如果大部分编辑都集中在部署日的某个窗口,所有条目压成同一天,Google 会判定可疑。

怎么判断:所有 lastmod 是同一天的 00:00:00Z

6. 新增的 URL 继承”现在”而不是真实发布日期

新文章加入 sitemap 时 lastmod 直接写今天,哪怕它其实是两年前发布的。同样的逻辑在每次回填跑批时错误地 bump 所有老 URL。

怎么判断:多年没动过的老归档 URL lastmod 是最近的。

开始前

  • 改之前先快照一份当前 sitemap(或顶层索引)。修完用来对照。
  • 看 Search Console → Sitemaps → 报告详情,是否有 “valid but warning” 之类信息 —— Google 有时会明确警告 lastmod 不可靠。
  • 决定 lastmod 的真相来源:通常是 frontmatter 的 modifiedAt(缺失时 fallback 到 publishedAt)。
  • 如果你已经按 发布日期与 JSON-LD 日期不一致 审过日期,跟那里用同一个来源。

需要收集的信息

  • 当前 lastmod 值的分布:是不是大多数落在 5 分钟窗口内,还是跨越好几个月?
  • 输出 sitemap 的生成器代码或插件。
  • 每种内容类型有哪些 frontmatter 字段可用(publishedAtmodifiedAtupdatedAt)。
  • Search Console “Crawl stats” 最近的趋势 —— 是不是新增内容多,但总抓取请求数反而降了?
  • sitemap 是按内容类型拆分的,还是一份大单文件。

一步步修

按代价排序。

第 1 步:先看一眼当前 sitemap

curl -s https://example.com/sitemap.xml | \
  grep -oE '<lastmod>[^<]+</lastmod>' | sort | uniq -c | sort -rn | head -10

最大那一组覆盖几千个 URL 且匹配上次部署时间 —— bug 坐实。

第 2 步:让 lastmod 来自真实内容日期

替换 build-time 生成逻辑:

// before
{ loc: url, lastmod: new Date().toISOString() }

// after
{
  loc: url,
  lastmod: (article.modifiedAt ?? article.publishedAt),
}

没有真实时间戳的,宁可整段不写 lastmod,也别造一个。

第 3 步:frontmatter 缺日期就从 git history 回填

git log --diff-filter=AM --follow --format=%aI -- "src/content/articles/en/${slug}.mdx" \
  | head -1

这返回最近一次新增或修改这个文件的 commit 时间。对没有显式日期的文章拿来当 modifiedAt

第 4 步:让 build 前的步骤不要乱动 mtime

如果有格式化工具每次 build 都把所有文件重写一遍,改成内存里转换,或者用内容 hash 守门:

const before = await readFile(path);
const after = format(before);
if (after !== before) await writeFile(path, after);

writeFile 只在真有变化时触发。mtime 不再无意义地翻动。

第 5 步:确认 CI 保留了 git 时间戳

如果你依赖 commit 日期,确保 CI clone 拿完整历史:

- uses: actions/checkout@v4
  with:
    fetch-depth: 0

然后让 sitemap 生成器读 commit 时间而不是 mtime。

第 6 步:检查边缘层有没有改写

diff <(curl -s https://origin.example.com/sitemap.xml) \
     <(curl -s https://example.com/sitemap.xml)

lastmod 有差异,就是 CDN 或边缘 Worker 在改写。要么绕过,要么修掉。

第 7 步:重新提交并观察抓取响应

在 Search Console 重新提交 sitemap。2-3 周内观察 “Crawl stats” 曲线,看抓取是否倾向于真正变化的 URL。“已发现 — 当前未编入索引”对真正更新的页面应该慢慢减少。

验证

  • Sitemap lastmod 的分布跨越周和月,有真实方差,不是每次部署一个统一值。
  • 新文章的 lastmod 匹配 publishedAt,不是今天。
  • Search Console → Crawl stats 显示真正编辑过的 URL 重抓被优先。
  • 重要内容上的”已发现 — 当前未编入索引”开始回落。
  • CMS 编辑动作在一个部署周期内能流到 lastmod 上。

长期预防

  • 团队范围明确:lastmod 反映真实内容变化,永远不取 build time。
  • CI 断言:sitemap 里超过 30% 的条目共享同一个 lastmod 分钟,构建失败。
  • lastmod 来源跟驱动 JSON-LD dateModified 的来源是同一个 —— 一个真相来源。
  • 真正不会变的静态归档页,宁可不写 lastmod,也别挂一个过期的值。
  • 每季度审一次边缘 / CDN 的响应转换,盯住静默改写。

常见坑

  • “保险起见”把 lastmod 完全删掉。真实的 lastmod 是有用的优先级信号,只有在没靠谱来源时才不写。
  • 改了导航或 footer 之后给每个页面 bump lastmod。站点级模板改不是单页内容变化。
  • 修个错别字就把 lastmod 改成”现在”。琐碎编辑应该不动 lastmod;跟你给 dateModified 定的”有意义的编辑”规则保持一致。
  • lastmoddateModified 分别由两个独立生成器算 —— 一定会漂移;合并。
  • 一天内重交 5 次 sitemap “逼” Google 重抓。完全没效果,反而可能给 sitemap 上标记。

FAQ

Q:Google 真的会惩罚刷 lastmod 这种模式吗?

Google 公开说过,不可靠的 lastmod 值会被忽略不用于抓取优先级。“惩罚”就是当你没提供任何信号 —— 损失的是真正更新时的重抓机会。

Q:要给每个 URL 都写 lastmod 吗,还是只有变更过的写?

有可靠时间戳就写,没就别写。诚实的缺失好过编造的值。

Q:CMS 只跟踪”编辑日期”,够吗?

够,只要”编辑日期”只在真正编辑时变,不在自动保存或模板改动时变。一段时间不编辑然后看这个值有没有变,能验证。

Q:lastmod 精度应该到哪一级?日、小时、秒?

跟你的跟踪精度一致。内容站点到日(2026-05-24)够用。快变 feed 用到秒合适。别造你本没有的精度。

标签: #SEO #排查 #Sitemap #lastmod #抓取预算