发布日期卡在 2023:明明刷新了文章但 SERP 显示陈旧——updatedAt + volatile + 实质改动才动日期

publishedAt 永不移动——SERP 显示陈旧。加 updatedAt、快变话题用 volatile、只在实质改动时刷日期——而不是改个错别字也动。

你”ChatGPT 插件”那篇 2023 年发的。上个月重写过——新截图、新模型名、新例子——但 frontmatter 的 publishedAt 还是 2023-03-14。SERP 在你标题旁显示”Mar 14, 2023”——读者跳过你选看起来更新鲜的对手——Google 把它当陈旧降权。文章其实被刷新了,日期没动而已。

反向陷阱一样糟:每次小修都把 publishedAt 撞到今天——所有文章看起来都刚发——Google 读出来是 date-spam 直接降权。正解:两个字段、明确的刷新策略、对合理需要频繁更新的话题加 volatile flag。

常见原因

1. 只有一个日期字段——永不动

schema 只有 publishedAt,没 updatedAt。所以日期要么永不动(看起来陈旧)要么撞(看起来 date-spam)——都错。

如何判断:查 content collection schema 有没有 updatedAt。没有——你的设计本身就有这病。

2. updatedAt 有但作者不更新

schema 有 updatedAt 但作者真刷文章时忘了撞——页面 header 显示”Updated: 2023-03-14”——但文章上周才重写。

如何判断:比 git log mtime 和 frontmatter 的 updatedAt。git mtime 比 updatedAt 新 6 个月+——错标陈旧。

3. 每次小修都撞日期

作者改个错别字、撞 updatedAt 到今天;一周后再改个错别字、再撞。文章一年没实质改动但 updatedAt 周周动——Google 注意到。

如何判断:看 git log -pupdatedAt 的变更。80% 撞动都是单行 diff——你有 date-spam。

4. 快变话题没标 volatile

“当前 ChatGPT 定价”——天然每季度过期。没有 volatile: true flag——它和常青内容一起被同样对待——应该按计划刷新。

如何判断:列含”current”/“latest”/“2024”/“pricing”/“limits”等关键词的文章——没 volatile flag 就该加。

5. Schema markup 用错日期

JSON-LD 的 datePublisheddateModified 都取自 publishedAt。Google 的新鲜算法读 dateModified——有 updatedAt 但 JSON-LD 不用——SERP 日期反映不出刷新。

如何判断:view-source 看一篇刷新过的——查 JSON-LD 块:

curl -s https://site.com/en/articles/your-slug/ | grep -A2 'dateModified'

dateModified 和老发布日期一样——schema 错了。

最短修复路径

Step 1:加一个真的 updatedAt 字段

更新 content collection schema,要求两个:

// src/content/config.ts
import { z, defineCollection } from "astro:content";

const articles = defineCollection({
  schema: z.object({
    title: z.string(),
    publishedAt: z.date(),
    updatedAt: z.date().optional(),
    volatile: z.boolean().default(false),
    // ...
  }),
});

publishedAt 首次发布时一次性设定、永不动。updatedAt 实质刷新时设(不是错别字)。

Step 2:JSON-LD + 可见日期都接 updatedAt

article layout 里显示最近那个,并发到 JSON-LD 的 dateModified

---
const { article } = Astro.props;
const lastUpdate = article.data.updatedAt ?? article.data.publishedAt;
---
<time datetime={lastUpdate.toISOString()}>
  Updated {lastUpdate.toLocaleDateString()}
</time>

<script type="application/ld+json" set:html={JSON.stringify({
  "@context": "https://schema.org",
  "@type": "Article",
  datePublished: article.data.publishedAt.toISOString(),
  dateModified: lastUpdate.toISOString(),
  // ...
})} />

SERP 显示刷新日期、Google 新鲜算法看到真动作。

Step 3:定义刷新策略

写下何时该撞 updatedAt

- 实质:加新章节、模型/定价事实更新、代码示例重写、换截图——撞
- 小修:错别字、措辞润色、加内链——不撞
- 只修对端翻译漂移:不撞(被刷的那一端各自处理)

reviewer 在 PR 上执行——小修 PR 撞了日期就给”恢复日期”评论。

Step 4:标 volatile 文章

定价、模型清单、“X 的当前状态”类——加 volatile: true

---
title: "Current ChatGPT Pricing"
volatile: true
updatedAt: 2026-05-24
---

季度脚本扫所有 volatile: trueupdatedAt 超 90 天的——成为你的刷新队列。

# scripts/audit-volatile-staleness.mjs
import fs from "node:fs";
import matter from "gray-matter";
const THRESHOLD_DAYS = 90;
const now = Date.now();
for (const f of /* 遍历文章 */ []) {
  const { data } = matter(fs.readFileSync(f, "utf8"));
  if (!data.volatile) continue;
  const last = new Date(data.updatedAt || data.publishedAt).getTime();
  const ageDays = (now - last) / 86400000;
  if (ageDays > THRESHOLD_DAYS) {
    console.log(`STALE volatile: ${f} (${Math.round(ageDays)}d old)`);
  }
}

Step 5:一次性回填存量陈旧标签

跑一次一次性回填:

1. 列 git mtime 比 publishedAt 新 6 个月+ 的
2. 每篇判断:是不是真实质刷新过
3. 是——把 updatedAt 设为合理近期日期(最后一次大编辑的 git mtime)
4. 否——别动

不要把所有文章撞到今天。挑过的、可辩护的日期。

预防

  • 两字段 schema:publishedAt 不变、updatedAt 配刷新
  • volatile: true 配时间敏感话题——季度刷新队列
  • 文档化刷新策略——reviewer 在 PR 上执行”实质 vs 小修”
  • JSON-LD dateModifiedupdatedAt,不是 publishedAt
  • 审计脚本扫 90 天+ 没刷的 volatile 文章
  • 用 git history 当真相一次性回填真实刷新过的文章

相关

标签: #内容运营 #站点质量 #站点审计 #排查 #publish-date