你更新了一篇文章。过了几天。Google 缓存版本还显示旧内容。JSON-LD 的 dateModified 是对的、页面本身渲染的是新内容——但 Google 没重抓。一个常见原因:服务器没返回 Last-Modified HTTP 头(或所有请求都返回同样的值、不随内容变)。没有 Last-Modified,Google 没法判断页面自上次抓取后有没有变,重抓优先级就会被压低。
这在静态站上最常见:Vercel 和 Netlify 通常会自动设;Firebase Hosting 和一些自托管 nginx 不会。
常见原因
按命中率从高到低。
1. 托管平台不设 Last-Modified
Firebase Hosting、GitHub Pages、某些定制 nginx 配置不会自动给静态文件设 Last-Modified。
怎么判断:
curl -sI "https://yoursite.com/article" | grep -i last-modified
为空 = 没设。
2. CDN 把头剥了
源站返回 Last-Modified,Cloudflare 或别的 CDN 为缓存原因剥掉。
怎么判断:跑两次 curl -I——一次直连源站(绕 CDN),一次正常。源站有 Last-Modified、CDN 后没有,就是 CDN 剥了。
3. Last-Modified 一直是同一个值(部署时间)
某些静态站生成器把 Last-Modified 设为构建时间,所有页面都一样。Google 看不到差异就当无信号。
怎么判断:curl -I 不同页面。共享同一个 Last-Modified 就是 bug。
4. Cache-Control: no-store 覆盖
如果设了 Cache-Control: no-store,Last-Modified 就没意义了——客户端不缓存。Google 也会把信号打折。
怎么判断:
curl -sI "https://yoursite.com/article" | grep -E 'cache-control|last-modified'
cache-control: no-store 和 last-modified 都在就是冲突。
5. 只用 ETag 但漏 Last-Modified
ETag 没问题但 Google 对 HTML 页更偏好 Last-Modified。只发 ETag 不全。
怎么判断:头里只有 ETag 没 Last-Modified。两个都加。
6. 页面由 worker / SSR 函数生成、没设头
Cloudflare Worker 或 Vercel Edge function 生成的页默认不加 Last-Modified。
怎么判断:页面是动态生成的(看 cf-ray 或 server 头)且 Last-Modified 缺失。
最短修复路径
第 1 步:确认头缺失
curl -sI "https://yoursite.com/article" | head -10
看 Last-Modified 行。没有就需要加。
第 2 步:按平台启用
Vercel — 静态文件默认有。SSR 路由的响应里加:
return new Response(html, {
headers: { 'Last-Modified': new Date(article.modifiedAt).toUTCString() },
});
Netlify — 默认有。_headers 文件自定义:
/article/*
Last-Modified: ...
(注意:_headers 不支持动态值;要动态用 Netlify Functions。)
Firebase Hosting — firebase.json 显式加:
{
"hosting": {
"headers": [{
"source": "**/*.html",
"headers": [
{ "key": "Last-Modified", "value": "Wed, 21 Oct 2025 07:28:00 GMT" }
]
}]
}
}
Firebase 静态不能动态计算;可以用 build 脚本给每次部署设值。
GitHub Pages — 不支持自定义头。Last-Modified 很重要就别用它。
Cloudflare Workers — 手动加到 Response:
return new Response(body, {
headers: {
'Last-Modified': lastModifiedDate.toUTCString(),
'Cache-Control': 'public, max-age=3600',
},
});
第 3 步:值要反映真实内容变化
每篇:
const lastModified = new Date(article.modifiedAt || article.publishedAt).toUTCString();
不要所有页都用构建时间。
第 4 步:CDN 别剥
Cloudflare → Caching → Configuration → 确保 Last-Modified 不在任何 transform 规则里。默认是保留的。
第 5 步:源站和 CDN 都验证
# 源站(用源站 IP 或绕 CDN URL)
curl -sI "https://origin.yoursite.com/article" | grep -i last-modified
# CDN 服务的
curl -sI "https://yoursite.com/article" | grep -i last-modified
两个都应有 Last-Modified。源站有、CDN 没有就是 CDN 在剥。
第 6 步:用 If-Modified-Since 测缓存行为
LM=$(curl -sI "https://yoursite.com/article" | grep -i last-modified | cut -d' ' -f2-)
curl -sI -H "If-Modified-Since: $LM" "https://yoursite.com/article"
页面没变就应返回 304 Not Modified。返回 200 说明缓存没尊重头。
预防建议
- 每次部署后立刻
curl -I三个代表页面,确认Last-Modified存在且每页不同。 Last-Modified从文章的真实modifiedAt字段设,不要从构建时间设。- CDN 配置里不要剥头。
Last-Modified配ETag一起(最佳实践)。- CDN 服务的 HTML,加缓存规则让它尊重
Last-Modified和If-Modified-Since。